Compare commits

..

No commits in common. "f5fbe1f483dae14295c70f232b3ec4558dd27a98" and "43b705995707960ae2234f45daeebe6d59018a32" have entirely different histories.

30 changed files with 321 additions and 624 deletions

View File

@ -471,11 +471,5 @@
"accountNav": "You",
"performance": "Performance",
"animatedMessageList": "Non-animated message list",
"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"
"animatedMessageListDesc": "Remove animation effects in message list, to reduce cause lag"
}

View File

@ -467,11 +467,5 @@
"accountNav": "您",
"performance": "性能",
"animatedMessageList": "无动画消息列表",
"animatedMessageListDesc": "在消息列表中禁用动画效果",
"theme": "主题",
"globalTheme": "全局应用主题",
"agedTheme": "过时主题",
"agedThemeDesc": "将全局主题降级为 Material Design 2可能发生意料之外的问题仅供实验使用",
"appBackgroundImage": "全局背景图片",
"appBackgroundImageDesc": "全局背景图片将会在所有页面中展示"
"animatedMessageListDesc": "在消息列表中禁用动画效果"
}

View File

@ -17,7 +17,6 @@ import 'package:solian/providers/relation.dart';
import 'package:solian/providers/theme_switcher.dart';
import 'package:solian/providers/websocket.dart';
import 'package:solian/services.dart';
import 'package:solian/widgets/root_container.dart';
import 'package:solian/widgets/sized_container.dart';
import 'package:flutter_app_update/flutter_app_update.dart';
import 'package:version/version.dart';
@ -256,7 +255,8 @@ class _BootstrapperShellState extends State<BootstrapperShell> {
Widget build(BuildContext context) {
if (_isBusy || _isErrored) {
return GestureDetector(
child: RootContainer(
child: Material(
color: Theme.of(context).colorScheme.surface,
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,

View File

@ -1,50 +0,0 @@
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;
}
}

View File

@ -1,26 +0,0 @@
// 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,
};

View File

@ -1,8 +1,5 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:solian/models/theme.dart';
import 'package:solian/theme.dart';
class ThemeSwitcher extends ChangeNotifier {
@ -16,21 +13,11 @@ class ThemeSwitcher extends ChangeNotifier {
Future<void> restoreTheme() async {
final prefs = await SharedPreferences.getInstance();
if (prefs.containsKey('global_theme')) {
final value = SolianThemeData.fromJson(
jsonDecode(prefs.getString('global_theme')!),
);
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,
);
if (prefs.containsKey('global_theme_color')) {
final value = prefs.getInt('global_theme_color')!;
final color = Color(value);
lightThemeData = AppTheme.build(Brightness.light, seedColor: color);
darkThemeData = AppTheme.build(Brightness.dark, seedColor: color);
notifyListeners();
}
}
@ -40,25 +27,4 @@ class ThemeSwitcher extends ChangeNotifier {
darkThemeData = dark;
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();
}
}

View File

@ -4,7 +4,6 @@ import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:solian/widgets/root_container.dart';
import 'package:solian/widgets/sized_container.dart';
import 'package:url_launcher/url_launcher_string.dart';
@ -16,7 +15,8 @@ class AboutScreen extends StatelessWidget {
const denseButtonStyle =
ButtonStyle(visualDensity: VisualDensity(vertical: -4));
return RootContainer(
return Material(
color: Theme.of(context).colorScheme.surface,
child: SizedBox(
width: double.infinity,
child: Column(

View File

@ -7,7 +7,6 @@ import 'package:solian/providers/account_status.dart';
import 'package:solian/providers/relation.dart';
import 'package:solian/router.dart';
import 'package:solian/widgets/account/account_heading.dart';
import 'package:solian/widgets/root_container.dart';
import 'package:solian/widgets/sized_container.dart';
import 'package:badges/badges.dart' as badges;
@ -50,7 +49,8 @@ class _AccountScreenState extends State<AccountScreen> {
final AuthProvider auth = Get.find();
return RootContainer(
return Material(
color: Theme.of(context).colorScheme.surface,
child: SafeArea(
child: Obx(() {
if (auth.isAuthorized.isFalse) {

View File

@ -6,7 +6,6 @@ import 'package:solian/models/relations.dart';
import 'package:solian/providers/relation.dart';
import 'package:solian/theme.dart';
import 'package:solian/widgets/account/relative_list.dart';
import 'package:solian/widgets/root_container.dart';
class FriendScreen extends StatefulWidget {
const FriendScreen({super.key});
@ -118,7 +117,8 @@ class _FriendScreenState extends State<FriendScreen>
@override
Widget build(BuildContext context) {
return RootContainer(
return Material(
color: Theme.of(context).colorScheme.surface,
child: Scaffold(
appBar: AppBar(
centerTitle: false,

View File

@ -6,7 +6,6 @@ import 'package:google_fonts/google_fonts.dart';
import 'package:solian/exceptions/request.dart';
import 'package:solian/exts.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/widgets/root_container.dart';
class NotificationPreferencesScreen extends StatefulWidget {
const NotificationPreferencesScreen({super.key});
@ -75,7 +74,8 @@ class _NotificationPreferencesScreenState
@override
Widget build(BuildContext context) {
return RootContainer(
return Material(
color: Theme.of(context).colorScheme.surface,
child: Column(
children: [
if (_isBusy) const LinearProgressIndicator().animate().scaleX(),

View File

@ -12,7 +12,6 @@ import 'package:solian/providers/auth.dart';
import 'package:solian/providers/content/attachment.dart';
import 'package:solian/services.dart';
import 'package:solian/widgets/account/account_avatar.dart';
import 'package:solian/widgets/root_container.dart';
class PersonalizeScreen extends StatefulWidget {
const PersonalizeScreen({super.key});
@ -187,7 +186,8 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
Widget build(BuildContext context) {
const double padding = 32;
return RootContainer(
return Material(
color: Theme.of(context).colorScheme.surface,
child: ListView(
children: [
if (_isBusy) const LinearProgressIndicator().animate().scaleX(),

View File

@ -28,7 +28,6 @@ import 'package:solian/widgets/daily_sign/history_chart.dart';
import 'package:solian/widgets/posts/post_list.dart';
import 'package:solian/widgets/posts/post_warped_list.dart';
import 'package:solian/widgets/reports/abuse_report.dart';
import 'package:solian/widgets/root_container.dart';
import 'package:solian/widgets/sized_container.dart';
class AccountProfilePage extends StatefulWidget {
@ -234,7 +233,8 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
return const Center(child: CircularProgressIndicator());
}
return RootContainer(
return Material(
color: Theme.of(context).colorScheme.surface,
child: DefaultTabController(
length: 3,
child: NestedScrollView(

View File

@ -11,7 +11,6 @@ import 'package:solian/providers/content/realm.dart';
import 'package:solian/providers/relation.dart';
import 'package:solian/providers/websocket.dart';
import 'package:solian/services.dart';
import 'package:solian/widgets/root_container.dart';
import 'package:solian/widgets/sized_container.dart';
import 'package:url_launcher/url_launcher_string.dart';
@ -217,7 +216,8 @@ class _SignInScreenState extends State<SignInScreen> {
@override
Widget build(BuildContext context) {
return RootContainer(
return Material(
color: Theme.of(context).colorScheme.surface,
child: CenteredContainer(
maxWidth: 360,
child: PageTransitionSwitcher(

View File

@ -3,7 +3,6 @@ import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:solian/exts.dart';
import 'package:solian/services.dart';
import 'package:solian/widgets/root_container.dart';
import 'package:solian/widgets/sized_container.dart';
import 'package:url_launcher/url_launcher_string.dart';
@ -66,7 +65,8 @@ class _SignUpScreenState extends State<SignUpScreen> {
@override
Widget build(BuildContext context) {
return RootContainer(
return Material(
color: Theme.of(context).colorScheme.surface,
child: CenteredContainer(
maxWidth: 360,
child: ListView(

View File

@ -11,7 +11,6 @@ import 'package:solian/widgets/app_bar_leading.dart';
import 'package:solian/widgets/chat/call/call_controls.dart';
import 'package:solian/widgets/chat/call/call_participant.dart';
import 'package:livekit_client/livekit_client.dart' as livekit;
import 'package:solian/widgets/root_container.dart';
class CallScreen extends StatefulWidget {
final bool hideAppBar;
@ -198,7 +197,8 @@ class _CallScreenState extends State<CallScreen> with TickerProviderStateMixin {
Widget build(BuildContext context) {
final ChatCallProvider ctrl = Get.find();
return RootContainer(
return Material(
color: Theme.of(context).colorScheme.surface,
child: Scaffold(
appBar: widget.hideAppBar
? null

View File

@ -9,7 +9,6 @@ import 'package:solian/providers/content/channel.dart';
import 'package:solian/router.dart';
import 'package:solian/theme.dart';
import 'package:solian/widgets/app_bar_title.dart';
import 'package:solian/widgets/root_container.dart';
import 'package:uuid/uuid.dart';
class ChannelOrganizeArguments {
@ -115,7 +114,8 @@ class _ChannelOrganizeScreenState extends State<ChannelOrganizeScreen> {
),
];
return RootContainer(
return Material(
color: Theme.of(context).colorScheme.surface,
child: Scaffold(
appBar: AppBar(
title: AppBarTitle('channelOrganizing'.tr),

View File

@ -19,7 +19,6 @@ import 'package:solian/widgets/app_bar_title.dart';
import 'package:solian/widgets/channel/channel_list.dart';
import 'package:solian/widgets/chat/call/chat_call_indicator.dart';
import 'package:solian/widgets/current_state_action.dart';
import 'package:solian/widgets/root_container.dart';
import 'package:solian/widgets/sidebar/empty_placeholder.dart';
class ChatScreen extends StatelessWidget {
@ -27,8 +26,9 @@ class ChatScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const RootContainer(
child: ChatList(),
return Material(
color: Theme.of(context).colorScheme.surface,
child: const ChatList(),
);
}
}
@ -40,7 +40,8 @@ class ChatListShell extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RootContainer(
return Material(
color: Theme.of(context).colorScheme.surface,
child: Row(
children: [
const SizedBox(
@ -140,186 +141,181 @@ class _ChatListState extends State<ChatList> {
return Obx(
() => DefaultTabController(
length: 2 + realms.availableRealms.length,
child: RootContainer(
child: Scaffold(
appBar: AppBar(
leading: Obx(() {
final adaptive = AppBarLeadingButton.adaptive(context);
if (adaptive != null) return adaptive;
if (_channels.isLoading.value) {
return const CircularProgressIndicator(
strokeWidth: 3,
).paddingAll(18);
}
return const SizedBox.shrink();
}),
title: AppBarTitle('chat'.tr),
centerTitle: true,
toolbarHeight: AppTheme.toolbarHeight(context),
actions: [
const BackgroundStateWidget(),
const NotificationButton(),
PopupMenuButton(
icon: const Icon(Icons.add_circle),
itemBuilder: (BuildContext context) => [
PopupMenuItem(
child: ListTile(
title: Text('channelOrganizeCommon'.tr),
leading: const Icon(Icons.tag),
contentPadding:
const EdgeInsets.symmetric(horizontal: 8),
),
onTap: () {
AppRouter.instance.pushNamed('channelOrganizing').then(
(value) {
if (value != null) {
_loadAllChannels();
}
},
);
},
child: Scaffold(
appBar: AppBar(
leading: Obx(() {
final adaptive = AppBarLeadingButton.adaptive(context);
if (adaptive != null) return adaptive;
if (_channels.isLoading.value) {
return const CircularProgressIndicator(
strokeWidth: 3,
).paddingAll(18);
}
return const SizedBox.shrink();
}),
title: AppBarTitle('chat'.tr),
centerTitle: true,
toolbarHeight: AppTheme.toolbarHeight(context),
actions: [
const BackgroundStateWidget(),
const NotificationButton(),
PopupMenuButton(
icon: const Icon(Icons.add_circle),
itemBuilder: (BuildContext context) => [
PopupMenuItem(
child: ListTile(
title: Text('channelOrganizeCommon'.tr),
leading: const Icon(Icons.tag),
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
),
PopupMenuItem(
child: ListTile(
title: Text('channelOrganizeDirect'.tr),
leading: const FaIcon(
FontAwesomeIcons.userGroup,
size: 16,
),
contentPadding:
const EdgeInsets.symmetric(horizontal: 8),
),
onTap: () {
final ChannelProvider channels = Get.find();
channels
.createDirectChannel(context, 'global')
.then((resp) {
if (resp != null) {
onTap: () {
AppRouter.instance.pushNamed('channelOrganizing').then(
(value) {
if (value != null) {
_loadAllChannels();
}
}).catchError((e) {
context.showErrorDialog(e);
});
},
),
],
),
SizedBox(
width: AppTheme.isLargeScreen(context) ? 8 : 16,
),
],
bottom: TabBar(
isScrollable: true,
dividerHeight: 0.3,
tabAlignment: TabAlignment.startOffset,
tabs: [
Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
CircleAvatar(
radius: 14,
backgroundColor:
Theme.of(context).colorScheme.primary,
child: const Icon(
Icons.forum,
size: 16,
color: Colors.white,
),
),
const Gap(8),
Text('all'.tr),
],
),
},
);
},
),
Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const CircleAvatar(
radius: 14,
child: Icon(
Icons.chat_bubble,
size: 16,
),
),
const Gap(8),
Text('channelTypeDirect'.tr),
],
PopupMenuItem(
child: ListTile(
title: Text('channelOrganizeDirect'.tr),
leading: const FaIcon(
FontAwesomeIcons.userGroup,
size: 16,
),
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
),
onTap: () {
final ChannelProvider channels = Get.find();
channels
.createDirectChannel(context, 'global')
.then((resp) {
if (resp != null) {
_loadAllChannels();
}
}).catchError((e) {
context.showErrorDialog(e);
});
},
),
...realms.availableRealms.map((x) => Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
AccountAvatar(
content: x.avatar,
radius: 14,
fallbackWidget: const Icon(
Icons.workspaces,
size: 16,
),
),
const Gap(8),
Text(x.name),
],
),
)),
],
),
),
body: Obx(() {
if (auth.isAuthorized.isFalse) {
return SigninRequiredOverlay(
onDone: () => _loadAllChannels(),
);
}
final selfId = auth.userProfile.value!['id'];
return Column(
children: [
const ChatCallCurrentIndicator(),
Expanded(
child: TabBarView(
children: [
RefreshIndicator(
onRefresh: _loadNormalChannels,
child: ChannelListWidget(
channels: _sortChannels([
..._normalChannels,
..._directChannels,
..._realmChannels.values.expand((x) => x),
]),
selfId: selfId,
useReplace: AppTheme.isLargeScreen(context),
),
SizedBox(
width: AppTheme.isLargeScreen(context) ? 8 : 16,
),
],
bottom: TabBar(
isScrollable: true,
dividerHeight: 0.3,
tabAlignment: TabAlignment.startOffset,
tabs: [
Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
CircleAvatar(
radius: 14,
backgroundColor: Theme.of(context).colorScheme.primary,
child: const Icon(
Icons.forum,
size: 16,
color: Colors.white,
),
RefreshIndicator(
onRefresh: _loadDirectChannels,
child: ChannelListWidget(
channels: _directChannels,
selfId: selfId,
useReplace: AppTheme.isLargeScreen(context),
),
),
const Gap(8),
Text('all'.tr),
],
),
),
Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const CircleAvatar(
radius: 14,
child: Icon(
Icons.chat_bubble,
size: 16,
),
...realms.availableRealms.map(
(x) => RefreshIndicator(
onRefresh: () => _loadRealmChannels(x.alias),
child: ChannelListWidget(
channels: _realmChannels[x.alias] ?? [],
selfId: selfId,
useReplace: AppTheme.isLargeScreen(context),
),
const Gap(8),
Text('channelTypeDirect'.tr),
],
),
),
...realms.availableRealms.map((x) => Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
AccountAvatar(
content: x.avatar,
radius: 14,
fallbackWidget: const Icon(
Icons.workspaces,
size: 16,
),
),
),
],
),
),
],
);
}),
const Gap(8),
Text(x.name),
],
),
)),
],
),
),
body: Obx(() {
if (auth.isAuthorized.isFalse) {
return SigninRequiredOverlay(
onDone: () => _loadAllChannels(),
);
}
final selfId = auth.userProfile.value!['id'];
return Column(
children: [
const ChatCallCurrentIndicator(),
Expanded(
child: TabBarView(
children: [
RefreshIndicator(
onRefresh: _loadNormalChannels,
child: ChannelListWidget(
channels: _sortChannels([
..._normalChannels,
..._directChannels,
..._realmChannels.values.expand((x) => x),
]),
selfId: selfId,
useReplace: AppTheme.isLargeScreen(context),
),
),
RefreshIndicator(
onRefresh: _loadDirectChannels,
child: ChannelListWidget(
channels: _directChannels,
selfId: selfId,
useReplace: AppTheme.isLargeScreen(context),
),
),
...realms.availableRealms.map(
(x) => RefreshIndicator(
onRefresh: () => _loadRealmChannels(x.alias),
child: ChannelListWidget(
channels: _realmChannels[x.alias] ?? [],
selfId: selfId,
useReplace: AppTheme.isLargeScreen(context),
),
),
),
],
),
),
],
);
}),
),
),
);

View File

@ -15,7 +15,6 @@ import 'package:solian/widgets/app_bar_leading.dart';
import 'package:solian/widgets/navigation/realm_switcher.dart';
import 'package:solian/widgets/posts/post_shuffle_swiper.dart';
import 'package:solian/widgets/posts/post_warped_list.dart';
import 'package:solian/widgets/root_container.dart';
class ExploreScreen extends StatefulWidget {
const ExploreScreen({super.key});
@ -57,7 +56,8 @@ class _ExploreScreenState extends State<ExploreScreen>
Widget build(BuildContext context) {
final AuthProvider auth = Get.find();
return RootContainer(
return Material(
color: Theme.of(context).colorScheme.surface,
child: Scaffold(
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),

View File

@ -9,7 +9,6 @@ import 'package:solian/widgets/app_bar_leading.dart';
import 'package:solian/widgets/app_bar_title.dart';
import 'package:solian/widgets/posts/post_action.dart';
import 'package:solian/widgets/posts/post_owned_list.dart';
import 'package:solian/widgets/root_container.dart';
class DraftBoxScreen extends StatefulWidget {
const DraftBoxScreen({super.key});
@ -55,7 +54,8 @@ class _DraftBoxScreenState extends State<DraftBoxScreen> {
@override
Widget build(BuildContext context) {
return RootContainer(
return Material(
color: Theme.of(context).colorScheme.surface,
child: Scaffold(
appBar: AppBar(
leading: AppBarLeadingButton.adaptive(context),

View File

@ -6,7 +6,6 @@ import 'package:solian/providers/content/posts.dart';
import 'package:solian/providers/last_read.dart';
import 'package:solian/widgets/posts/post_item.dart';
import 'package:solian/widgets/posts/post_replies.dart';
import 'package:solian/widgets/root_container.dart';
class PostDetailScreen extends StatefulWidget {
final String id;
@ -48,7 +47,8 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
@override
Widget build(BuildContext context) {
return RootContainer(
return Material(
color: Theme.of(context).colorScheme.surface,
child: FutureBuilder(
future: getDetail(),
builder: (context, snapshot) {

View File

@ -19,7 +19,6 @@ import 'package:solian/widgets/app_bar_title.dart';
import 'package:solian/widgets/markdown_text_content.dart';
import 'package:solian/widgets/posts/post_item.dart';
import 'package:badges/badges.dart' as badges;
import 'package:solian/widgets/root_container.dart';
class PostPublishArguments {
final Post? edit;
@ -152,7 +151,8 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
)
];
return RootContainer(
return Material(
color: Theme.of(context).colorScheme.surface,
child: Scaffold(
appBar: AppBar(
leading: AppBarLeadingButton.adaptive(context),

View File

@ -15,7 +15,6 @@ import 'package:solian/widgets/app_bar_leading.dart';
import 'package:solian/widgets/app_bar_title.dart';
import 'package:solian/widgets/auto_cache_image.dart';
import 'package:solian/widgets/current_state_action.dart';
import 'package:solian/widgets/root_container.dart';
import 'package:solian/widgets/sized_container.dart';
class RealmListScreen extends StatefulWidget {
@ -59,7 +58,8 @@ class _RealmListScreenState extends State<RealmListScreen> {
Widget build(BuildContext context) {
final AuthProvider auth = Get.find();
return RootContainer(
return Material(
color: Theme.of(context).colorScheme.surface,
child: Scaffold(
appBar: AppBar(
leading: AppBarLeadingButton.adaptive(context),

View File

@ -7,7 +7,6 @@ import 'package:solian/router.dart';
import 'package:solian/screens/realms/realm_organize.dart';
import 'package:solian/widgets/realms/realm_deletion.dart';
import 'package:solian/widgets/realms/realm_member.dart';
import 'package:solian/widgets/root_container.dart';
class RealmDetailScreen extends StatefulWidget {
final String alias;
@ -87,63 +86,61 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
),
];
return RootContainer(
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
children: [
const CircleAvatar(
radius: 28,
backgroundColor: Colors.teal,
child: Icon(Icons.group, color: Colors.white),
return Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
children: [
const CircleAvatar(
radius: 28,
backgroundColor: Colors.teal,
child: Icon(Icons.group, color: Colors.white),
),
const Gap(16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(widget.realm.name,
style: Theme.of(context).textTheme.bodyLarge),
Text(widget.realm.description,
style: Theme.of(context).textTheme.bodySmall),
Text(
'#${widget.realm.id.toString().padLeft(8, '0')} · ${widget.realm.alias}',
style: const TextStyle(fontSize: 11),
),
],
),
const Gap(16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(widget.realm.name,
style: Theme.of(context).textTheme.bodyLarge),
Text(widget.realm.description,
style: Theme.of(context).textTheme.bodySmall),
Text(
'#${widget.realm.id.toString().padLeft(8, '0')} · ${widget.realm.alias}',
style: const TextStyle(fontSize: 11),
),
],
),
)
],
),
)
],
),
const Divider(thickness: 0.3),
Expanded(
child: ListView(
children: [
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Icons.supervisor_account),
trailing: const Icon(Icons.chevron_right),
title: Text('realmMembers'.tr),
onTap: () => showMemberList(),
),
...(_isOwned ? ownerActions : List.empty()),
const Divider(thickness: 0.3),
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: _isOwned
? const Icon(Icons.delete)
: const Icon(Icons.exit_to_app),
title: Text(_isOwned ? 'delete'.tr : 'leave'.tr),
onTap: () => promptLeaveChannel(),
),
],
),
),
const Divider(thickness: 0.3),
Expanded(
child: ListView(
children: [
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Icons.supervisor_account),
trailing: const Icon(Icons.chevron_right),
title: Text('realmMembers'.tr),
onTap: () => showMemberList(),
),
...(_isOwned ? ownerActions : List.empty()),
const Divider(thickness: 0.3),
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: _isOwned
? const Icon(Icons.delete)
: const Icon(Icons.exit_to_app),
title: Text(_isOwned ? 'delete'.tr : 'leave'.tr),
onTap: () => promptLeaveChannel(),
),
],
),
],
),
),
],
);
}
}

View File

@ -13,7 +13,6 @@ import 'package:solian/router.dart';
import 'package:solian/theme.dart';
import 'package:solian/widgets/app_bar_leading.dart';
import 'package:solian/widgets/app_bar_title.dart';
import 'package:solian/widgets/root_container.dart';
import 'package:uuid/uuid.dart';
class RealmOrganizeArguments {
@ -190,7 +189,8 @@ class _RealmOrganizeScreenState extends State<RealmOrganizeScreen> {
@override
Widget build(BuildContext context) {
return RootContainer(
return Material(
color: Theme.of(context).colorScheme.surface,
child: Scaffold(
appBar: AppBar(
leading: AppBarLeadingButton.adaptive(context),

View File

@ -16,7 +16,6 @@ import 'package:solian/theme.dart';
import 'package:solian/widgets/app_bar_leading.dart';
import 'package:solian/widgets/channel/channel_list.dart';
import 'package:solian/widgets/posts/post_list.dart';
import 'package:solian/widgets/root_container.dart';
class RealmViewScreen extends StatefulWidget {
final String alias;
@ -87,7 +86,8 @@ class _RealmViewScreenState extends State<RealmViewScreen> {
@override
Widget build(BuildContext context) {
return RootContainer(
return Material(
color: Theme.of(context).colorScheme.surface,
child: DefaultTabController(
length: 2,
child: NestedScrollView(

View File

@ -1,25 +1,17 @@
import 'dart:convert';
import 'dart:io';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart';
import 'package:in_app_review/in_app_review.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:solian/exceptions/request.dart';
import 'package:solian/exts.dart';
import 'package:solian/models/theme.dart';
import 'package:solian/platform.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/providers/database/database.dart';
import 'package:solian/providers/theme_switcher.dart';
import 'package:solian/router.dart';
import 'package:solian/theme.dart';
import 'package:solian/widgets/reports/abuse_report.dart';
import 'package:solian/widgets/root_container.dart';
class SettingScreen extends StatefulWidget {
const SettingScreen({super.key});
@ -30,7 +22,6 @@ class SettingScreen extends StatefulWidget {
class _SettingScreenState extends State<SettingScreen> {
SharedPreferences? _prefs;
String _docBasepath = '/';
Widget _buildCaptionHeader(String title) {
return Container(
@ -41,38 +32,39 @@ class _SettingScreenState extends State<SettingScreen> {
);
}
static final List<SolianThemeData> _presentTheme = [
SolianThemeData(
id: 'themeColorRed',
seedColor: const Color.fromRGBO(154, 98, 91, 1),
),
SolianThemeData(
id: 'themeColorBlue',
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),
),
Widget _buildThemeColorButton(String label, Color color) {
return IconButton(
icon: Icon(Icons.circle, color: color),
tooltip: label,
onPressed: () {
context.read<ThemeSwitcher>().setTheme(
AppTheme.build(
Brightness.light,
seedColor: color,
),
AppTheme.build(
Brightness.dark,
seedColor: color,
),
);
_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
void initState() {
super.initState();
getApplicationDocumentsDirectory().then((dir) {
_docBasepath = dir.path;
if (mounted) {
setState(() {});
}
});
SharedPreferences.getInstance().then((inst) {
_prefs = inst;
if (mounted) {
@ -83,101 +75,20 @@ class _SettingScreenState extends State<SettingScreen> {
@override
Widget build(BuildContext context) {
return RootContainer(
return Material(
color: Theme.of(context).colorScheme.surface,
child: ListView(
children: [
_buildCaptionHeader('theme'.tr),
ListTile(
leading: const Icon(Icons.palette),
contentPadding: const EdgeInsets.symmetric(horizontal: 22),
title: Text('globalTheme'.tr),
trailing: DropdownButtonHideUnderline(
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(),
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,
),
),
),
_buildCaptionHeader('themeColor'.tr),
SizedBox(
height: 56,
child: ListView(
scrollDirection: Axis.horizontal,
children: _presentTheme
.map((x) => _buildThemeColorButton(x.$1, x.$2))
.toList(),
).paddingSymmetric(horizontal: 12, vertical: 8),
),
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),
Tooltip(
message: 'settingsNotificationBgServiceDesc'.tr,

View File

@ -5,7 +5,6 @@ import 'package:solian/theme.dart';
import 'package:solian/widgets/app_bar_title.dart';
import 'package:solian/widgets/app_bar_leading.dart';
import 'package:solian/widgets/current_state_action.dart';
import 'package:solian/widgets/root_container.dart';
class TitleShell extends StatelessWidget {
final bool showAppBar;
@ -27,26 +26,24 @@ class TitleShell extends StatelessWidget {
Widget build(BuildContext context) {
assert(state != null || title != null);
return RootContainer(
child: Scaffold(
appBar: showAppBar
? AppBar(
leading: AppBarLeadingButton.adaptive(context),
title: AppBarTitle(
title ?? (state!.topRoute?.name?.tr ?? 'page'.tr),
return Scaffold(
appBar: showAppBar
? AppBar(
leading: AppBarLeadingButton.adaptive(context),
title: AppBarTitle(
title ?? (state!.topRoute?.name?.tr ?? 'page'.tr),
),
centerTitle: isCenteredTitle,
toolbarHeight: AppTheme.toolbarHeight(context),
actions: [
const BackgroundStateWidget(),
SizedBox(
width: AppTheme.isLargeScreen(context) ? 8 : 16,
),
centerTitle: isCenteredTitle,
toolbarHeight: AppTheme.toolbarHeight(context),
actions: [
const BackgroundStateWidget(),
SizedBox(
width: AppTheme.isLargeScreen(context) ? 8 : 16,
),
],
)
: null,
body: child,
),
],
)
: null,
body: child,
);
}
}

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:solian/models/theme.dart';
import 'package:solian/platform.dart';
abstract class AppTheme {
@ -39,7 +38,6 @@ abstract class AppTheme {
brightness: brightness,
seedColor: seedColor ?? const Color.fromRGBO(154, 98, 91, 1),
),
scaffoldBackgroundColor: Colors.transparent,
snackBarTheme: const SnackBarThemeData(
behavior: SnackBarBehavior.floating,
),
@ -57,36 +55,4 @@ 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(),
),
);
}
}

View File

@ -1,48 +0,0 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:solian/platform.dart';
class RootContainer extends StatelessWidget {
final Widget? child;
const RootContainer({super.key, this.child});
@override
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(
color: Theme.of(context).colorScheme.surface,
child: child,
);
},
);
}
}

View File

@ -1,12 +1,12 @@
import 'package:flutter/material.dart';
import 'package:solian/widgets/root_container.dart';
class EmptyPagePlaceholder extends StatelessWidget {
const EmptyPagePlaceholder({super.key});
@override
Widget build(BuildContext context) {
return RootContainer(
return Material(
color: Theme.of(context).colorScheme.surface,
child: Center(
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(12)),