✨ Better theme & background image
This commit is contained in:
parent
fcf4dc7a2d
commit
f5fbe1f483
@ -471,5 +471,11 @@
|
|||||||
"accountNav": "You",
|
"accountNav": "You",
|
||||||
"performance": "Performance",
|
"performance": "Performance",
|
||||||
"animatedMessageList": "Non-animated message list",
|
"animatedMessageList": "Non-animated message list",
|
||||||
"animatedMessageListDesc": "Remove animation effects in message list, to reduce cause lag"
|
"animatedMessageListDesc": "Remove animation effects in message list, to reduce cause lag",
|
||||||
|
"theme": "Theme",
|
||||||
|
"globalTheme": "Global theme",
|
||||||
|
"agedTheme": "Old school style theme",
|
||||||
|
"agedThemeDesc": "Downgrade the global theme to Material Design 2. Unexpected issues may occur. For experimental use only.",
|
||||||
|
"appBackgroundImage": "Global background image",
|
||||||
|
"appBackgroundImageDesc": "The global background image will be displayed on all pages"
|
||||||
}
|
}
|
||||||
|
@ -467,5 +467,11 @@
|
|||||||
"accountNav": "您",
|
"accountNav": "您",
|
||||||
"performance": "性能",
|
"performance": "性能",
|
||||||
"animatedMessageList": "无动画消息列表",
|
"animatedMessageList": "无动画消息列表",
|
||||||
"animatedMessageListDesc": "在消息列表中禁用动画效果"
|
"animatedMessageListDesc": "在消息列表中禁用动画效果",
|
||||||
|
"theme": "主题",
|
||||||
|
"globalTheme": "全局应用主题",
|
||||||
|
"agedTheme": "过时主题",
|
||||||
|
"agedThemeDesc": "将全局主题降级为 Material Design 2,可能发生意料之外的问题,仅供实验使用",
|
||||||
|
"appBackgroundImage": "全局背景图片",
|
||||||
|
"appBackgroundImageDesc": "全局背景图片将会在所有页面中展示"
|
||||||
}
|
}
|
||||||
|
50
lib/models/theme.dart
Normal file
50
lib/models/theme.dart
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
part 'theme.g.dart';
|
||||||
|
|
||||||
|
@JsonSerializable(converters: [ColorConverter()])
|
||||||
|
class SolianThemeData {
|
||||||
|
String id;
|
||||||
|
Color seedColor;
|
||||||
|
String? fontFamily;
|
||||||
|
List<String>? fontFamilyFallback;
|
||||||
|
|
||||||
|
SolianThemeData({
|
||||||
|
required this.id,
|
||||||
|
required this.seedColor,
|
||||||
|
this.fontFamily,
|
||||||
|
this.fontFamilyFallback,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SolianThemeData.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SolianThemeDataFromJson(json);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => _$SolianThemeDataToJson(this);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => id.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (other is SolianThemeData) {
|
||||||
|
return id == other.id;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ColorConverter extends JsonConverter<Color, int> {
|
||||||
|
const ColorConverter();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color fromJson(int json) {
|
||||||
|
return Color(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int toJson(Color object) {
|
||||||
|
return object.value;
|
||||||
|
}
|
||||||
|
}
|
26
lib/models/theme.g.dart
Normal file
26
lib/models/theme.g.dart
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'theme.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
SolianThemeData _$SolianThemeDataFromJson(Map<String, dynamic> json) =>
|
||||||
|
SolianThemeData(
|
||||||
|
id: json['id'] as String,
|
||||||
|
seedColor:
|
||||||
|
const ColorConverter().fromJson((json['seed_color'] as num).toInt()),
|
||||||
|
fontFamily: json['font_family'] as String?,
|
||||||
|
fontFamilyFallback: (json['font_family_fallback'] as List<dynamic>?)
|
||||||
|
?.map((e) => e as String)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$SolianThemeDataToJson(SolianThemeData instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'seed_color': const ColorConverter().toJson(instance.seedColor),
|
||||||
|
'font_family': instance.fontFamily,
|
||||||
|
'font_family_fallback': instance.fontFamilyFallback,
|
||||||
|
};
|
@ -1,5 +1,8 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:solian/models/theme.dart';
|
||||||
import 'package:solian/theme.dart';
|
import 'package:solian/theme.dart';
|
||||||
|
|
||||||
class ThemeSwitcher extends ChangeNotifier {
|
class ThemeSwitcher extends ChangeNotifier {
|
||||||
@ -13,11 +16,21 @@ class ThemeSwitcher extends ChangeNotifier {
|
|||||||
|
|
||||||
Future<void> restoreTheme() async {
|
Future<void> restoreTheme() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
if (prefs.containsKey('global_theme_color')) {
|
if (prefs.containsKey('global_theme')) {
|
||||||
final value = prefs.getInt('global_theme_color')!;
|
final value = SolianThemeData.fromJson(
|
||||||
final color = Color(value);
|
jsonDecode(prefs.getString('global_theme')!),
|
||||||
lightThemeData = AppTheme.build(Brightness.light, seedColor: color);
|
);
|
||||||
darkThemeData = AppTheme.build(Brightness.dark, seedColor: color);
|
final agedTheme = prefs.getBool('aged_theme');
|
||||||
|
lightThemeData = AppTheme.buildFromData(
|
||||||
|
Brightness.light,
|
||||||
|
value,
|
||||||
|
useMaterial3: agedTheme == null ? true : !agedTheme,
|
||||||
|
);
|
||||||
|
darkThemeData = AppTheme.buildFromData(
|
||||||
|
Brightness.dark,
|
||||||
|
value,
|
||||||
|
useMaterial3: agedTheme == null ? true : !agedTheme,
|
||||||
|
);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -27,4 +40,25 @@ class ThemeSwitcher extends ChangeNotifier {
|
|||||||
darkThemeData = dark;
|
darkThemeData = dark;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> setThemeData(SolianThemeData? data) async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
if (data == null) {
|
||||||
|
prefs.remove('global_theme');
|
||||||
|
} else {
|
||||||
|
prefs.setString(
|
||||||
|
'global_theme',
|
||||||
|
jsonEncode(data.toJson()),
|
||||||
|
);
|
||||||
|
lightThemeData = AppTheme.buildFromData(Brightness.light, data);
|
||||||
|
darkThemeData = AppTheme.buildFromData(Brightness.dark, data);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setAgedTheme(bool enabled) async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
prefs.setBool('aged_theme', enabled);
|
||||||
|
await restoreTheme();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,6 +140,7 @@ class _ChatListState extends State<ChatList> {
|
|||||||
return Obx(
|
return Obx(
|
||||||
() => DefaultTabController(
|
() => DefaultTabController(
|
||||||
length: 2 + realms.availableRealms.length,
|
length: 2 + realms.availableRealms.length,
|
||||||
|
child: RootContainer(
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: Obx(() {
|
leading: Obx(() {
|
||||||
@ -165,7 +166,8 @@ class _ChatListState extends State<ChatList> {
|
|||||||
child: ListTile(
|
child: ListTile(
|
||||||
title: Text('channelOrganizeCommon'.tr),
|
title: Text('channelOrganizeCommon'.tr),
|
||||||
leading: const Icon(Icons.tag),
|
leading: const Icon(Icons.tag),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
|
contentPadding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 8),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
AppRouter.instance.pushNamed('channelOrganizing').then(
|
AppRouter.instance.pushNamed('channelOrganizing').then(
|
||||||
@ -184,7 +186,8 @@ class _ChatListState extends State<ChatList> {
|
|||||||
FontAwesomeIcons.userGroup,
|
FontAwesomeIcons.userGroup,
|
||||||
size: 16,
|
size: 16,
|
||||||
),
|
),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
|
contentPadding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 8),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
final ChannelProvider channels = Get.find();
|
final ChannelProvider channels = Get.find();
|
||||||
@ -216,7 +219,8 @@ class _ChatListState extends State<ChatList> {
|
|||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 14,
|
radius: 14,
|
||||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
backgroundColor:
|
||||||
|
Theme.of(context).colorScheme.primary,
|
||||||
child: const Icon(
|
child: const Icon(
|
||||||
Icons.forum,
|
Icons.forum,
|
||||||
size: 16,
|
size: 16,
|
||||||
@ -317,6 +321,7 @@ class _ChatListState extends State<ChatList> {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import 'package:solian/router.dart';
|
|||||||
import 'package:solian/screens/realms/realm_organize.dart';
|
import 'package:solian/screens/realms/realm_organize.dart';
|
||||||
import 'package:solian/widgets/realms/realm_deletion.dart';
|
import 'package:solian/widgets/realms/realm_deletion.dart';
|
||||||
import 'package:solian/widgets/realms/realm_member.dart';
|
import 'package:solian/widgets/realms/realm_member.dart';
|
||||||
|
import 'package:solian/widgets/root_container.dart';
|
||||||
|
|
||||||
class RealmDetailScreen extends StatefulWidget {
|
class RealmDetailScreen extends StatefulWidget {
|
||||||
final String alias;
|
final String alias;
|
||||||
@ -86,7 +87,8 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
return Column(
|
return RootContainer(
|
||||||
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
@ -141,6 +143,7 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,23 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:in_app_review/in_app_review.dart';
|
import 'package:in_app_review/in_app_review.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:solian/exceptions/request.dart';
|
import 'package:solian/exceptions/request.dart';
|
||||||
import 'package:solian/exts.dart';
|
import 'package:solian/exts.dart';
|
||||||
|
import 'package:solian/models/theme.dart';
|
||||||
import 'package:solian/platform.dart';
|
import 'package:solian/platform.dart';
|
||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
import 'package:solian/providers/database/database.dart';
|
import 'package:solian/providers/database/database.dart';
|
||||||
import 'package:solian/providers/theme_switcher.dart';
|
import 'package:solian/providers/theme_switcher.dart';
|
||||||
import 'package:solian/router.dart';
|
import 'package:solian/router.dart';
|
||||||
import 'package:solian/theme.dart';
|
|
||||||
import 'package:solian/widgets/reports/abuse_report.dart';
|
import 'package:solian/widgets/reports/abuse_report.dart';
|
||||||
import 'package:solian/widgets/root_container.dart';
|
import 'package:solian/widgets/root_container.dart';
|
||||||
|
|
||||||
@ -23,6 +30,7 @@ class SettingScreen extends StatefulWidget {
|
|||||||
|
|
||||||
class _SettingScreenState extends State<SettingScreen> {
|
class _SettingScreenState extends State<SettingScreen> {
|
||||||
SharedPreferences? _prefs;
|
SharedPreferences? _prefs;
|
||||||
|
String _docBasepath = '/';
|
||||||
|
|
||||||
Widget _buildCaptionHeader(String title) {
|
Widget _buildCaptionHeader(String title) {
|
||||||
return Container(
|
return Container(
|
||||||
@ -33,39 +41,38 @@ class _SettingScreenState extends State<SettingScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildThemeColorButton(String label, Color color) {
|
static final List<SolianThemeData> _presentTheme = [
|
||||||
return IconButton(
|
SolianThemeData(
|
||||||
icon: Icon(Icons.circle, color: color),
|
id: 'themeColorRed',
|
||||||
tooltip: label,
|
seedColor: const Color.fromRGBO(154, 98, 91, 1),
|
||||||
onPressed: () {
|
|
||||||
context.read<ThemeSwitcher>().setTheme(
|
|
||||||
AppTheme.build(
|
|
||||||
Brightness.light,
|
|
||||||
seedColor: color,
|
|
||||||
),
|
),
|
||||||
AppTheme.build(
|
SolianThemeData(
|
||||||
Brightness.dark,
|
id: 'themeColorBlue',
|
||||||
seedColor: color,
|
seedColor: const Color.fromRGBO(103, 96, 193, 1),
|
||||||
|
),
|
||||||
|
SolianThemeData(
|
||||||
|
id: 'themeColorMiku',
|
||||||
|
seedColor: const Color.fromRGBO(56, 120, 126, 1),
|
||||||
|
),
|
||||||
|
SolianThemeData(
|
||||||
|
id: 'themeColorKagamine',
|
||||||
|
seedColor: const Color.fromRGBO(244, 183, 63, 1),
|
||||||
|
),
|
||||||
|
SolianThemeData(
|
||||||
|
id: 'themeColorLuka',
|
||||||
|
seedColor: const Color.fromRGBO(243, 174, 218, 1),
|
||||||
),
|
),
|
||||||
);
|
|
||||||
_prefs?.setInt('global_theme_color', color.value);
|
|
||||||
context.clearSnackbar();
|
|
||||||
context.showSnackbar('themeColorApplied'.tr);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static final List<(String, Color)> _presentTheme = [
|
|
||||||
('themeColorRed', const Color.fromRGBO(154, 98, 91, 1)),
|
|
||||||
('themeColorBlue', const Color.fromRGBO(103, 96, 193, 1)),
|
|
||||||
('themeColorMiku', const Color.fromRGBO(56, 120, 126, 1)),
|
|
||||||
('themeColorKagamine', const Color.fromRGBO(244, 183, 63, 1)),
|
|
||||||
('themeColorLuka', const Color.fromRGBO(243, 174, 218, 1)),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
getApplicationDocumentsDirectory().then((dir) {
|
||||||
|
_docBasepath = dir.path;
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
});
|
||||||
SharedPreferences.getInstance().then((inst) {
|
SharedPreferences.getInstance().then((inst) {
|
||||||
_prefs = inst;
|
_prefs = inst;
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
@ -79,15 +86,97 @@ class _SettingScreenState extends State<SettingScreen> {
|
|||||||
return RootContainer(
|
return RootContainer(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
_buildCaptionHeader('themeColor'.tr),
|
_buildCaptionHeader('theme'.tr),
|
||||||
SizedBox(
|
ListTile(
|
||||||
height: 56,
|
leading: const Icon(Icons.palette),
|
||||||
child: ListView(
|
contentPadding: const EdgeInsets.symmetric(horizontal: 22),
|
||||||
scrollDirection: Axis.horizontal,
|
title: Text('globalTheme'.tr),
|
||||||
children: _presentTheme
|
trailing: DropdownButtonHideUnderline(
|
||||||
.map((x) => _buildThemeColorButton(x.$1, x.$2))
|
child: DropdownButton2<SolianThemeData>(
|
||||||
|
isExpanded: true,
|
||||||
|
hint: Text(
|
||||||
|
'theme'.tr,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
items: _presentTheme
|
||||||
|
.map((SolianThemeData item) =>
|
||||||
|
DropdownMenuItem<SolianThemeData>(
|
||||||
|
value: item,
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.circle, color: item.seedColor),
|
||||||
|
const Gap(8),
|
||||||
|
Text(
|
||||||
|
item.id.tr,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
))
|
||||||
.toList(),
|
.toList(),
|
||||||
).paddingSymmetric(horizontal: 12, vertical: 8),
|
value: (_prefs?.containsKey('global_theme') ?? false)
|
||||||
|
? SolianThemeData.fromJson(
|
||||||
|
jsonDecode(_prefs!.getString('global_theme')!),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
onChanged: (SolianThemeData? value) {
|
||||||
|
context.read<ThemeSwitcher>().setThemeData(value);
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
buttonStyleData: const ButtonStyleData(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
height: 40,
|
||||||
|
width: 140,
|
||||||
|
),
|
||||||
|
menuItemStyleData: const MenuItemStyleData(
|
||||||
|
height: 40,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CheckboxListTile(
|
||||||
|
secondary: const Icon(Icons.military_tech),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 22),
|
||||||
|
title: Text('agedTheme'.tr),
|
||||||
|
subtitle: Text('agedThemeDesc'.tr),
|
||||||
|
value: _prefs?.getBool('aged_theme') ?? false,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
context.read<ThemeSwitcher>().setAgedTheme(value);
|
||||||
|
}
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (!PlatformInfo.isWeb)
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.wallpaper),
|
||||||
|
contentPadding: const EdgeInsets.only(left: 22, right: 31),
|
||||||
|
title: Text('appBackgroundImage'.tr),
|
||||||
|
subtitle: Text('appBackgroundImageDesc'.tr),
|
||||||
|
trailing: File('$_docBasepath/app_background_image').existsSync()
|
||||||
|
? const Icon(Icons.check_box)
|
||||||
|
: const Icon(Icons.check_box_outline_blank),
|
||||||
|
onTap: () async {
|
||||||
|
if (File('$_docBasepath/app_background_image').existsSync()) {
|
||||||
|
File('$_docBasepath/app_background_image').deleteSync();
|
||||||
|
} else {
|
||||||
|
final image = await ImagePicker().pickImage(
|
||||||
|
source: ImageSource.gallery,
|
||||||
|
);
|
||||||
|
if (image == null) return;
|
||||||
|
|
||||||
|
await File(image.path)
|
||||||
|
.copy('$_docBasepath/app_background_image');
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
_buildCaptionHeader('notification'.tr),
|
_buildCaptionHeader('notification'.tr),
|
||||||
Tooltip(
|
Tooltip(
|
||||||
|
@ -5,6 +5,7 @@ import 'package:solian/theme.dart';
|
|||||||
import 'package:solian/widgets/app_bar_title.dart';
|
import 'package:solian/widgets/app_bar_title.dart';
|
||||||
import 'package:solian/widgets/app_bar_leading.dart';
|
import 'package:solian/widgets/app_bar_leading.dart';
|
||||||
import 'package:solian/widgets/current_state_action.dart';
|
import 'package:solian/widgets/current_state_action.dart';
|
||||||
|
import 'package:solian/widgets/root_container.dart';
|
||||||
|
|
||||||
class TitleShell extends StatelessWidget {
|
class TitleShell extends StatelessWidget {
|
||||||
final bool showAppBar;
|
final bool showAppBar;
|
||||||
@ -26,7 +27,8 @@ class TitleShell extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
assert(state != null || title != null);
|
assert(state != null || title != null);
|
||||||
|
|
||||||
return Scaffold(
|
return RootContainer(
|
||||||
|
child: Scaffold(
|
||||||
appBar: showAppBar
|
appBar: showAppBar
|
||||||
? AppBar(
|
? AppBar(
|
||||||
leading: AppBarLeadingButton.adaptive(context),
|
leading: AppBarLeadingButton.adaptive(context),
|
||||||
@ -44,6 +46,7 @@ class TitleShell extends StatelessWidget {
|
|||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
body: child,
|
body: child,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:solian/models/theme.dart';
|
||||||
import 'package:solian/platform.dart';
|
import 'package:solian/platform.dart';
|
||||||
|
|
||||||
abstract class AppTheme {
|
abstract class AppTheme {
|
||||||
@ -38,6 +39,7 @@ abstract class AppTheme {
|
|||||||
brightness: brightness,
|
brightness: brightness,
|
||||||
seedColor: seedColor ?? const Color.fromRGBO(154, 98, 91, 1),
|
seedColor: seedColor ?? const Color.fromRGBO(154, 98, 91, 1),
|
||||||
),
|
),
|
||||||
|
scaffoldBackgroundColor: Colors.transparent,
|
||||||
snackBarTheme: const SnackBarThemeData(
|
snackBarTheme: const SnackBarThemeData(
|
||||||
behavior: SnackBarBehavior.floating,
|
behavior: SnackBarBehavior.floating,
|
||||||
),
|
),
|
||||||
@ -55,4 +57,36 @@ abstract class AppTheme {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ThemeData buildFromData(
|
||||||
|
Brightness brightness,
|
||||||
|
SolianThemeData data, {
|
||||||
|
bool useMaterial3 = true,
|
||||||
|
}) {
|
||||||
|
return ThemeData(
|
||||||
|
brightness: brightness,
|
||||||
|
useMaterial3: useMaterial3,
|
||||||
|
colorScheme: ColorScheme.fromSeed(
|
||||||
|
brightness: brightness,
|
||||||
|
seedColor: data.seedColor,
|
||||||
|
),
|
||||||
|
snackBarTheme: const SnackBarThemeData(
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
),
|
||||||
|
scaffoldBackgroundColor: Colors.transparent,
|
||||||
|
fontFamily: data.fontFamily ?? 'Comfortaa',
|
||||||
|
fontFamilyFallback: data.fontFamilyFallback ??
|
||||||
|
[
|
||||||
|
'NotoSansSC',
|
||||||
|
'NotoSansHK',
|
||||||
|
'NotoSansJP',
|
||||||
|
if (PlatformInfo.isWeb) 'NotoSansEmoji',
|
||||||
|
],
|
||||||
|
typography: Typography.material2021(
|
||||||
|
colorScheme: brightness == Brightness.light
|
||||||
|
? const ColorScheme.light()
|
||||||
|
: const ColorScheme.dark(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:solian/platform.dart';
|
||||||
|
|
||||||
class RootContainer extends StatelessWidget {
|
class RootContainer extends StatelessWidget {
|
||||||
final Widget? child;
|
final Widget? child;
|
||||||
@ -7,9 +11,38 @@ class RootContainer extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
return FutureBuilder(
|
||||||
|
future: PlatformInfo.isWeb
|
||||||
|
? Future.value(null)
|
||||||
|
: getApplicationDocumentsDirectory(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
final path = '${snapshot.data!.path}/app_background_image';
|
||||||
|
final file = File(path);
|
||||||
|
if (file.existsSync()) {
|
||||||
|
return Material(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
backgroundBlendMode: BlendMode.darken,
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
image: DecorationImage(
|
||||||
|
opacity: 0.5,
|
||||||
|
image: FileImage(file),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
color: Theme.of(context).colorScheme.surface,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user