refactor: Use adaptive_theme

This commit is contained in:
Christian Pauly 2021-01-15 19:41:55 +01:00
parent 46c83867ad
commit 5d52c26146
8 changed files with 191 additions and 410 deletions

View File

@ -1,6 +1,7 @@
import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:circular_check_box/circular_check_box.dart'; import 'package:circular_check_box/circular_check_box.dart';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/utils/event_extension.dart'; import 'package:fluffychat/utils/event_extension.dart';
import 'package:fluffychat/utils/matrix_locals.dart'; import 'package:fluffychat/utils/matrix_locals.dart';
import 'package:fluffychat/utils/room_status_extension.dart'; import 'package:fluffychat/utils/room_status_extension.dart';
@ -18,7 +19,6 @@ import '../dialogs/send_file_dialog.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart';
import '../matrix.dart'; import '../matrix.dart';
import '../mouse_over_builder.dart'; import '../mouse_over_builder.dart';
import '../theme_switcher.dart';
enum ArchivedRoomAction { delete, rejoin } enum ArchivedRoomAction { delete, rejoin }
@ -142,7 +142,7 @@ class ChatListItem extends StatelessWidget {
room.lastEvent?.senderId == Matrix.of(context).client.userID; room.lastEvent?.senderId == Matrix.of(context).client.userID;
return Center( return Center(
child: Material( child: Material(
color: chatListItemColor(context, activeChat, selected), color: FluffyThemes.chatListItemColor(context, activeChat, selected),
child: ListTile( child: ListTile(
onLongPress: onLongPress, onLongPress: onLongPress,
leading: MouseOverBuilder( leading: MouseOverBuilder(

View File

@ -1,83 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import '../components/matrix.dart';
import '../components/theme_switcher.dart';
class ThemesSettings extends StatefulWidget {
@override
ThemesSettingsState createState() => ThemesSettingsState();
}
class ThemesSettingsState extends State<ThemesSettings> {
Themes _selectedTheme;
bool _amoledEnabled;
@override
Widget build(BuildContext context) {
final matrix = Matrix.of(context);
final themeEngine = ThemeSwitcherWidget.of(context);
_selectedTheme = themeEngine.selectedTheme;
_amoledEnabled = themeEngine.amoledEnabled;
return Column(
children: <Widget>[
RadioListTile<Themes>(
title: Text(
L10n.of(context).systemTheme,
),
value: Themes.system,
groupValue: _selectedTheme,
activeColor: Theme.of(context).primaryColor,
onChanged: (Themes value) {
setState(() {
_selectedTheme = value;
themeEngine.switchTheme(matrix, value, _amoledEnabled);
});
},
),
RadioListTile<Themes>(
title: Text(
L10n.of(context).lightTheme,
),
value: Themes.light,
groupValue: _selectedTheme,
activeColor: Theme.of(context).primaryColor,
onChanged: (Themes value) {
setState(() {
_selectedTheme = value;
themeEngine.switchTheme(matrix, value, _amoledEnabled);
});
},
),
RadioListTile<Themes>(
title: Text(
L10n.of(context).darkTheme,
),
value: Themes.dark,
groupValue: _selectedTheme,
activeColor: Theme.of(context).primaryColor,
onChanged: (Themes value) {
setState(() {
_selectedTheme = value;
themeEngine.switchTheme(matrix, value, _amoledEnabled);
});
},
),
SwitchListTile(
title: Text(
L10n.of(context).useAmoledTheme,
),
value: _amoledEnabled,
activeColor: Theme.of(context).primaryColor,
onChanged: (bool value) {
setState(() {
_amoledEnabled = value;
themeEngine.switchTheme(matrix, _selectedTheme, value);
});
},
),
],
);
}
}

View File

@ -1,276 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'matrix.dart';
import '../config/setting_keys.dart';
enum Themes {
light,
dark,
system,
}
final ThemeData lightTheme = ThemeData(
primaryColorDark: Colors.white,
primaryColorLight: Color(0xff121212),
brightness: Brightness.light,
primaryColor: Color(0xFF5625BA),
backgroundColor: Colors.white,
secondaryHeaderColor: Color(0xFFECECF2),
scaffoldBackgroundColor: Colors.white,
snackBarTheme: SnackBarThemeData(
behavior: kIsWeb ? SnackBarBehavior.floating : SnackBarBehavior.fixed,
),
dialogTheme: DialogTheme(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
),
popupMenuTheme: PopupMenuThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
),
appBarTheme: AppBarTheme(
brightness: Brightness.light,
color: Colors.white,
textTheme: TextTheme(
headline6: TextStyle(
color: Colors.black,
fontSize: 20,
),
),
iconTheme: IconThemeData(color: Colors.black),
),
);
final ThemeData darkTheme = ThemeData.dark().copyWith(
primaryColorDark: Color(0xff1B1B1B),
primaryColorLight: Colors.white,
primaryColor: Color(0xFF8966CF),
errorColor: Color(0xFFCF6679),
backgroundColor: Color(0xff121212),
scaffoldBackgroundColor: Color(0xff1B1B1B),
accentColor: Color(0xFFF5B4D2),
secondaryHeaderColor: Color(0xff202020),
snackBarTheme: SnackBarThemeData(
behavior: kIsWeb ? SnackBarBehavior.floating : SnackBarBehavior.fixed,
),
dialogTheme: DialogTheme(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
),
popupMenuTheme: PopupMenuThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
),
appBarTheme: AppBarTheme(
brightness: Brightness.dark,
color: Color(0xff1D1D1D),
textTheme: TextTheme(
headline6: TextStyle(
color: Colors.white,
fontSize: 20,
),
),
iconTheme: IconThemeData(color: Colors.white),
),
);
final ThemeData amoledTheme = ThemeData.dark().copyWith(
primaryColorDark: Color(0xff121212),
primaryColorLight: Colors.white,
primaryColor: Color(0xFF8966CF),
errorColor: Color(0xFFCF6679),
backgroundColor: Colors.black,
scaffoldBackgroundColor: Colors.black,
accentColor: Color(0xFFF5B4D2),
secondaryHeaderColor: Color(0xff1D1D1D),
snackBarTheme: SnackBarThemeData(
behavior: kIsWeb ? SnackBarBehavior.floating : SnackBarBehavior.fixed,
),
dialogTheme: DialogTheme(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
),
popupMenuTheme: PopupMenuThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
),
appBarTheme: AppBarTheme(
brightness: Brightness.dark,
color: Color(0xff1D1D1D),
textTheme: TextTheme(
headline6: TextStyle(
color: Colors.white,
fontSize: 20,
),
),
iconTheme: IconThemeData(color: Colors.white),
),
);
Color chatListItemColor(BuildContext context, bool activeChat, bool selected) =>
selected
? Theme.of(context).primaryColor.withAlpha(100)
: Theme.of(context).brightness == Brightness.light
? activeChat
? Color(0xFFE8E8E8)
: Colors.white
: activeChat
? ThemeSwitcherWidget.of(context).amoledEnabled
? Color(0xff121212)
: Colors.black
: ThemeSwitcherWidget.of(context).amoledEnabled
? Colors.black
: Color(0xff121212);
Color blackWhiteColor(BuildContext context) =>
Theme.of(context).brightness == Brightness.light
? Colors.white
: Colors.black;
class ThemeSwitcher extends InheritedWidget {
final ThemeSwitcherWidgetState data;
const ThemeSwitcher({
Key key,
@required this.data,
@required Widget child,
}) : assert(child != null),
super(key: key, child: child);
@override
bool updateShouldNotify(ThemeSwitcher old) {
return this != old;
}
}
class ThemeSwitcherWidget extends StatefulWidget {
final Widget child;
ThemeSwitcherWidget({Key key, @required this.child})
: assert(child != null),
super(key: key);
@override
ThemeSwitcherWidgetState createState() => ThemeSwitcherWidgetState();
/// Returns the (nearest) Client instance of your application.
static ThemeSwitcherWidgetState of(BuildContext context) {
var newState =
(context.dependOnInheritedWidgetOfExactType<ThemeSwitcher>()).data;
newState.context = context;
return newState;
}
}
class ThemeSwitcherWidgetState extends State<ThemeSwitcherWidget> {
ThemeData themeData;
Themes selectedTheme;
bool amoledEnabled;
@override
BuildContext context;
Future loadSelection(MatrixState matrix) async {
var item = await matrix.store.getItem(SettingKeys.theme) ?? 'system';
selectedTheme = Themes.values.firstWhere(
(e) => e.toString() == 'Themes.' + item,
orElse: () => Themes.system);
amoledEnabled = await matrix.store.getItemBool(SettingKeys.amoledEnabled);
switchTheme(matrix, selectedTheme, amoledEnabled);
return;
}
void switchTheme(
MatrixState matrix, Themes newTheme, bool amoled_enabled) async {
ThemeData theme;
switch (newTheme) {
case Themes.light:
theme = lightTheme;
break;
case Themes.dark:
if (amoled_enabled) {
theme = amoledTheme;
} else {
theme = darkTheme;
}
break;
case Themes.system:
// This needs to be a low level call as we don't have a MaterialApp yet
var brightness =
MediaQueryData.fromWindow(WidgetsBinding.instance.window)
.platformBrightness;
if (brightness == Brightness.dark) {
if (amoled_enabled) {
theme = amoledTheme;
} else {
theme = darkTheme;
}
} else {
theme = lightTheme;
}
break;
}
await saveThemeValue(matrix, newTheme);
await saveAmoledEnabledValue(matrix, amoled_enabled);
setState(() {
amoledEnabled = amoled_enabled;
selectedTheme = newTheme;
themeData = theme;
});
}
Future saveThemeValue(MatrixState matrix, Themes value) async {
await matrix.store
.setItem(SettingKeys.theme, value.toString().split('.').last);
}
Future saveAmoledEnabledValue(MatrixState matrix, bool value) async {
await matrix.store.setItem(SettingKeys.amoledEnabled, value.toString());
}
void setup() async {
final matrix = Matrix.of(context);
await loadSelection(matrix);
}
@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (amoledEnabled == null || selectedTheme == null) {
setup();
}
});
super.initState();
}
@override
Widget build(BuildContext context) {
if (themeData == null) {
// This needs to be a low level call as we don't have a MaterialApp yet
var brightness = MediaQueryData.fromWindow(WidgetsBinding.instance.window)
.platformBrightness;
if (brightness == Brightness.dark) {
themeData = darkTheme;
} else {
themeData = lightTheme;
}
return ThemeSwitcher(
data: this,
child: widget.child,
);
} else {
return ThemeSwitcher(
data: this,
child: widget.child,
);
}
}
}

90
lib/config/themes.dart Normal file
View File

@ -0,0 +1,90 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
abstract class FluffyThemes {
static ThemeData light = ThemeData(
primaryColorDark: Colors.white,
primaryColorLight: Color(0xff121212),
brightness: Brightness.light,
primaryColor: Color(0xFF5625BA),
backgroundColor: Colors.white,
secondaryHeaderColor: Color(0xFFECECF2),
scaffoldBackgroundColor: Colors.white,
snackBarTheme: SnackBarThemeData(
behavior: kIsWeb ? SnackBarBehavior.floating : SnackBarBehavior.fixed,
),
dialogTheme: DialogTheme(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
),
popupMenuTheme: PopupMenuThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
),
appBarTheme: AppBarTheme(
brightness: Brightness.light,
color: Colors.white,
textTheme: TextTheme(
headline6: TextStyle(
color: Colors.black,
fontSize: 20,
),
),
iconTheme: IconThemeData(color: Colors.black),
),
);
static ThemeData dark = ThemeData.dark().copyWith(
primaryColorDark: Color(0xff121212),
primaryColorLight: Colors.white,
primaryColor: Color(0xFF8966CF),
errorColor: Color(0xFFCF6679),
backgroundColor: Colors.black,
scaffoldBackgroundColor: Colors.black,
accentColor: Color(0xFFF5B4D2),
secondaryHeaderColor: Color(0xff1D1D1D),
snackBarTheme: SnackBarThemeData(
behavior: kIsWeb ? SnackBarBehavior.floating : SnackBarBehavior.fixed,
),
dialogTheme: DialogTheme(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
),
popupMenuTheme: PopupMenuThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
),
appBarTheme: AppBarTheme(
brightness: Brightness.dark,
color: Color(0xff1D1D1D),
textTheme: TextTheme(
headline6: TextStyle(
color: Colors.white,
fontSize: 20,
),
),
iconTheme: IconThemeData(color: Colors.white),
),
);
static Color chatListItemColor(
BuildContext context, bool activeChat, bool selected) =>
selected
? Theme.of(context).primaryColor.withAlpha(100)
: Theme.of(context).brightness == Brightness.light
? activeChat
? Color(0xFFE8E8E8)
: Colors.white
: activeChat
? Color(0xff121212)
: Colors.black;
static Color blackWhiteColor(BuildContext context) =>
Theme.of(context).brightness == Brightness.light
? Colors.white
: Colors.black;
}

View File

@ -1,6 +1,7 @@
// @dart=2.9 // @dart=2.9
import 'dart:async'; import 'dart:async';
import 'package:adaptive_theme/adaptive_theme.dart';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/utils/sentry_controller.dart'; import 'package:fluffychat/utils/sentry_controller.dart';
import 'package:fluffychat/views/homeserver_picker.dart'; import 'package:fluffychat/views/homeserver_picker.dart';
@ -13,7 +14,7 @@ import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:universal_html/prefer_universal/html.dart' as html; import 'package:universal_html/prefer_universal/html.dart' as html;
import 'components/matrix.dart'; import 'components/matrix.dart';
import 'components/theme_switcher.dart'; import 'config/themes.dart';
import 'utils/localized_exception_extension.dart'; import 'utils/localized_exception_extension.dart';
import 'app_config.dart'; import 'app_config.dart';
import 'views/chat_list.dart'; import 'views/chat_list.dart';
@ -34,51 +35,49 @@ class App extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Matrix( return Matrix(
child: Builder( child: Builder(
builder: (BuildContext context) => ThemeSwitcherWidget( builder: (BuildContext context) => AdaptiveTheme(
child: Builder( light: FluffyThemes.light,
builder: (context) => MaterialApp( dark: FluffyThemes.dark,
title: '${AppConfig.applicationName}', initial: AdaptiveThemeMode.system,
theme: ThemeSwitcherWidget.of(context).themeData, builder: (theme, darkTheme) => MaterialApp(
localizationsDelegates: L10n.localizationsDelegates, title: '${AppConfig.applicationName}',
supportedLocales: L10n.supportedLocales, theme: theme,
locale: kIsWeb darkTheme: darkTheme,
? Locale( localizationsDelegates: L10n.localizationsDelegates,
html.window.navigator.language.split('-').first) supportedLocales: L10n.supportedLocales,
: null, locale: kIsWeb
home: FutureBuilder<LoginState>( ? Locale(html.window.navigator.language.split('-').first)
future: Matrix.of(context) : null,
.client home: FutureBuilder<LoginState>(
.onLoginStateChanged future:
.stream Matrix.of(context).client.onLoginStateChanged.stream.first,
.first, builder: (context, snapshot) {
builder: (context, snapshot) { LoadingDialog.defaultTitle = L10n.of(context).loadingPleaseWait;
LoadingDialog.defaultTitle = LoadingDialog.defaultBackLabel = L10n.of(context).close;
L10n.of(context).loadingPleaseWait; LoadingDialog.defaultOnError =
LoadingDialog.defaultBackLabel = L10n.of(context).close; (Object e) => e.toLocalizedString(context);
LoadingDialog.defaultOnError = if (snapshot.hasError) {
(Object e) => e.toLocalizedString(context); WidgetsBinding.instance
if (snapshot.hasError) { .addPostFrameCallback((_) => FlushbarHelper.createError(
WidgetsBinding.instance.addPostFrameCallback((_) => title: L10n.of(context).oopsSomethingWentWrong,
FlushbarHelper.createError( message: snapshot.error.toString(),
title: L10n.of(context).oopsSomethingWentWrong, ).show(context));
message: snapshot.error.toString(), return HomeserverPicker();
).show(context)); }
return HomeserverPicker(); if (!snapshot.hasData) {
} return Scaffold(
if (!snapshot.hasData) { body: Center(
return Scaffold( child: CircularProgressIndicator(),
body: Center(
child: CircularProgressIndicator(),
),
);
}
if (Matrix.of(context).client.isLogged()) {
return ChatListView();
}
return HomeserverPicker();
},
), ),
)), );
}
if (Matrix.of(context).client.isLogged()) {
return ChatListView();
}
return HomeserverPicker();
},
),
),
), ),
), ),
); );

View File

@ -1,7 +1,7 @@
import 'dart:io'; import 'dart:io';
import 'package:adaptive_theme/adaptive_theme.dart';
import 'package:fluffychat/components/adaptive_page_layout.dart'; import 'package:fluffychat/components/adaptive_page_layout.dart';
import 'package:fluffychat/components/settings_themes.dart';
import 'package:fluffychat/config/setting_keys.dart'; import 'package:fluffychat/config/setting_keys.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
@ -43,6 +43,23 @@ class _SettingsStyleState extends State<SettingsStyle> {
setState(() => null); setState(() => null);
} }
AdaptiveThemeMode _currentTheme;
void _switchTheme(AdaptiveThemeMode newTheme, BuildContext context) {
switch (newTheme) {
case AdaptiveThemeMode.light:
AdaptiveTheme.of(context).setLight();
break;
case AdaptiveThemeMode.dark:
AdaptiveTheme.of(context).setDark();
break;
case AdaptiveThemeMode.system:
AdaptiveTheme.of(context).setSystem();
break;
}
setState(() => _currentTheme = newTheme);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -51,7 +68,24 @@ class _SettingsStyleState extends State<SettingsStyle> {
), ),
body: ListView( body: ListView(
children: [ children: [
ThemesSettings(), RadioListTile<AdaptiveThemeMode>(
groupValue: _currentTheme,
value: AdaptiveThemeMode.system,
title: Text(L10n.of(context).systemTheme),
onChanged: (t) => _switchTheme(t, context),
),
RadioListTile<AdaptiveThemeMode>(
groupValue: _currentTheme,
value: AdaptiveThemeMode.light,
title: Text(L10n.of(context).lightTheme),
onChanged: (t) => _switchTheme(t, context),
),
RadioListTile<AdaptiveThemeMode>(
groupValue: _currentTheme,
value: AdaptiveThemeMode.dark,
title: Text(L10n.of(context).darkTheme),
onChanged: (t) => _switchTheme(t, context),
),
Divider(thickness: 1), Divider(thickness: 1),
ListTile( ListTile(
title: Text( title: Text(

View File

@ -15,6 +15,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.9.3" version: "0.9.3"
adaptive_page_layout:
dependency: "direct main"
description:
name: adaptive_page_layout
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.6"
adaptive_theme:
dependency: "direct main"
description:
name: adaptive_theme
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
analyzer: analyzer:
dependency: transitive dependency: transitive
description: description:
@ -831,12 +845,12 @@ packages:
source: hosted source: hosted
version: "3.0.13" version: "3.0.13"
provider: provider:
dependency: transitive dependency: "direct main"
description: description:
name: provider name: provider
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.3.2+2" version: "4.3.2+4"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:

View File

@ -22,6 +22,9 @@ dependencies:
cached_network_image: ^2.5.0 cached_network_image: ^2.5.0
firebase_messaging: ^7.0.3 firebase_messaging: ^7.0.3
flutter_local_notifications: ^3.0.3 flutter_local_notifications: ^3.0.3
adaptive_page_layout: ^0.1.6
provider: ^4.3.2+4
adaptive_theme: ^1.1.0
# desktop_notifications: ^0.0.0-dev.4 // Currently blocked by: https://github.com/canonical/desktop_notifications.dart/issues/5 # desktop_notifications: ^0.0.0-dev.4 // Currently blocked by: https://github.com/canonical/desktop_notifications.dart/issues/5
matrix_link_text: ^0.3.2 matrix_link_text: ^0.3.2
path_provider: ^1.6.27 path_provider: ^1.6.27