Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
2c4040096f | |||
b449735bf5 | |||
dd01f964d4 | |||
6daa04c208 |
@ -391,5 +391,6 @@
|
||||
"userLevel10": "Grandmaster",
|
||||
"userLevel11": "Legend",
|
||||
"userLevel12": "Mythic",
|
||||
"userLevel13": "Immortal"
|
||||
"userLevel13": "Immortal",
|
||||
"postBrowsingIn": "Browsing in @region"
|
||||
}
|
||||
|
@ -392,5 +392,6 @@
|
||||
"userLevel10": "出神入化",
|
||||
"userLevel11": "名垂千古",
|
||||
"userLevel12": "独占鳌头",
|
||||
"userLevel13": "万古流芳"
|
||||
"userLevel13": "万古流芳",
|
||||
"postBrowsingIn": "浏览 @region 内的帖子中"
|
||||
}
|
||||
|
@ -155,13 +155,14 @@ class PostEditorController extends GetxController {
|
||||
);
|
||||
}
|
||||
|
||||
void localRead() {
|
||||
SharedPreferences.getInstance().then((inst) {
|
||||
if (inst.containsKey('post_editor_local_save')) {
|
||||
isRestoreFromLocal.value = true;
|
||||
payload = jsonDecode(inst.getString('post_editor_local_save')!);
|
||||
}
|
||||
});
|
||||
Future<bool> localRead() async {
|
||||
final inst = await SharedPreferences.getInstance();
|
||||
if (inst.containsKey('post_editor_local_save')) {
|
||||
isRestoreFromLocal.value = true;
|
||||
payload = jsonDecode(inst.getString('post_editor_local_save')!);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void localClear() {
|
||||
|
@ -9,6 +9,7 @@ import 'package:solian/providers/last_read.dart';
|
||||
|
||||
class PostListController extends GetxController {
|
||||
String? author;
|
||||
String? realm;
|
||||
|
||||
/// The polling source modifier.
|
||||
/// - `0`: default recommendations
|
||||
@ -99,8 +100,10 @@ class PostListController extends GetxController {
|
||||
final idx = <dynamic>{};
|
||||
postList.retainWhere((x) => idx.add(x.id));
|
||||
|
||||
var lastId = postList.map((x) => x.id).reduce(max);
|
||||
Get.find<LastReadProvider>().feedLastReadAt = lastId;
|
||||
if (postList.isNotEmpty) {
|
||||
var lastId = postList.map((x) => x.id).reduce(max);
|
||||
Get.find<LastReadProvider>().feedLastReadAt = lastId;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -123,16 +126,21 @@ class PostListController extends GetxController {
|
||||
resp = await provider.listRecommendations(
|
||||
pageKey,
|
||||
channel: 'shuffle',
|
||||
realm: realm,
|
||||
);
|
||||
break;
|
||||
case 1:
|
||||
resp = await provider.listRecommendations(
|
||||
pageKey,
|
||||
channel: 'friends',
|
||||
realm: realm,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
resp = await provider.listRecommendations(pageKey);
|
||||
resp = await provider.listRecommendations(
|
||||
pageKey,
|
||||
realm: realm,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import 'package:solian/providers/attachment_uploader.dart';
|
||||
import 'package:solian/providers/daily_sign.dart';
|
||||
import 'package:solian/providers/last_read.dart';
|
||||
import 'package:solian/providers/link_expander.dart';
|
||||
import 'package:solian/providers/navigation.dart';
|
||||
import 'package:solian/providers/stickers.dart';
|
||||
import 'package:solian/providers/theme_switcher.dart';
|
||||
import 'package:solian/providers/websocket.dart';
|
||||
@ -79,8 +80,8 @@ Future<void> _initializePlatformComponents() async {
|
||||
}
|
||||
|
||||
final themeSwitcher = ThemeSwitcher(
|
||||
lightThemeData: SolianTheme.build(Brightness.light),
|
||||
darkThemeData: SolianTheme.build(Brightness.dark),
|
||||
lightThemeData: AppTheme.build(Brightness.light),
|
||||
darkThemeData: AppTheme.build(Brightness.dark),
|
||||
);
|
||||
|
||||
class SolianApp extends StatelessWidget {
|
||||
@ -123,6 +124,8 @@ class SolianApp extends StatelessWidget {
|
||||
}
|
||||
|
||||
void _initializeProviders(BuildContext context) async {
|
||||
Get.put(NavigationStateProvider());
|
||||
|
||||
Get.lazyPut(() => AuthProvider());
|
||||
Get.lazyPut(() => RelationshipProvider());
|
||||
Get.lazyPut(() => PostProvider());
|
||||
|
@ -12,9 +12,12 @@ class Realm {
|
||||
String alias;
|
||||
String name;
|
||||
String description;
|
||||
String? avatar;
|
||||
String? banner;
|
||||
bool isPublic;
|
||||
bool isCommunity;
|
||||
int? accountId;
|
||||
int? externalId;
|
||||
|
||||
Realm({
|
||||
required this.id,
|
||||
@ -24,9 +27,12 @@ class Realm {
|
||||
required this.alias,
|
||||
required this.name,
|
||||
required this.description,
|
||||
required this.avatar,
|
||||
required this.banner,
|
||||
required this.isPublic,
|
||||
required this.isCommunity,
|
||||
this.accountId,
|
||||
this.externalId,
|
||||
});
|
||||
|
||||
factory Realm.fromJson(Map<String, dynamic> json) => _$RealmFromJson(json);
|
||||
|
@ -16,9 +16,12 @@ Realm _$RealmFromJson(Map<String, dynamic> json) => Realm(
|
||||
alias: json['alias'] as String,
|
||||
name: json['name'] as String,
|
||||
description: json['description'] as String,
|
||||
avatar: json['avatar'] as String?,
|
||||
banner: json['banner'] as String?,
|
||||
isPublic: json['is_public'] as bool,
|
||||
isCommunity: json['is_community'] as bool,
|
||||
accountId: (json['account_id'] as num?)?.toInt(),
|
||||
externalId: (json['external_id'] as num?)?.toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$RealmToJson(Realm instance) => <String, dynamic>{
|
||||
@ -29,9 +32,12 @@ Map<String, dynamic> _$RealmToJson(Realm instance) => <String, dynamic>{
|
||||
'alias': instance.alias,
|
||||
'name': instance.name,
|
||||
'description': instance.description,
|
||||
'avatar': instance.avatar,
|
||||
'banner': instance.banner,
|
||||
'is_public': instance.isPublic,
|
||||
'is_community': instance.isCommunity,
|
||||
'account_id': instance.accountId,
|
||||
'external_id': instance.externalId,
|
||||
};
|
||||
|
||||
RealmMember _$RealmMemberFromJson(Map<String, dynamic> json) => RealmMember(
|
||||
|
6
lib/providers/navigation.dart
Normal file
6
lib/providers/navigation.dart
Normal file
@ -0,0 +1,6 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:solian/models/realm.dart';
|
||||
|
||||
class NavigationStateProvider extends GetxController {
|
||||
final Rx<Realm?> focusedRealm = Rx(null);
|
||||
}
|
@ -16,8 +16,8 @@ class ThemeSwitcher extends ChangeNotifier {
|
||||
if (prefs.containsKey('global_theme_color')) {
|
||||
final value = prefs.getInt('global_theme_color')!;
|
||||
final color = Color(value);
|
||||
lightThemeData = SolianTheme.build(Brightness.light, seedColor: color);
|
||||
darkThemeData = SolianTheme.build(Brightness.dark, seedColor: color);
|
||||
lightThemeData = AppTheme.build(Brightness.light, seedColor: color);
|
||||
darkThemeData = AppTheme.build(Brightness.dark, seedColor: color);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
@ -154,6 +154,7 @@ abstract class AppRouter {
|
||||
name: 'channelChat',
|
||||
builder: (context, state) {
|
||||
return ChannelChatScreen(
|
||||
key: UniqueKey(),
|
||||
alias: state.pathParameters['alias']!,
|
||||
realm: state.uri.queryParameters['realm'] ?? 'global',
|
||||
);
|
||||
|
@ -133,7 +133,7 @@ class _FriendScreenState extends State<FriendScreen>
|
||||
).paddingAll(14),
|
||||
),
|
||||
SizedBox(
|
||||
width: SolianTheme.isLargeScreen(context) ? 8 : 16,
|
||||
width: AppTheme.isLargeScreen(context) ? 8 : 16,
|
||||
),
|
||||
],
|
||||
bottom: TabBar(
|
||||
|
@ -152,7 +152,7 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
|
||||
SliverAppBar(
|
||||
centerTitle: false,
|
||||
floating: true,
|
||||
toolbarHeight: SolianTheme.toolbarHeight(context),
|
||||
toolbarHeight: AppTheme.toolbarHeight(context),
|
||||
leadingWidth: 24,
|
||||
automaticallyImplyLeading: false,
|
||||
flexibleSpace: Row(
|
||||
@ -207,7 +207,7 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
|
||||
onPressed: null,
|
||||
),
|
||||
SizedBox(
|
||||
width: SolianTheme.isLargeScreen(context) ? 8 : 16,
|
||||
width: AppTheme.isLargeScreen(context) ? 8 : 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -205,7 +205,7 @@ class _CallScreenState extends State<CallScreen> with TickerProviderStateMixin {
|
||||
: AppBar(
|
||||
leading: AppBarLeadingButton.adaptive(context),
|
||||
centerTitle: true,
|
||||
toolbarHeight: SolianTheme.toolbarHeight(context),
|
||||
toolbarHeight: AppTheme.toolbarHeight(context),
|
||||
title: Obx(
|
||||
() => RichText(
|
||||
textAlign: TextAlign.center,
|
||||
|
@ -217,8 +217,8 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
|
||||
leading: AppBarLeadingButton.adaptive(context),
|
||||
title: AppBarTitle(title),
|
||||
centerTitle: false,
|
||||
titleSpacing: SolianTheme.titleSpacing(context),
|
||||
toolbarHeight: SolianTheme.toolbarHeight(context),
|
||||
titleSpacing: AppTheme.titleSpacing(context),
|
||||
toolbarHeight: AppTheme.toolbarHeight(context),
|
||||
actions: [
|
||||
const BackgroundStateWidget(),
|
||||
Builder(builder: (context) {
|
||||
@ -255,7 +255,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: SolianTheme.isLargeScreen(context) ? 8 : 16,
|
||||
width: AppTheme.isLargeScreen(context) ? 8 : 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -276,7 +276,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
|
||||
channel: _channel!,
|
||||
ongoingCall: _ongoingCall!,
|
||||
onJoin: () {
|
||||
if (!SolianTheme.isLargeScreen(context)) {
|
||||
if (!AppTheme.isLargeScreen(context)) {
|
||||
final ChatCallProvider call = Get.find();
|
||||
call.gotoScreen(context);
|
||||
}
|
||||
@ -337,7 +337,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
|
||||
),
|
||||
Obx(() {
|
||||
final ChatCallProvider call = Get.find();
|
||||
if (call.isMounted.value && SolianTheme.isLargeScreen(context)) {
|
||||
if (call.isMounted.value && AppTheme.isLargeScreen(context)) {
|
||||
return const Expanded(
|
||||
child: Row(children: [
|
||||
VerticalDivider(width: 0.3, thickness: 0.3),
|
||||
|
@ -110,7 +110,7 @@ class _ChannelOrganizeScreenState extends State<ChannelOrganizeScreen> {
|
||||
appBar: AppBar(
|
||||
title: AppBarTitle('channelOrganizing'.tr),
|
||||
centerTitle: false,
|
||||
toolbarHeight: SolianTheme.toolbarHeight(context),
|
||||
toolbarHeight: AppTheme.toolbarHeight(context),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: _isBusy ? null : () => applyChannel(),
|
||||
|
@ -47,7 +47,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
leading: AppBarLeadingButton.adaptive(context),
|
||||
title: AppBarTitle('chat'.tr),
|
||||
centerTitle: true,
|
||||
toolbarHeight: SolianTheme.toolbarHeight(context),
|
||||
toolbarHeight: AppTheme.toolbarHeight(context),
|
||||
actions: [
|
||||
const BackgroundStateWidget(),
|
||||
const NotificationButton(),
|
||||
@ -95,7 +95,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
width: SolianTheme.isLargeScreen(context) ? 8 : 16,
|
||||
width: AppTheme.isLargeScreen(context) ? 8 : 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -315,7 +315,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
|
||||
Card(
|
||||
child: ListTile(
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 24),
|
||||
const EdgeInsets.only(left: 24, right: 32),
|
||||
trailing: const Icon(Icons.inbox_outlined),
|
||||
title: Text('notifyEmpty'.tr),
|
||||
subtitle: Text('notifyEmptyCaption'.tr),
|
||||
@ -368,18 +368,23 @@ class _DashboardScreenState extends State<DashboardScreen> {
|
||||
return SizedBox(
|
||||
width: min(480, width),
|
||||
child: Card(
|
||||
child: SingleChildScrollView(
|
||||
child: PostListEntryWidget(
|
||||
item: item,
|
||||
isClickable: true,
|
||||
isShowEmbed: true,
|
||||
isNestedClickable: true,
|
||||
onUpdate: (_) {
|
||||
_pullPosts();
|
||||
},
|
||||
backgroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceContainerLow,
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(8),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: PostListEntryWidget(
|
||||
item: item,
|
||||
isClickable: true,
|
||||
isShowEmbed: true,
|
||||
isNestedClickable: true,
|
||||
onUpdate: (_) {
|
||||
_pullPosts();
|
||||
},
|
||||
backgroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceContainerLow,
|
||||
),
|
||||
),
|
||||
),
|
||||
).paddingSymmetric(horizontal: 8),
|
||||
@ -499,7 +504,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
|
||||
|
||||
/// Footer
|
||||
Column(
|
||||
mainAxisAlignment: SolianTheme.isLargeScreen(context)
|
||||
mainAxisAlignment: AppTheme.isLargeScreen(context)
|
||||
? MainAxisAlignment.start
|
||||
: MainAxisAlignment.center,
|
||||
children: [
|
||||
|
@ -1,8 +1,11 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:solian/controllers/post_list_controller.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:solian/providers/navigation.dart';
|
||||
import 'package:solian/router.dart';
|
||||
import 'package:solian/screens/account/notification.dart';
|
||||
import 'package:solian/theme.dart';
|
||||
@ -25,21 +28,34 @@ class _FeedScreenState extends State<FeedScreen>
|
||||
late final PostListController _postController;
|
||||
late final TabController _tabController;
|
||||
|
||||
List<StreamSubscription>? _subscriptions;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final navState = Get.find<NavigationStateProvider>();
|
||||
_postController = PostListController();
|
||||
_postController.realm = navState.focusedRealm.value?.alias;
|
||||
_tabController = TabController(length: 3, vsync: this);
|
||||
_tabController.addListener(() {
|
||||
if (_postController.mode.value == _tabController.index) return;
|
||||
_postController.mode.value = _tabController.index;
|
||||
_postController.reloadAllOver();
|
||||
});
|
||||
_subscriptions = [
|
||||
Get.find<NavigationStateProvider>().focusedRealm.listen((value) {
|
||||
if (value?.alias != _postController.realm) {
|
||||
_postController.realm = value?.alias;
|
||||
_postController.reloadAllOver();
|
||||
}
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final AuthProvider auth = Get.find();
|
||||
final NavigationStateProvider navState = Get.find();
|
||||
|
||||
return Material(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
@ -69,13 +85,13 @@ class _FeedScreenState extends State<FeedScreen>
|
||||
title: AppBarTitle('feed'.tr),
|
||||
centerTitle: false,
|
||||
floating: true,
|
||||
toolbarHeight: SolianTheme.toolbarHeight(context),
|
||||
toolbarHeight: AppTheme.toolbarHeight(context),
|
||||
leading: AppBarLeadingButton.adaptive(context),
|
||||
actions: [
|
||||
const BackgroundStateWidget(),
|
||||
const NotificationButton(),
|
||||
SizedBox(
|
||||
width: SolianTheme.isLargeScreen(context) ? 8 : 16,
|
||||
width: AppTheme.isLargeScreen(context) ? 8 : 16,
|
||||
),
|
||||
],
|
||||
bottom: TabBar(
|
||||
@ -96,37 +112,53 @@ class _FeedScreenState extends State<FeedScreen>
|
||||
);
|
||||
}
|
||||
|
||||
return TabBarView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
controller: _tabController,
|
||||
return Column(
|
||||
children: [
|
||||
RefreshIndicator(
|
||||
onRefresh: () => _postController.reloadAllOver(),
|
||||
child: CustomScrollView(slivers: [
|
||||
PostWarpedListWidget(
|
||||
controller: _postController.pagingController,
|
||||
onUpdate: () => _postController.reloadAllOver(),
|
||||
if (navState.focusedRealm.value != null)
|
||||
MaterialBanner(
|
||||
leading: const Icon(Icons.layers),
|
||||
content: Text(
|
||||
'postBrowsingIn'.trParams({
|
||||
'region': '#${navState.focusedRealm.value!.alias}',
|
||||
}),
|
||||
),
|
||||
]),
|
||||
actions: const [SizedBox.shrink()],
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
controller: _tabController,
|
||||
children: [
|
||||
RefreshIndicator(
|
||||
onRefresh: () => _postController.reloadAllOver(),
|
||||
child: CustomScrollView(slivers: [
|
||||
PostWarpedListWidget(
|
||||
controller: _postController.pagingController,
|
||||
onUpdate: () => _postController.reloadAllOver(),
|
||||
),
|
||||
]),
|
||||
),
|
||||
Obx(() {
|
||||
if (auth.isAuthorized.value) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => _postController.reloadAllOver(),
|
||||
child: CustomScrollView(slivers: [
|
||||
PostWarpedListWidget(
|
||||
controller: _postController.pagingController,
|
||||
onUpdate: () => _postController.reloadAllOver(),
|
||||
),
|
||||
]),
|
||||
);
|
||||
} else {
|
||||
return SigninRequiredOverlay(
|
||||
onSignedIn: () => _postController.reloadAllOver(),
|
||||
);
|
||||
}
|
||||
}),
|
||||
PostShuffleSwiper(controller: _postController),
|
||||
],
|
||||
),
|
||||
),
|
||||
Obx(() {
|
||||
if (auth.isAuthorized.value) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => _postController.reloadAllOver(),
|
||||
child: CustomScrollView(slivers: [
|
||||
PostWarpedListWidget(
|
||||
controller: _postController.pagingController,
|
||||
onUpdate: () => _postController.reloadAllOver(),
|
||||
),
|
||||
]),
|
||||
);
|
||||
} else {
|
||||
return SigninRequiredOverlay(
|
||||
onSignedIn: () => _postController.reloadAllOver(),
|
||||
);
|
||||
}
|
||||
}),
|
||||
PostShuffleSwiper(controller: _postController),
|
||||
],
|
||||
);
|
||||
}),
|
||||
@ -138,6 +170,11 @@ class _FeedScreenState extends State<FeedScreen>
|
||||
@override
|
||||
void dispose() {
|
||||
_postController.dispose();
|
||||
if (_subscriptions != null) {
|
||||
for (final subscription in _subscriptions!) {
|
||||
subscription.cancel();
|
||||
}
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -61,10 +61,10 @@ class _DraftBoxScreenState extends State<DraftBoxScreen> {
|
||||
leading: AppBarLeadingButton.adaptive(context),
|
||||
title: AppBarTitle('draftBox'.tr),
|
||||
centerTitle: false,
|
||||
toolbarHeight: SolianTheme.toolbarHeight(context),
|
||||
toolbarHeight: AppTheme.toolbarHeight(context),
|
||||
actions: [
|
||||
SizedBox(
|
||||
width: SolianTheme.isLargeScreen(context) ? 8 : 16,
|
||||
width: AppTheme.isLargeScreen(context) ? 8 : 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -11,6 +11,7 @@ import 'package:solian/models/post.dart';
|
||||
import 'package:solian/models/realm.dart';
|
||||
import 'package:solian/providers/attachment_uploader.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:solian/providers/navigation.dart';
|
||||
import 'package:solian/router.dart';
|
||||
import 'package:solian/theme.dart';
|
||||
import 'package:solian/widgets/app_bar_leading.dart';
|
||||
@ -124,7 +125,12 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.edit == null && widget.reply == null && widget.repost == null) {
|
||||
_editorController.localRead();
|
||||
_editorController.localRead().then((res) {
|
||||
if (!res) {
|
||||
final navState = Get.find<NavigationStateProvider>();
|
||||
_editorController.realmZone.value = navState.focusedRealm.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (widget.reply != null) {
|
||||
_editorController.replyTo.value = widget.reply;
|
||||
@ -158,7 +164,7 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
|
||||
),
|
||||
),
|
||||
centerTitle: false,
|
||||
toolbarHeight: SolianTheme.toolbarHeight(context),
|
||||
toolbarHeight: AppTheme.toolbarHeight(context),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: _isBusy ? null : () => _applyPost(),
|
||||
@ -177,23 +183,19 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
|
||||
children: [
|
||||
ListTile(
|
||||
tileColor: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||
title: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
_editorController.title ?? 'title'.tr,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
title: Row(
|
||||
children: [
|
||||
Text(
|
||||
_editorController.title ?? 'title'.tr,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const Gap(6),
|
||||
if (_editorController.aliasController.text.isNotEmpty)
|
||||
Badge(
|
||||
label: Text('#${_editorController.aliasController.text}'),
|
||||
),
|
||||
const Gap(6),
|
||||
if (_editorController.aliasController.text.isNotEmpty)
|
||||
Badge(
|
||||
label:
|
||||
Text('#${_editorController.aliasController.text}'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: Text(
|
||||
_editorController.description ?? 'description'.tr,
|
||||
@ -365,12 +367,12 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
|
||||
],
|
||||
),
|
||||
),
|
||||
if (SolianTheme.isLargeScreen(context))
|
||||
if (AppTheme.isLargeScreen(context))
|
||||
const VerticalDivider(width: 0.3, thickness: 0.3)
|
||||
.paddingSymmetric(
|
||||
horizontal: 16,
|
||||
),
|
||||
if (SolianTheme.isLargeScreen(context))
|
||||
if (AppTheme.isLargeScreen(context))
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: MarkdownTextContent(
|
||||
|
@ -62,7 +62,7 @@ class _RealmListScreenState extends State<RealmListScreen> {
|
||||
leading: AppBarLeadingButton.adaptive(context),
|
||||
title: AppBarTitle('realm'.tr),
|
||||
centerTitle: true,
|
||||
toolbarHeight: SolianTheme.toolbarHeight(context),
|
||||
toolbarHeight: AppTheme.toolbarHeight(context),
|
||||
actions: [
|
||||
const BackgroundStateWidget(),
|
||||
const NotificationButton(),
|
||||
@ -77,7 +77,7 @@ class _RealmListScreenState extends State<RealmListScreen> {
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: SolianTheme.isLargeScreen(context) ? 8 : 16,
|
||||
width: AppTheme.isLargeScreen(context) ? 8 : 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -102,7 +102,7 @@ class _RealmOrganizeScreenState extends State<RealmOrganizeScreen> {
|
||||
leading: AppBarLeadingButton.adaptive(context),
|
||||
title: AppBarTitle('realmOrganizing'.tr),
|
||||
centerTitle: false,
|
||||
toolbarHeight: SolianTheme.toolbarHeight(context),
|
||||
toolbarHeight: AppTheme.toolbarHeight(context),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: _isBusy ? null : () => applyRealm(),
|
||||
|
@ -114,7 +114,7 @@ class _RealmViewScreenState extends State<RealmViewScreen> {
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: SolianTheme.isLargeScreen(context) ? 8 : 16,
|
||||
width: AppTheme.isLargeScreen(context) ? 8 : 16,
|
||||
),
|
||||
],
|
||||
bottom: const TabBar(
|
||||
|
@ -33,11 +33,11 @@ class _SettingScreenState extends State<SettingScreen> {
|
||||
tooltip: label,
|
||||
onPressed: () {
|
||||
context.read<ThemeSwitcher>().setTheme(
|
||||
SolianTheme.build(
|
||||
AppTheme.build(
|
||||
Brightness.light,
|
||||
seedColor: color,
|
||||
),
|
||||
SolianTheme.build(
|
||||
AppTheme.build(
|
||||
Brightness.dark,
|
||||
seedColor: color,
|
||||
),
|
||||
|
@ -25,7 +25,7 @@ class CenteredShell extends StatelessWidget {
|
||||
leading: AppBarLeadingButton.adaptive(context),
|
||||
title: AppBarTitle(state.topRoute?.name?.tr ?? 'page'.tr),
|
||||
centerTitle: false,
|
||||
toolbarHeight: SolianTheme.toolbarHeight(context),
|
||||
toolbarHeight: AppTheme.toolbarHeight(context),
|
||||
)
|
||||
: null,
|
||||
body: Center(
|
||||
|
@ -41,10 +41,10 @@ class RootShell extends StatelessWidget {
|
||||
|
||||
return Scaffold(
|
||||
key: rootScaffoldKey,
|
||||
drawer: SolianTheme.isLargeScreen(context)
|
||||
drawer: AppTheme.isLargeScreen(context)
|
||||
? null
|
||||
: AppNavigationDrawer(routeName: routeName),
|
||||
body: SolianTheme.isLargeScreen(context)
|
||||
body: AppTheme.isLargeScreen(context)
|
||||
? Row(
|
||||
children: [
|
||||
if (showNavigation) AppNavigationDrawer(routeName: routeName),
|
||||
|
@ -29,9 +29,9 @@ class SidebarShell extends StatelessWidget {
|
||||
flex: 2,
|
||||
child: child,
|
||||
),
|
||||
if (SolianTheme.isExtraLargeScreen(context))
|
||||
if (AppTheme.isExtraLargeScreen(context))
|
||||
const VerticalDivider(thickness: 0.3, width: 1),
|
||||
if (SolianTheme.isExtraLargeScreen(context))
|
||||
if (AppTheme.isExtraLargeScreen(context))
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: sidebar ?? const SidebarPlaceholder(),
|
||||
@ -47,10 +47,10 @@ class SidebarShell extends StatelessWidget {
|
||||
leading: AppBarLeadingButton.adaptive(context),
|
||||
title: AppBarTitle(state.topRoute?.name?.tr ?? 'page'.tr),
|
||||
centerTitle: false,
|
||||
toolbarHeight: SolianTheme.toolbarHeight(context),
|
||||
toolbarHeight: AppTheme.toolbarHeight(context),
|
||||
)
|
||||
: null,
|
||||
body: SolianTheme.isLargeScreen(context)
|
||||
body: AppTheme.isLargeScreen(context)
|
||||
? Row(
|
||||
children: sidebarFirst
|
||||
? buildContent(context).reversed.toList()
|
||||
|
@ -32,11 +32,11 @@ class TitleShell extends StatelessWidget {
|
||||
title ?? (state!.topRoute?.name?.tr ?? 'page'.tr),
|
||||
),
|
||||
centerTitle: isCenteredTitle,
|
||||
toolbarHeight: SolianTheme.toolbarHeight(context),
|
||||
toolbarHeight: AppTheme.toolbarHeight(context),
|
||||
actions: [
|
||||
const BackgroundStateWidget(),
|
||||
SizedBox(
|
||||
width: SolianTheme.isLargeScreen(context) ? 8 : 16,
|
||||
width: AppTheme.isLargeScreen(context) ? 8 : 16,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:solian/platform.dart';
|
||||
|
||||
abstract class SolianTheme {
|
||||
abstract class AppTheme {
|
||||
static bool isLargeScreen(BuildContext context) =>
|
||||
MediaQuery.of(context).size.width > 640;
|
||||
|
||||
@ -9,13 +9,13 @@ abstract class SolianTheme {
|
||||
MediaQuery.of(context).size.width > 720;
|
||||
|
||||
static bool isSpecializedMacOS(BuildContext context) =>
|
||||
PlatformInfo.isMacOS && !SolianTheme.isLargeScreen(context);
|
||||
PlatformInfo.isMacOS && !AppTheme.isLargeScreen(context);
|
||||
|
||||
static double? titleSpacing(BuildContext context) {
|
||||
if (SolianTheme.isSpecializedMacOS(context)) {
|
||||
if (AppTheme.isSpecializedMacOS(context)) {
|
||||
return 24;
|
||||
} else {
|
||||
return SolianTheme.isLargeScreen(context) ? null : 24;
|
||||
return AppTheme.isLargeScreen(context) ? null : 24;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ class AppBarTitle extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (SolianTheme.isSpecializedMacOS(context)) {
|
||||
if (AppTheme.isSpecializedMacOS(context)) {
|
||||
return Text(title);
|
||||
} else {
|
||||
return Text(title);
|
||||
|
@ -98,12 +98,12 @@ class ChannelCallIndicator extends StatelessWidget {
|
||||
child: Text('callJoin'.tr),
|
||||
);
|
||||
} else if (call.channel.value?.id == channel.id &&
|
||||
!SolianTheme.isLargeScreen(context)) {
|
||||
!AppTheme.isLargeScreen(context)) {
|
||||
return TextButton(
|
||||
onPressed: () => onJoin(),
|
||||
child: Text('callResume'.tr),
|
||||
);
|
||||
} else if (!SolianTheme.isLargeScreen(context)) {
|
||||
} else if (!AppTheme.isLargeScreen(context)) {
|
||||
return TextButton(
|
||||
onPressed: null,
|
||||
child: Text('callJoin'.tr),
|
||||
|
@ -11,6 +11,7 @@ class ChannelListWidget extends StatefulWidget {
|
||||
final List<Channel> channels;
|
||||
final int selfId;
|
||||
final bool isDense;
|
||||
final bool isCollapsed;
|
||||
final bool noCategory;
|
||||
final bool useReplace;
|
||||
final Function(Channel)? onSelected;
|
||||
@ -20,6 +21,7 @@ class ChannelListWidget extends StatefulWidget {
|
||||
required this.channels,
|
||||
required this.selfId,
|
||||
this.isDense = false,
|
||||
this.isCollapsed = false,
|
||||
this.noCategory = false,
|
||||
this.useReplace = false,
|
||||
this.onSelected,
|
||||
@ -130,13 +132,25 @@ class _ChannelListWidgetState extends State<ChannelListWidget> {
|
||||
final otherside =
|
||||
item.members!.where((e) => e.account.id != widget.selfId).first;
|
||||
|
||||
final avatar = AccountAvatar(
|
||||
content: otherside.account.avatar,
|
||||
radius: widget.isDense ? 12 : 20,
|
||||
bgColor: Theme.of(context).colorScheme.primary,
|
||||
feColor: Theme.of(context).colorScheme.onPrimary,
|
||||
);
|
||||
|
||||
if (widget.isCollapsed) {
|
||||
return Tooltip(
|
||||
message: otherside.account.nick,
|
||||
child: InkWell(
|
||||
child: avatar.paddingSymmetric(vertical: 12),
|
||||
onTap: () => _gotoChannel(item),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
leading: AccountAvatar(
|
||||
content: otherside.account.avatar,
|
||||
radius: widget.isDense ? 12 : 20,
|
||||
bgColor: Theme.of(context).colorScheme.primary,
|
||||
feColor: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
leading: avatar,
|
||||
contentPadding: padding,
|
||||
title: Text(otherside.account.nick),
|
||||
subtitle: !widget.isDense
|
||||
@ -145,24 +159,42 @@ class _ChannelListWidgetState extends State<ChannelListWidget> {
|
||||
onTap: () => _gotoChannel(item),
|
||||
);
|
||||
} else {
|
||||
final avatar = CircleAvatar(
|
||||
backgroundColor: item.realmId == null
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.transparent,
|
||||
radius: widget.isDense ? 12 : 20,
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.hashtag,
|
||||
color: item.realmId == null
|
||||
? Theme.of(context).colorScheme.onPrimary
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
size: widget.isDense ? 12 : 16,
|
||||
),
|
||||
);
|
||||
|
||||
if (widget.isCollapsed) {
|
||||
return Tooltip(
|
||||
message: item.name,
|
||||
child: InkWell(
|
||||
child: avatar.paddingSymmetric(vertical: 12),
|
||||
onTap: () => _gotoChannel(item),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
minTileHeight: widget.isDense ? 48 : null,
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: item.realmId == null
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.transparent,
|
||||
radius: widget.isDense ? 12 : 20,
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.hashtag,
|
||||
color: item.realmId == null
|
||||
? Theme.of(context).colorScheme.onPrimary
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
size: widget.isDense ? 12 : 16,
|
||||
),
|
||||
),
|
||||
leading: avatar,
|
||||
contentPadding: padding,
|
||||
title: Text(item.name),
|
||||
subtitle: !widget.isDense ? Text(item.description) : null,
|
||||
subtitle: !widget.isDense
|
||||
? Text(
|
||||
item.description,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)
|
||||
: null,
|
||||
onTap: () => _gotoChannel(item),
|
||||
);
|
||||
}
|
||||
|
@ -75,9 +75,6 @@ class ChatEvent extends StatelessWidget {
|
||||
key: Key('m${item.uuid}attachments-box'),
|
||||
width: MediaQuery.of(context).size.width,
|
||||
padding: EdgeInsets.only(top: isMerged ? 0 : 4, bottom: 4),
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: 720,
|
||||
),
|
||||
child: AttachmentList(
|
||||
key: Key('m${item.uuid}attachments'),
|
||||
parentId: item.uuid,
|
||||
@ -301,7 +298,10 @@ class ChatEvent extends StatelessWidget {
|
||||
],
|
||||
).paddingSymmetric(horizontal: 12),
|
||||
_buildLinkExpansion().paddingOnly(left: 52, right: 8),
|
||||
_buildAttachment(context).paddingOnly(left: 56, right: 8),
|
||||
_buildAttachment(
|
||||
context,
|
||||
isMinimal: ['messages.edit'].contains(item.type),
|
||||
).paddingOnly(left: 56, right: 8),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -245,7 +245,8 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
|
||||
_editTo = widget.edit!;
|
||||
_textController.text = body.text;
|
||||
_attachments.addAll(
|
||||
widget.edit!.body['attachments']?.cast<int>() ?? List.empty());
|
||||
widget.edit!.body['attachments']?.cast<String>() ?? List.empty(),
|
||||
);
|
||||
}
|
||||
if (widget.reply != null) {
|
||||
_replyTo = widget.reply!;
|
||||
|
@ -192,9 +192,9 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer>
|
||||
}
|
||||
|
||||
void _autoResize() {
|
||||
if (SolianTheme.isExtraLargeScreen(context)) {
|
||||
if (AppTheme.isExtraLargeScreen(context)) {
|
||||
_expandDrawer();
|
||||
} else if (SolianTheme.isLargeScreen(context)) {
|
||||
} else if (AppTheme.isLargeScreen(context)) {
|
||||
_collapseDrawer();
|
||||
} else {
|
||||
_drawerAnimationController.value = 1;
|
||||
@ -229,7 +229,7 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer>
|
||||
return Drawer(
|
||||
width: _drawerAnimation.value,
|
||||
backgroundColor:
|
||||
SolianTheme.isLargeScreen(context) ? Colors.transparent : null,
|
||||
AppTheme.isLargeScreen(context) ? Colors.transparent : null,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
@ -239,45 +239,38 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer>
|
||||
children: [
|
||||
_buildUserInfo().paddingSymmetric(vertical: 8),
|
||||
const Divider(thickness: 0.3, height: 1),
|
||||
Column(
|
||||
children: AppNavigation.destinations
|
||||
.map(
|
||||
(e) => _isCollapsed
|
||||
? Tooltip(
|
||||
message: e.label,
|
||||
child: InkWell(
|
||||
child: Icon(e.icon, size: 20).paddingSymmetric(
|
||||
horizontal: 28,
|
||||
vertical: 16,
|
||||
),
|
||||
onTap: () {
|
||||
AppRouter.instance.goNamed(e.page);
|
||||
_closeDrawer();
|
||||
},
|
||||
),
|
||||
)
|
||||
: ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
),
|
||||
leading: Icon(e.icon, size: 20).paddingAll(2),
|
||||
title: !_isCollapsed ? Text(e.label) : null,
|
||||
enabled: true,
|
||||
onTap: () {
|
||||
AppRouter.instance.goNamed(e.page);
|
||||
_closeDrawer();
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: Wrap(
|
||||
runSpacing: 8,
|
||||
spacing: 8,
|
||||
alignment: WrapAlignment.spaceAround,
|
||||
children: AppNavigation.destinations
|
||||
.map(
|
||||
(e) => Tooltip(
|
||||
message: e.label,
|
||||
child: InkWell(
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(8)),
|
||||
child: Icon(
|
||||
e.icon,
|
||||
size: 22,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
).paddingAll(16),
|
||||
onTap: () {
|
||||
AppRouter.instance.goNamed(e.page);
|
||||
_closeDrawer();
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
).paddingSymmetric(vertical: 8, horizontal: 12),
|
||||
),
|
||||
const Divider(thickness: 0.3, height: 1),
|
||||
Expanded(
|
||||
child: AppNavigationRegion(
|
||||
isCollapsed: _isCollapsed,
|
||||
onSelected: (item) {
|
||||
_closeDrawer();
|
||||
},
|
||||
),
|
||||
),
|
||||
const Divider(thickness: 0.3, height: 1),
|
||||
|
@ -1,48 +1,110 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:solian/models/channel.dart';
|
||||
import 'package:solian/models/realm.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:solian/providers/content/channel.dart';
|
||||
import 'package:solian/router.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:solian/providers/content/realm.dart';
|
||||
import 'package:solian/providers/navigation.dart';
|
||||
import 'package:solian/widgets/account/account_avatar.dart';
|
||||
import 'package:solian/widgets/channel/channel_list.dart';
|
||||
|
||||
class AppNavigationRegion extends StatelessWidget {
|
||||
class AppNavigationRegion extends StatefulWidget {
|
||||
final bool isCollapsed;
|
||||
final Function(Channel item) onSelected;
|
||||
|
||||
const AppNavigationRegion({
|
||||
super.key,
|
||||
required this.onSelected,
|
||||
this.isCollapsed = false,
|
||||
});
|
||||
|
||||
void _gotoChannel(Channel item) {
|
||||
AppRouter.instance.goNamed(
|
||||
'channelChat',
|
||||
pathParameters: {'alias': item.alias},
|
||||
queryParameters: {
|
||||
if (item.realmId != null) 'realm': item.realm!.alias,
|
||||
},
|
||||
);
|
||||
@override
|
||||
State<AppNavigationRegion> createState() => _AppNavigationRegionState();
|
||||
}
|
||||
|
||||
onSelected(item);
|
||||
class _AppNavigationRegionState extends State<AppNavigationRegion> {
|
||||
bool _isTryingExit = false;
|
||||
|
||||
void _focusRealm(Realm item) {
|
||||
setState(
|
||||
() => Get.find<NavigationStateProvider>().focusedRealm.value = item,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEntry(BuildContext context, Channel item) {
|
||||
const padding = EdgeInsets.symmetric(horizontal: 20);
|
||||
void _unFocusRealm() {
|
||||
setState(
|
||||
() => Get.find<NavigationStateProvider>().focusedRealm.value = null,
|
||||
);
|
||||
}
|
||||
|
||||
if (isCollapsed) {
|
||||
return InkWell(
|
||||
child: const Icon(Icons.tag_outlined, size: 20).paddingSymmetric(
|
||||
horizontal: 20,
|
||||
vertical: 16,
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Widget _buildRealmFocusAvatar() {
|
||||
final focusedRealm = Get.find<NavigationStateProvider>().focusedRealm.value;
|
||||
return GestureDetector(
|
||||
child: MouseRegion(
|
||||
child: AnimatedSwitcher(
|
||||
switchInCurve: Curves.fastOutSlowIn,
|
||||
switchOutCurve: Curves.fastOutSlowIn,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
transitionBuilder: (child, animation) {
|
||||
return ScaleTransition(
|
||||
scale: animation,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: _isTryingExit
|
||||
? CircleAvatar(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
child: const Icon(
|
||||
Icons.arrow_back,
|
||||
color: Colors.white,
|
||||
size: 16,
|
||||
),
|
||||
).paddingSymmetric(
|
||||
vertical: 8,
|
||||
)
|
||||
: _buildEntryAvatar(focusedRealm!),
|
||||
),
|
||||
onTap: () => _gotoChannel(item),
|
||||
onEnter: (_) => setState(() => _isTryingExit = true),
|
||||
onExit: (_) => setState(() => _isTryingExit = false),
|
||||
),
|
||||
onTap: () => _unFocusRealm(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEntryAvatar(Realm item) {
|
||||
return Hero(
|
||||
tag: Key('region-realm-avatar-${item.id}'),
|
||||
child: (item.avatar?.isNotEmpty ?? false)
|
||||
? AccountAvatar(content: item.avatar)
|
||||
: CircleAvatar(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
child: const Icon(
|
||||
Icons.workspaces,
|
||||
color: Colors.white,
|
||||
size: 16,
|
||||
),
|
||||
).paddingSymmetric(
|
||||
vertical: 8,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEntry(BuildContext context, Realm item) {
|
||||
const padding = EdgeInsets.symmetric(horizontal: 20, vertical: 8);
|
||||
|
||||
if (widget.isCollapsed) {
|
||||
return InkWell(
|
||||
child: _buildEntryAvatar(item).paddingSymmetric(vertical: 8),
|
||||
onTap: () => _focusRealm(item),
|
||||
);
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
minTileHeight: 0,
|
||||
leading: const Icon(Icons.tag_outlined),
|
||||
leading: _buildEntryAvatar(item),
|
||||
contentPadding: padding,
|
||||
title: Text(item.name),
|
||||
subtitle: Text(
|
||||
@ -50,76 +112,104 @@ class AppNavigationRegion extends StatelessWidget {
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
onTap: () => _gotoChannel(item),
|
||||
onTap: () => _focusRealm(item),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final RealmProvider realms = Get.find();
|
||||
final ChannelProvider channels = Get.find();
|
||||
final AuthProvider auth = Get.find();
|
||||
final NavigationStateProvider navState = Get.find();
|
||||
|
||||
return Obx(() {
|
||||
final List<Channel> noRealmGroupChannels = channels.availableChannels
|
||||
.where((x) => x.type == 0 && x.realmId == null)
|
||||
.toList();
|
||||
final List<Channel> hasRealmGroupChannels = channels.availableChannels
|
||||
.where((x) => x.type == 0 && x.realmId != null)
|
||||
.toList();
|
||||
|
||||
if (isCollapsed) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
const SliverPadding(padding: EdgeInsets.only(top: 8)),
|
||||
SliverList.builder(
|
||||
itemCount:
|
||||
noRealmGroupChannels.length + hasRealmGroupChannels.length,
|
||||
itemBuilder: (context, index) {
|
||||
final element = index >= noRealmGroupChannels.length
|
||||
? hasRealmGroupChannels[index - noRealmGroupChannels.length]
|
||||
: noRealmGroupChannels[index];
|
||||
return Tooltip(
|
||||
message: element.name,
|
||||
child: _buildEntry(context, element),
|
||||
);
|
||||
},
|
||||
return Obx(
|
||||
() => AnimatedSwitcher(
|
||||
switchInCurve: Curves.fastOutSlowIn,
|
||||
switchOutCurve: Curves.fastOutSlowIn,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
transitionBuilder: (child, animation) {
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(1.0, 0.0),
|
||||
end: Offset.zero,
|
||||
).animate(animation),
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: child,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
const SliverPadding(padding: EdgeInsets.only(top: 8)),
|
||||
SliverList.builder(
|
||||
itemCount: noRealmGroupChannels.length,
|
||||
itemBuilder: (context, index) {
|
||||
final element = noRealmGroupChannels[index];
|
||||
return _buildEntry(context, element);
|
||||
},
|
||||
),
|
||||
SliverList.list(
|
||||
children: hasRealmGroupChannels
|
||||
.groupListsBy((x) => x.realm)
|
||||
.entries
|
||||
.map((element) {
|
||||
return ExpansionTile(
|
||||
minTileHeight: 0,
|
||||
initiallyExpanded: true,
|
||||
tilePadding: const EdgeInsets.only(left: 20, right: 24),
|
||||
backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
|
||||
collapsedBackgroundColor:
|
||||
Theme.of(context).colorScheme.surfaceContainer,
|
||||
title: Text(element.value.first.realm!.name),
|
||||
leading: const Icon(Icons.workspaces, size: 16)
|
||||
.paddingSymmetric(horizontal: 4),
|
||||
children:
|
||||
element.value.map((x) => _buildEntry(context, x)).toList(),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
const SliverPadding(padding: EdgeInsets.only(bottom: 8)),
|
||||
],
|
||||
);
|
||||
});
|
||||
);
|
||||
},
|
||||
child: navState.focusedRealm.value == null
|
||||
? widget.isCollapsed
|
||||
? CustomScrollView(
|
||||
slivers: [
|
||||
const SliverPadding(padding: EdgeInsets.only(top: 16)),
|
||||
SliverList.builder(
|
||||
itemCount: realms.availableRealms.length,
|
||||
itemBuilder: (context, index) {
|
||||
final element = realms.availableRealms[index];
|
||||
return Tooltip(
|
||||
message: element.name,
|
||||
child: _buildEntry(context, element),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
: CustomScrollView(
|
||||
slivers: [
|
||||
SliverList.builder(
|
||||
itemCount: realms.availableRealms.length,
|
||||
itemBuilder: (context, index) {
|
||||
final element = realms.availableRealms[index];
|
||||
return _buildEntry(context, element);
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
: Column(
|
||||
children: [
|
||||
if (widget.isCollapsed)
|
||||
Tooltip(
|
||||
message: navState.focusedRealm.value!.name,
|
||||
child: _buildRealmFocusAvatar().paddingOnly(
|
||||
top: 24,
|
||||
bottom: 8,
|
||||
),
|
||||
)
|
||||
else
|
||||
ListTile(
|
||||
minTileHeight: 0,
|
||||
tileColor:
|
||||
Theme.of(context).colorScheme.surfaceContainerLow,
|
||||
leading: _buildRealmFocusAvatar(),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 20, vertical: 8),
|
||||
title: Text(navState.focusedRealm.value!.name),
|
||||
subtitle: Text(
|
||||
navState.focusedRealm.value!.description,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Obx(
|
||||
() => ChannelListWidget(
|
||||
useReplace: true,
|
||||
channels: channels.availableChannels
|
||||
.where((x) =>
|
||||
x.realm?.id == navState.focusedRealm.value?.id)
|
||||
.toList(),
|
||||
isCollapsed: widget.isCollapsed,
|
||||
selfId: auth.userProfile.value!['id'],
|
||||
noCategory: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import 'package:intl/intl.dart';
|
||||
import 'package:solian/models/post.dart';
|
||||
import 'package:solian/screens/posts/post_detail.dart';
|
||||
import 'package:solian/shells/title_shell.dart';
|
||||
import 'package:solian/theme.dart';
|
||||
import 'package:solian/widgets/account/account_avatar.dart';
|
||||
import 'package:solian/widgets/account/account_profile_popup.dart';
|
||||
import 'package:solian/widgets/attachments/attachment_list.dart';
|
||||
@ -302,7 +303,7 @@ class _PostItemState extends State<PostItem> {
|
||||
autoload: false,
|
||||
isGrid: true,
|
||||
).paddingOnly(left: 36, top: 4, bottom: 4);
|
||||
} else if (attachments.length > 1) {
|
||||
} else if (attachments.length > 1 || AppTheme.isLargeScreen(context)) {
|
||||
return AttachmentList(
|
||||
parentId: widget.item.id.toString(),
|
||||
attachmentsId: attachments,
|
||||
@ -497,7 +498,10 @@ class _PostItemState extends State<PostItem> {
|
||||
],
|
||||
).paddingOnly(
|
||||
top: 10,
|
||||
bottom: attachments.length == 1 ? 10 : 0,
|
||||
bottom:
|
||||
(attachments.length == 1 && !AppTheme.isLargeScreen(context))
|
||||
? 10
|
||||
: 0,
|
||||
right: 16,
|
||||
left: 16,
|
||||
),
|
||||
@ -514,8 +518,13 @@ class _PostItemState extends State<PostItem> {
|
||||
});
|
||||
},
|
||||
).paddingOnly(
|
||||
top: attachments.length == 1 ? 10 : 6,
|
||||
left: attachments.length == 1 ? 24 : 60,
|
||||
top: (attachments.length == 1 && !AppTheme.isLargeScreen(context))
|
||||
? 10
|
||||
: 6,
|
||||
left:
|
||||
(attachments.length == 1 && !AppTheme.isLargeScreen(context))
|
||||
? 24
|
||||
: 60,
|
||||
right: 16,
|
||||
bottom: 10,
|
||||
)
|
||||
|
@ -2,7 +2,7 @@ name: solian
|
||||
description: "The Solar Network App"
|
||||
publish_to: "none"
|
||||
|
||||
version: 1.2.1+35
|
||||
version: 1.2.1+36
|
||||
|
||||
environment:
|
||||
sdk: ">=3.3.4 <4.0.0"
|
||||
|
@ -35,7 +35,6 @@
|
||||
<link rel="manifest" href="manifest.json">
|
||||
|
||||
|
||||
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
|
||||
<style id="splash-screen-style">
|
||||
html {
|
||||
height: 100%
|
||||
|
Reference in New Issue
Block a user