Compare commits

...

4 Commits

Author SHA1 Message Date
2c4040096f 🚀 Launch 1.2.1+36 2024-09-13 20:22:33 +08:00
b449735bf5 Better side navigation
🐛 Bug fixes and optimizations
2024-09-13 20:22:10 +08:00
dd01f964d4 Focused realm linked with feed stream 2024-09-13 00:17:56 +08:00
6daa04c208 Brand new app navigation region 2024-09-12 23:55:31 +08:00
39 changed files with 472 additions and 271 deletions

View File

@ -391,5 +391,6 @@
"userLevel10": "Grandmaster", "userLevel10": "Grandmaster",
"userLevel11": "Legend", "userLevel11": "Legend",
"userLevel12": "Mythic", "userLevel12": "Mythic",
"userLevel13": "Immortal" "userLevel13": "Immortal",
"postBrowsingIn": "Browsing in @region"
} }

View File

@ -392,5 +392,6 @@
"userLevel10": "出神入化", "userLevel10": "出神入化",
"userLevel11": "名垂千古", "userLevel11": "名垂千古",
"userLevel12": "独占鳌头", "userLevel12": "独占鳌头",
"userLevel13": "万古流芳" "userLevel13": "万古流芳",
"postBrowsingIn": "浏览 @region 内的帖子中"
} }

View File

@ -155,13 +155,14 @@ class PostEditorController extends GetxController {
); );
} }
void localRead() { Future<bool> localRead() async {
SharedPreferences.getInstance().then((inst) { final inst = await SharedPreferences.getInstance();
if (inst.containsKey('post_editor_local_save')) { if (inst.containsKey('post_editor_local_save')) {
isRestoreFromLocal.value = true; isRestoreFromLocal.value = true;
payload = jsonDecode(inst.getString('post_editor_local_save')!); payload = jsonDecode(inst.getString('post_editor_local_save')!);
return true;
} }
}); return false;
} }
void localClear() { void localClear() {

View File

@ -9,6 +9,7 @@ import 'package:solian/providers/last_read.dart';
class PostListController extends GetxController { class PostListController extends GetxController {
String? author; String? author;
String? realm;
/// The polling source modifier. /// The polling source modifier.
/// - `0`: default recommendations /// - `0`: default recommendations
@ -99,8 +100,10 @@ class PostListController extends GetxController {
final idx = <dynamic>{}; final idx = <dynamic>{};
postList.retainWhere((x) => idx.add(x.id)); postList.retainWhere((x) => idx.add(x.id));
if (postList.isNotEmpty) {
var lastId = postList.map((x) => x.id).reduce(max); var lastId = postList.map((x) => x.id).reduce(max);
Get.find<LastReadProvider>().feedLastReadAt = lastId; Get.find<LastReadProvider>().feedLastReadAt = lastId;
}
return result; return result;
} }
@ -123,16 +126,21 @@ class PostListController extends GetxController {
resp = await provider.listRecommendations( resp = await provider.listRecommendations(
pageKey, pageKey,
channel: 'shuffle', channel: 'shuffle',
realm: realm,
); );
break; break;
case 1: case 1:
resp = await provider.listRecommendations( resp = await provider.listRecommendations(
pageKey, pageKey,
channel: 'friends', channel: 'friends',
realm: realm,
); );
break; break;
default: default:
resp = await provider.listRecommendations(pageKey); resp = await provider.listRecommendations(
pageKey,
realm: realm,
);
break; break;
} }
} }

View File

@ -15,6 +15,7 @@ import 'package:solian/providers/attachment_uploader.dart';
import 'package:solian/providers/daily_sign.dart'; import 'package:solian/providers/daily_sign.dart';
import 'package:solian/providers/last_read.dart'; import 'package:solian/providers/last_read.dart';
import 'package:solian/providers/link_expander.dart'; import 'package:solian/providers/link_expander.dart';
import 'package:solian/providers/navigation.dart';
import 'package:solian/providers/stickers.dart'; import 'package:solian/providers/stickers.dart';
import 'package:solian/providers/theme_switcher.dart'; import 'package:solian/providers/theme_switcher.dart';
import 'package:solian/providers/websocket.dart'; import 'package:solian/providers/websocket.dart';
@ -79,8 +80,8 @@ Future<void> _initializePlatformComponents() async {
} }
final themeSwitcher = ThemeSwitcher( final themeSwitcher = ThemeSwitcher(
lightThemeData: SolianTheme.build(Brightness.light), lightThemeData: AppTheme.build(Brightness.light),
darkThemeData: SolianTheme.build(Brightness.dark), darkThemeData: AppTheme.build(Brightness.dark),
); );
class SolianApp extends StatelessWidget { class SolianApp extends StatelessWidget {
@ -123,6 +124,8 @@ class SolianApp extends StatelessWidget {
} }
void _initializeProviders(BuildContext context) async { void _initializeProviders(BuildContext context) async {
Get.put(NavigationStateProvider());
Get.lazyPut(() => AuthProvider()); Get.lazyPut(() => AuthProvider());
Get.lazyPut(() => RelationshipProvider()); Get.lazyPut(() => RelationshipProvider());
Get.lazyPut(() => PostProvider()); Get.lazyPut(() => PostProvider());

View File

@ -12,9 +12,12 @@ class Realm {
String alias; String alias;
String name; String name;
String description; String description;
String? avatar;
String? banner;
bool isPublic; bool isPublic;
bool isCommunity; bool isCommunity;
int? accountId; int? accountId;
int? externalId;
Realm({ Realm({
required this.id, required this.id,
@ -24,9 +27,12 @@ class Realm {
required this.alias, required this.alias,
required this.name, required this.name,
required this.description, required this.description,
required this.avatar,
required this.banner,
required this.isPublic, required this.isPublic,
required this.isCommunity, required this.isCommunity,
this.accountId, this.accountId,
this.externalId,
}); });
factory Realm.fromJson(Map<String, dynamic> json) => _$RealmFromJson(json); factory Realm.fromJson(Map<String, dynamic> json) => _$RealmFromJson(json);

View File

@ -16,9 +16,12 @@ Realm _$RealmFromJson(Map<String, dynamic> json) => Realm(
alias: json['alias'] as String, alias: json['alias'] as String,
name: json['name'] as String, name: json['name'] as String,
description: json['description'] as String, description: json['description'] as String,
avatar: json['avatar'] as String?,
banner: json['banner'] as String?,
isPublic: json['is_public'] as bool, isPublic: json['is_public'] as bool,
isCommunity: json['is_community'] as bool, isCommunity: json['is_community'] as bool,
accountId: (json['account_id'] as num?)?.toInt(), accountId: (json['account_id'] as num?)?.toInt(),
externalId: (json['external_id'] as num?)?.toInt(),
); );
Map<String, dynamic> _$RealmToJson(Realm instance) => <String, dynamic>{ Map<String, dynamic> _$RealmToJson(Realm instance) => <String, dynamic>{
@ -29,9 +32,12 @@ Map<String, dynamic> _$RealmToJson(Realm instance) => <String, dynamic>{
'alias': instance.alias, 'alias': instance.alias,
'name': instance.name, 'name': instance.name,
'description': instance.description, 'description': instance.description,
'avatar': instance.avatar,
'banner': instance.banner,
'is_public': instance.isPublic, 'is_public': instance.isPublic,
'is_community': instance.isCommunity, 'is_community': instance.isCommunity,
'account_id': instance.accountId, 'account_id': instance.accountId,
'external_id': instance.externalId,
}; };
RealmMember _$RealmMemberFromJson(Map<String, dynamic> json) => RealmMember( RealmMember _$RealmMemberFromJson(Map<String, dynamic> json) => RealmMember(

View 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);
}

View File

@ -16,8 +16,8 @@ class ThemeSwitcher extends ChangeNotifier {
if (prefs.containsKey('global_theme_color')) { if (prefs.containsKey('global_theme_color')) {
final value = prefs.getInt('global_theme_color')!; final value = prefs.getInt('global_theme_color')!;
final color = Color(value); final color = Color(value);
lightThemeData = SolianTheme.build(Brightness.light, seedColor: color); lightThemeData = AppTheme.build(Brightness.light, seedColor: color);
darkThemeData = SolianTheme.build(Brightness.dark, seedColor: color); darkThemeData = AppTheme.build(Brightness.dark, seedColor: color);
notifyListeners(); notifyListeners();
} }
} }

View File

@ -154,6 +154,7 @@ abstract class AppRouter {
name: 'channelChat', name: 'channelChat',
builder: (context, state) { builder: (context, state) {
return ChannelChatScreen( return ChannelChatScreen(
key: UniqueKey(),
alias: state.pathParameters['alias']!, alias: state.pathParameters['alias']!,
realm: state.uri.queryParameters['realm'] ?? 'global', realm: state.uri.queryParameters['realm'] ?? 'global',
); );

View File

@ -133,7 +133,7 @@ class _FriendScreenState extends State<FriendScreen>
).paddingAll(14), ).paddingAll(14),
), ),
SizedBox( SizedBox(
width: SolianTheme.isLargeScreen(context) ? 8 : 16, width: AppTheme.isLargeScreen(context) ? 8 : 16,
), ),
], ],
bottom: TabBar( bottom: TabBar(

View File

@ -152,7 +152,7 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
SliverAppBar( SliverAppBar(
centerTitle: false, centerTitle: false,
floating: true, floating: true,
toolbarHeight: SolianTheme.toolbarHeight(context), toolbarHeight: AppTheme.toolbarHeight(context),
leadingWidth: 24, leadingWidth: 24,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
flexibleSpace: Row( flexibleSpace: Row(
@ -207,7 +207,7 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
onPressed: null, onPressed: null,
), ),
SizedBox( SizedBox(
width: SolianTheme.isLargeScreen(context) ? 8 : 16, width: AppTheme.isLargeScreen(context) ? 8 : 16,
), ),
], ],
), ),

View File

@ -205,7 +205,7 @@ class _CallScreenState extends State<CallScreen> with TickerProviderStateMixin {
: AppBar( : AppBar(
leading: AppBarLeadingButton.adaptive(context), leading: AppBarLeadingButton.adaptive(context),
centerTitle: true, centerTitle: true,
toolbarHeight: SolianTheme.toolbarHeight(context), toolbarHeight: AppTheme.toolbarHeight(context),
title: Obx( title: Obx(
() => RichText( () => RichText(
textAlign: TextAlign.center, textAlign: TextAlign.center,

View File

@ -217,8 +217,8 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
leading: AppBarLeadingButton.adaptive(context), leading: AppBarLeadingButton.adaptive(context),
title: AppBarTitle(title), title: AppBarTitle(title),
centerTitle: false, centerTitle: false,
titleSpacing: SolianTheme.titleSpacing(context), titleSpacing: AppTheme.titleSpacing(context),
toolbarHeight: SolianTheme.toolbarHeight(context), toolbarHeight: AppTheme.toolbarHeight(context),
actions: [ actions: [
const BackgroundStateWidget(), const BackgroundStateWidget(),
Builder(builder: (context) { Builder(builder: (context) {
@ -255,7 +255,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
}, },
), ),
SizedBox( SizedBox(
width: SolianTheme.isLargeScreen(context) ? 8 : 16, width: AppTheme.isLargeScreen(context) ? 8 : 16,
), ),
], ],
), ),
@ -276,7 +276,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
channel: _channel!, channel: _channel!,
ongoingCall: _ongoingCall!, ongoingCall: _ongoingCall!,
onJoin: () { onJoin: () {
if (!SolianTheme.isLargeScreen(context)) { if (!AppTheme.isLargeScreen(context)) {
final ChatCallProvider call = Get.find(); final ChatCallProvider call = Get.find();
call.gotoScreen(context); call.gotoScreen(context);
} }
@ -337,7 +337,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
), ),
Obx(() { Obx(() {
final ChatCallProvider call = Get.find(); final ChatCallProvider call = Get.find();
if (call.isMounted.value && SolianTheme.isLargeScreen(context)) { if (call.isMounted.value && AppTheme.isLargeScreen(context)) {
return const Expanded( return const Expanded(
child: Row(children: [ child: Row(children: [
VerticalDivider(width: 0.3, thickness: 0.3), VerticalDivider(width: 0.3, thickness: 0.3),

View File

@ -110,7 +110,7 @@ class _ChannelOrganizeScreenState extends State<ChannelOrganizeScreen> {
appBar: AppBar( appBar: AppBar(
title: AppBarTitle('channelOrganizing'.tr), title: AppBarTitle('channelOrganizing'.tr),
centerTitle: false, centerTitle: false,
toolbarHeight: SolianTheme.toolbarHeight(context), toolbarHeight: AppTheme.toolbarHeight(context),
actions: [ actions: [
TextButton( TextButton(
onPressed: _isBusy ? null : () => applyChannel(), onPressed: _isBusy ? null : () => applyChannel(),

View File

@ -47,7 +47,7 @@ class _ChatScreenState extends State<ChatScreen> {
leading: AppBarLeadingButton.adaptive(context), leading: AppBarLeadingButton.adaptive(context),
title: AppBarTitle('chat'.tr), title: AppBarTitle('chat'.tr),
centerTitle: true, centerTitle: true,
toolbarHeight: SolianTheme.toolbarHeight(context), toolbarHeight: AppTheme.toolbarHeight(context),
actions: [ actions: [
const BackgroundStateWidget(), const BackgroundStateWidget(),
const NotificationButton(), const NotificationButton(),
@ -95,7 +95,7 @@ class _ChatScreenState extends State<ChatScreen> {
], ],
), ),
SizedBox( SizedBox(
width: SolianTheme.isLargeScreen(context) ? 8 : 16, width: AppTheme.isLargeScreen(context) ? 8 : 16,
), ),
], ],
), ),

View File

@ -315,7 +315,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
Card( Card(
child: ListTile( child: ListTile(
contentPadding: contentPadding:
const EdgeInsets.symmetric(horizontal: 24), const EdgeInsets.only(left: 24, right: 32),
trailing: const Icon(Icons.inbox_outlined), trailing: const Icon(Icons.inbox_outlined),
title: Text('notifyEmpty'.tr), title: Text('notifyEmpty'.tr),
subtitle: Text('notifyEmptyCaption'.tr), subtitle: Text('notifyEmptyCaption'.tr),
@ -368,6 +368,10 @@ class _DashboardScreenState extends State<DashboardScreen> {
return SizedBox( return SizedBox(
width: min(480, width), width: min(480, width),
child: Card( child: Card(
child: ClipRRect(
borderRadius: const BorderRadius.all(
Radius.circular(8),
),
child: SingleChildScrollView( child: SingleChildScrollView(
child: PostListEntryWidget( child: PostListEntryWidget(
item: item, item: item,
@ -382,6 +386,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
.surfaceContainerLow, .surfaceContainerLow,
), ),
), ),
),
).paddingSymmetric(horizontal: 8), ).paddingSymmetric(horizontal: 8),
); );
}, },
@ -499,7 +504,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
/// Footer /// Footer
Column( Column(
mainAxisAlignment: SolianTheme.isLargeScreen(context) mainAxisAlignment: AppTheme.isLargeScreen(context)
? MainAxisAlignment.start ? MainAxisAlignment.start
: MainAxisAlignment.center, : MainAxisAlignment.center,
children: [ children: [

View File

@ -1,8 +1,11 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:solian/controllers/post_list_controller.dart'; import 'package:solian/controllers/post_list_controller.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/providers/navigation.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:solian/screens/account/notification.dart'; import 'package:solian/screens/account/notification.dart';
import 'package:solian/theme.dart'; import 'package:solian/theme.dart';
@ -25,21 +28,34 @@ class _FeedScreenState extends State<FeedScreen>
late final PostListController _postController; late final PostListController _postController;
late final TabController _tabController; late final TabController _tabController;
List<StreamSubscription>? _subscriptions;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
final navState = Get.find<NavigationStateProvider>();
_postController = PostListController(); _postController = PostListController();
_postController.realm = navState.focusedRealm.value?.alias;
_tabController = TabController(length: 3, vsync: this); _tabController = TabController(length: 3, vsync: this);
_tabController.addListener(() { _tabController.addListener(() {
if (_postController.mode.value == _tabController.index) return; if (_postController.mode.value == _tabController.index) return;
_postController.mode.value = _tabController.index; _postController.mode.value = _tabController.index;
_postController.reloadAllOver(); _postController.reloadAllOver();
}); });
_subscriptions = [
Get.find<NavigationStateProvider>().focusedRealm.listen((value) {
if (value?.alias != _postController.realm) {
_postController.realm = value?.alias;
_postController.reloadAllOver();
}
}),
];
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
final NavigationStateProvider navState = Get.find();
return Material( return Material(
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
@ -69,13 +85,13 @@ class _FeedScreenState extends State<FeedScreen>
title: AppBarTitle('feed'.tr), title: AppBarTitle('feed'.tr),
centerTitle: false, centerTitle: false,
floating: true, floating: true,
toolbarHeight: SolianTheme.toolbarHeight(context), toolbarHeight: AppTheme.toolbarHeight(context),
leading: AppBarLeadingButton.adaptive(context), leading: AppBarLeadingButton.adaptive(context),
actions: [ actions: [
const BackgroundStateWidget(), const BackgroundStateWidget(),
const NotificationButton(), const NotificationButton(),
SizedBox( SizedBox(
width: SolianTheme.isLargeScreen(context) ? 8 : 16, width: AppTheme.isLargeScreen(context) ? 8 : 16,
), ),
], ],
bottom: TabBar( bottom: TabBar(
@ -96,7 +112,20 @@ class _FeedScreenState extends State<FeedScreen>
); );
} }
return TabBarView( return Column(
children: [
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(), physics: const NeverScrollableScrollPhysics(),
controller: _tabController, controller: _tabController,
children: [ children: [
@ -128,6 +157,9 @@ class _FeedScreenState extends State<FeedScreen>
}), }),
PostShuffleSwiper(controller: _postController), PostShuffleSwiper(controller: _postController),
], ],
),
),
],
); );
}), }),
), ),
@ -138,6 +170,11 @@ class _FeedScreenState extends State<FeedScreen>
@override @override
void dispose() { void dispose() {
_postController.dispose(); _postController.dispose();
if (_subscriptions != null) {
for (final subscription in _subscriptions!) {
subscription.cancel();
}
}
super.dispose(); super.dispose();
} }
} }

View File

@ -61,10 +61,10 @@ class _DraftBoxScreenState extends State<DraftBoxScreen> {
leading: AppBarLeadingButton.adaptive(context), leading: AppBarLeadingButton.adaptive(context),
title: AppBarTitle('draftBox'.tr), title: AppBarTitle('draftBox'.tr),
centerTitle: false, centerTitle: false,
toolbarHeight: SolianTheme.toolbarHeight(context), toolbarHeight: AppTheme.toolbarHeight(context),
actions: [ actions: [
SizedBox( SizedBox(
width: SolianTheme.isLargeScreen(context) ? 8 : 16, width: AppTheme.isLargeScreen(context) ? 8 : 16,
), ),
], ],
), ),

View File

@ -11,6 +11,7 @@ import 'package:solian/models/post.dart';
import 'package:solian/models/realm.dart'; import 'package:solian/models/realm.dart';
import 'package:solian/providers/attachment_uploader.dart'; import 'package:solian/providers/attachment_uploader.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/providers/navigation.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:solian/theme.dart'; import 'package:solian/theme.dart';
import 'package:solian/widgets/app_bar_leading.dart'; import 'package:solian/widgets/app_bar_leading.dart';
@ -124,7 +125,12 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
void initState() { void initState() {
super.initState(); super.initState();
if (widget.edit == null && widget.reply == null && widget.repost == null) { 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) { if (widget.reply != null) {
_editorController.replyTo.value = widget.reply; _editorController.replyTo.value = widget.reply;
@ -158,7 +164,7 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
), ),
), ),
centerTitle: false, centerTitle: false,
toolbarHeight: SolianTheme.toolbarHeight(context), toolbarHeight: AppTheme.toolbarHeight(context),
actions: [ actions: [
TextButton( TextButton(
onPressed: _isBusy ? null : () => _applyPost(), onPressed: _isBusy ? null : () => _applyPost(),
@ -177,9 +183,7 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
children: [ children: [
ListTile( ListTile(
tileColor: Theme.of(context).colorScheme.surfaceContainerLow, tileColor: Theme.of(context).colorScheme.surfaceContainerLow,
title: SingleChildScrollView( title: Row(
scrollDirection: Axis.horizontal,
child: Row(
children: [ children: [
Text( Text(
_editorController.title ?? 'title'.tr, _editorController.title ?? 'title'.tr,
@ -189,12 +193,10 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
const Gap(6), const Gap(6),
if (_editorController.aliasController.text.isNotEmpty) if (_editorController.aliasController.text.isNotEmpty)
Badge( Badge(
label: label: Text('#${_editorController.aliasController.text}'),
Text('#${_editorController.aliasController.text}'),
), ),
], ],
), ),
),
subtitle: Text( subtitle: Text(
_editorController.description ?? 'description'.tr, _editorController.description ?? 'description'.tr,
maxLines: 2, maxLines: 2,
@ -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) const VerticalDivider(width: 0.3, thickness: 0.3)
.paddingSymmetric( .paddingSymmetric(
horizontal: 16, horizontal: 16,
), ),
if (SolianTheme.isLargeScreen(context)) if (AppTheme.isLargeScreen(context))
Expanded( Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
child: MarkdownTextContent( child: MarkdownTextContent(

View File

@ -62,7 +62,7 @@ class _RealmListScreenState extends State<RealmListScreen> {
leading: AppBarLeadingButton.adaptive(context), leading: AppBarLeadingButton.adaptive(context),
title: AppBarTitle('realm'.tr), title: AppBarTitle('realm'.tr),
centerTitle: true, centerTitle: true,
toolbarHeight: SolianTheme.toolbarHeight(context), toolbarHeight: AppTheme.toolbarHeight(context),
actions: [ actions: [
const BackgroundStateWidget(), const BackgroundStateWidget(),
const NotificationButton(), const NotificationButton(),
@ -77,7 +77,7 @@ class _RealmListScreenState extends State<RealmListScreen> {
}, },
), ),
SizedBox( SizedBox(
width: SolianTheme.isLargeScreen(context) ? 8 : 16, width: AppTheme.isLargeScreen(context) ? 8 : 16,
), ),
], ],
), ),

View File

@ -102,7 +102,7 @@ class _RealmOrganizeScreenState extends State<RealmOrganizeScreen> {
leading: AppBarLeadingButton.adaptive(context), leading: AppBarLeadingButton.adaptive(context),
title: AppBarTitle('realmOrganizing'.tr), title: AppBarTitle('realmOrganizing'.tr),
centerTitle: false, centerTitle: false,
toolbarHeight: SolianTheme.toolbarHeight(context), toolbarHeight: AppTheme.toolbarHeight(context),
actions: [ actions: [
TextButton( TextButton(
onPressed: _isBusy ? null : () => applyRealm(), onPressed: _isBusy ? null : () => applyRealm(),

View File

@ -114,7 +114,7 @@ class _RealmViewScreenState extends State<RealmViewScreen> {
}, },
), ),
SizedBox( SizedBox(
width: SolianTheme.isLargeScreen(context) ? 8 : 16, width: AppTheme.isLargeScreen(context) ? 8 : 16,
), ),
], ],
bottom: const TabBar( bottom: const TabBar(

View File

@ -33,11 +33,11 @@ class _SettingScreenState extends State<SettingScreen> {
tooltip: label, tooltip: label,
onPressed: () { onPressed: () {
context.read<ThemeSwitcher>().setTheme( context.read<ThemeSwitcher>().setTheme(
SolianTheme.build( AppTheme.build(
Brightness.light, Brightness.light,
seedColor: color, seedColor: color,
), ),
SolianTheme.build( AppTheme.build(
Brightness.dark, Brightness.dark,
seedColor: color, seedColor: color,
), ),

View File

@ -25,7 +25,7 @@ class CenteredShell extends StatelessWidget {
leading: AppBarLeadingButton.adaptive(context), leading: AppBarLeadingButton.adaptive(context),
title: AppBarTitle(state.topRoute?.name?.tr ?? 'page'.tr), title: AppBarTitle(state.topRoute?.name?.tr ?? 'page'.tr),
centerTitle: false, centerTitle: false,
toolbarHeight: SolianTheme.toolbarHeight(context), toolbarHeight: AppTheme.toolbarHeight(context),
) )
: null, : null,
body: Center( body: Center(

View File

@ -41,10 +41,10 @@ class RootShell extends StatelessWidget {
return Scaffold( return Scaffold(
key: rootScaffoldKey, key: rootScaffoldKey,
drawer: SolianTheme.isLargeScreen(context) drawer: AppTheme.isLargeScreen(context)
? null ? null
: AppNavigationDrawer(routeName: routeName), : AppNavigationDrawer(routeName: routeName),
body: SolianTheme.isLargeScreen(context) body: AppTheme.isLargeScreen(context)
? Row( ? Row(
children: [ children: [
if (showNavigation) AppNavigationDrawer(routeName: routeName), if (showNavigation) AppNavigationDrawer(routeName: routeName),

View File

@ -29,9 +29,9 @@ class SidebarShell extends StatelessWidget {
flex: 2, flex: 2,
child: child, child: child,
), ),
if (SolianTheme.isExtraLargeScreen(context)) if (AppTheme.isExtraLargeScreen(context))
const VerticalDivider(thickness: 0.3, width: 1), const VerticalDivider(thickness: 0.3, width: 1),
if (SolianTheme.isExtraLargeScreen(context)) if (AppTheme.isExtraLargeScreen(context))
Flexible( Flexible(
flex: 1, flex: 1,
child: sidebar ?? const SidebarPlaceholder(), child: sidebar ?? const SidebarPlaceholder(),
@ -47,10 +47,10 @@ class SidebarShell extends StatelessWidget {
leading: AppBarLeadingButton.adaptive(context), leading: AppBarLeadingButton.adaptive(context),
title: AppBarTitle(state.topRoute?.name?.tr ?? 'page'.tr), title: AppBarTitle(state.topRoute?.name?.tr ?? 'page'.tr),
centerTitle: false, centerTitle: false,
toolbarHeight: SolianTheme.toolbarHeight(context), toolbarHeight: AppTheme.toolbarHeight(context),
) )
: null, : null,
body: SolianTheme.isLargeScreen(context) body: AppTheme.isLargeScreen(context)
? Row( ? Row(
children: sidebarFirst children: sidebarFirst
? buildContent(context).reversed.toList() ? buildContent(context).reversed.toList()

View File

@ -32,11 +32,11 @@ class TitleShell extends StatelessWidget {
title ?? (state!.topRoute?.name?.tr ?? 'page'.tr), title ?? (state!.topRoute?.name?.tr ?? 'page'.tr),
), ),
centerTitle: isCenteredTitle, centerTitle: isCenteredTitle,
toolbarHeight: SolianTheme.toolbarHeight(context), toolbarHeight: AppTheme.toolbarHeight(context),
actions: [ actions: [
const BackgroundStateWidget(), const BackgroundStateWidget(),
SizedBox( SizedBox(
width: SolianTheme.isLargeScreen(context) ? 8 : 16, width: AppTheme.isLargeScreen(context) ? 8 : 16,
), ),
], ],
) )

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:solian/platform.dart'; import 'package:solian/platform.dart';
abstract class SolianTheme { abstract class AppTheme {
static bool isLargeScreen(BuildContext context) => static bool isLargeScreen(BuildContext context) =>
MediaQuery.of(context).size.width > 640; MediaQuery.of(context).size.width > 640;
@ -9,13 +9,13 @@ abstract class SolianTheme {
MediaQuery.of(context).size.width > 720; MediaQuery.of(context).size.width > 720;
static bool isSpecializedMacOS(BuildContext context) => static bool isSpecializedMacOS(BuildContext context) =>
PlatformInfo.isMacOS && !SolianTheme.isLargeScreen(context); PlatformInfo.isMacOS && !AppTheme.isLargeScreen(context);
static double? titleSpacing(BuildContext context) { static double? titleSpacing(BuildContext context) {
if (SolianTheme.isSpecializedMacOS(context)) { if (AppTheme.isSpecializedMacOS(context)) {
return 24; return 24;
} else { } else {
return SolianTheme.isLargeScreen(context) ? null : 24; return AppTheme.isLargeScreen(context) ? null : 24;
} }
} }

View File

@ -8,7 +8,7 @@ class AppBarTitle extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (SolianTheme.isSpecializedMacOS(context)) { if (AppTheme.isSpecializedMacOS(context)) {
return Text(title); return Text(title);
} else { } else {
return Text(title); return Text(title);

View File

@ -98,12 +98,12 @@ class ChannelCallIndicator extends StatelessWidget {
child: Text('callJoin'.tr), child: Text('callJoin'.tr),
); );
} else if (call.channel.value?.id == channel.id && } else if (call.channel.value?.id == channel.id &&
!SolianTheme.isLargeScreen(context)) { !AppTheme.isLargeScreen(context)) {
return TextButton( return TextButton(
onPressed: () => onJoin(), onPressed: () => onJoin(),
child: Text('callResume'.tr), child: Text('callResume'.tr),
); );
} else if (!SolianTheme.isLargeScreen(context)) { } else if (!AppTheme.isLargeScreen(context)) {
return TextButton( return TextButton(
onPressed: null, onPressed: null,
child: Text('callJoin'.tr), child: Text('callJoin'.tr),

View File

@ -11,6 +11,7 @@ class ChannelListWidget extends StatefulWidget {
final List<Channel> channels; final List<Channel> channels;
final int selfId; final int selfId;
final bool isDense; final bool isDense;
final bool isCollapsed;
final bool noCategory; final bool noCategory;
final bool useReplace; final bool useReplace;
final Function(Channel)? onSelected; final Function(Channel)? onSelected;
@ -20,6 +21,7 @@ class ChannelListWidget extends StatefulWidget {
required this.channels, required this.channels,
required this.selfId, required this.selfId,
this.isDense = false, this.isDense = false,
this.isCollapsed = false,
this.noCategory = false, this.noCategory = false,
this.useReplace = false, this.useReplace = false,
this.onSelected, this.onSelected,
@ -130,13 +132,25 @@ class _ChannelListWidgetState extends State<ChannelListWidget> {
final otherside = final otherside =
item.members!.where((e) => e.account.id != widget.selfId).first; item.members!.where((e) => e.account.id != widget.selfId).first;
return ListTile( final avatar = AccountAvatar(
leading: AccountAvatar(
content: otherside.account.avatar, content: otherside.account.avatar,
radius: widget.isDense ? 12 : 20, radius: widget.isDense ? 12 : 20,
bgColor: Theme.of(context).colorScheme.primary, bgColor: Theme.of(context).colorScheme.primary,
feColor: Theme.of(context).colorScheme.onPrimary, 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: avatar,
contentPadding: padding, contentPadding: padding,
title: Text(otherside.account.nick), title: Text(otherside.account.nick),
subtitle: !widget.isDense subtitle: !widget.isDense
@ -145,9 +159,7 @@ class _ChannelListWidgetState extends State<ChannelListWidget> {
onTap: () => _gotoChannel(item), onTap: () => _gotoChannel(item),
); );
} else { } else {
return ListTile( final avatar = CircleAvatar(
minTileHeight: widget.isDense ? 48 : null,
leading: CircleAvatar(
backgroundColor: item.realmId == null backgroundColor: item.realmId == null
? Theme.of(context).colorScheme.primary ? Theme.of(context).colorScheme.primary
: Colors.transparent, : Colors.transparent,
@ -159,10 +171,30 @@ class _ChannelListWidgetState extends State<ChannelListWidget> {
: Theme.of(context).colorScheme.primary, : Theme.of(context).colorScheme.primary,
size: widget.isDense ? 12 : 16, 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: avatar,
contentPadding: padding, contentPadding: padding,
title: Text(item.name), 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), onTap: () => _gotoChannel(item),
); );
} }

View File

@ -75,9 +75,6 @@ class ChatEvent extends StatelessWidget {
key: Key('m${item.uuid}attachments-box'), key: Key('m${item.uuid}attachments-box'),
width: MediaQuery.of(context).size.width, width: MediaQuery.of(context).size.width,
padding: EdgeInsets.only(top: isMerged ? 0 : 4, bottom: 4), padding: EdgeInsets.only(top: isMerged ? 0 : 4, bottom: 4),
constraints: const BoxConstraints(
maxHeight: 720,
),
child: AttachmentList( child: AttachmentList(
key: Key('m${item.uuid}attachments'), key: Key('m${item.uuid}attachments'),
parentId: item.uuid, parentId: item.uuid,
@ -301,7 +298,10 @@ class ChatEvent extends StatelessWidget {
], ],
).paddingSymmetric(horizontal: 12), ).paddingSymmetric(horizontal: 12),
_buildLinkExpansion().paddingOnly(left: 52, right: 8), _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),
], ],
); );
} }

View File

@ -245,7 +245,8 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
_editTo = widget.edit!; _editTo = widget.edit!;
_textController.text = body.text; _textController.text = body.text;
_attachments.addAll( _attachments.addAll(
widget.edit!.body['attachments']?.cast<int>() ?? List.empty()); widget.edit!.body['attachments']?.cast<String>() ?? List.empty(),
);
} }
if (widget.reply != null) { if (widget.reply != null) {
_replyTo = widget.reply!; _replyTo = widget.reply!;

View File

@ -192,9 +192,9 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer>
} }
void _autoResize() { void _autoResize() {
if (SolianTheme.isExtraLargeScreen(context)) { if (AppTheme.isExtraLargeScreen(context)) {
_expandDrawer(); _expandDrawer();
} else if (SolianTheme.isLargeScreen(context)) { } else if (AppTheme.isLargeScreen(context)) {
_collapseDrawer(); _collapseDrawer();
} else { } else {
_drawerAnimationController.value = 1; _drawerAnimationController.value = 1;
@ -229,7 +229,7 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer>
return Drawer( return Drawer(
width: _drawerAnimation.value, width: _drawerAnimation.value,
backgroundColor: backgroundColor:
SolianTheme.isLargeScreen(context) ? Colors.transparent : null, AppTheme.isLargeScreen(context) ? Colors.transparent : null,
child: child, child: child,
); );
}, },
@ -239,45 +239,38 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer>
children: [ children: [
_buildUserInfo().paddingSymmetric(vertical: 8), _buildUserInfo().paddingSymmetric(vertical: 8),
const Divider(thickness: 0.3, height: 1), const Divider(thickness: 0.3, height: 1),
Column( SizedBox(
width: double.infinity,
child: Wrap(
runSpacing: 8,
spacing: 8,
alignment: WrapAlignment.spaceAround,
children: AppNavigation.destinations children: AppNavigation.destinations
.map( .map(
(e) => _isCollapsed (e) => Tooltip(
? Tooltip(
message: e.label, message: e.label,
child: InkWell( child: InkWell(
child: Icon(e.icon, size: 20).paddingSymmetric( borderRadius:
horizontal: 28, const BorderRadius.all(Radius.circular(8)),
vertical: 16, child: Icon(
), e.icon,
size: 22,
color: Theme.of(context).colorScheme.onSurface,
).paddingAll(16),
onTap: () { onTap: () {
AppRouter.instance.goNamed(e.page); AppRouter.instance.goNamed(e.page);
_closeDrawer(); _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(), .toList(),
).paddingSymmetric(vertical: 8, horizontal: 12),
), ),
const Divider(thickness: 0.3, height: 1), const Divider(thickness: 0.3, height: 1),
Expanded( Expanded(
child: AppNavigationRegion( child: AppNavigationRegion(
isCollapsed: _isCollapsed, isCollapsed: _isCollapsed,
onSelected: (item) {
_closeDrawer();
},
), ),
), ),
const Divider(thickness: 0.3, height: 1), const Divider(thickness: 0.3, height: 1),

View File

@ -1,48 +1,110 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.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/providers/content/channel.dart';
import 'package:solian/router.dart'; import 'package:solian/providers/content/realm.dart';
import 'package:collection/collection.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 bool isCollapsed;
final Function(Channel item) onSelected;
const AppNavigationRegion({ const AppNavigationRegion({
super.key, super.key,
required this.onSelected,
this.isCollapsed = false, this.isCollapsed = false,
}); });
void _gotoChannel(Channel item) { @override
AppRouter.instance.goNamed( State<AppNavigationRegion> createState() => _AppNavigationRegionState();
'channelChat', }
pathParameters: {'alias': item.alias},
queryParameters: {
if (item.realmId != null) 'realm': item.realm!.alias,
},
);
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) { void _unFocusRealm() {
const padding = EdgeInsets.symmetric(horizontal: 20); setState(
() => Get.find<NavigationStateProvider>().focusedRealm.value = null,
);
}
if (isCollapsed) { @override
return InkWell( void dispose() {
child: const Icon(Icons.tag_outlined, size: 20).paddingSymmetric( super.dispose();
horizontal: 20, }
vertical: 16,
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,
), ),
onTap: () => _gotoChannel(item), ).paddingSymmetric(
vertical: 8,
)
: _buildEntryAvatar(focusedRealm!),
),
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( return ListTile(
minTileHeight: 0, minTileHeight: 0,
leading: const Icon(Icons.tag_outlined), leading: _buildEntryAvatar(item),
contentPadding: padding, contentPadding: padding,
title: Text(item.name), title: Text(item.name),
subtitle: Text( subtitle: Text(
@ -50,33 +112,43 @@ class AppNavigationRegion extends StatelessWidget {
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
onTap: () => _gotoChannel(item), onTap: () => _focusRealm(item),
); );
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final RealmProvider realms = Get.find();
final ChannelProvider channels = Get.find(); final ChannelProvider channels = Get.find();
final AuthProvider auth = Get.find();
final NavigationStateProvider navState = Get.find();
return Obx(() { return Obx(
final List<Channel> noRealmGroupChannels = channels.availableChannels () => AnimatedSwitcher(
.where((x) => x.type == 0 && x.realmId == null) switchInCurve: Curves.fastOutSlowIn,
.toList(); switchOutCurve: Curves.fastOutSlowIn,
final List<Channel> hasRealmGroupChannels = channels.availableChannels duration: const Duration(milliseconds: 300),
.where((x) => x.type == 0 && x.realmId != null) transitionBuilder: (child, animation) {
.toList(); return SlideTransition(
position: Tween<Offset>(
if (isCollapsed) { begin: const Offset(1.0, 0.0),
return CustomScrollView( end: Offset.zero,
).animate(animation),
child: Material(
color: Theme.of(context).colorScheme.surface,
child: child,
),
);
},
child: navState.focusedRealm.value == null
? widget.isCollapsed
? CustomScrollView(
slivers: [ slivers: [
const SliverPadding(padding: EdgeInsets.only(top: 8)), const SliverPadding(padding: EdgeInsets.only(top: 16)),
SliverList.builder( SliverList.builder(
itemCount: itemCount: realms.availableRealms.length,
noRealmGroupChannels.length + hasRealmGroupChannels.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final element = index >= noRealmGroupChannels.length final element = realms.availableRealms[index];
? hasRealmGroupChannels[index - noRealmGroupChannels.length]
: noRealmGroupChannels[index];
return Tooltip( return Tooltip(
message: element.name, message: element.name,
child: _buildEntry(context, element), child: _buildEntry(context, element),
@ -84,42 +156,60 @@ class AppNavigationRegion extends StatelessWidget {
}, },
), ),
], ],
); )
} : CustomScrollView(
return CustomScrollView(
slivers: [ slivers: [
const SliverPadding(padding: EdgeInsets.only(top: 8)),
SliverList.builder( SliverList.builder(
itemCount: noRealmGroupChannels.length, itemCount: realms.availableRealms.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final element = noRealmGroupChannels[index]; final element = realms.availableRealms[index];
return _buildEntry(context, element); 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)),
], ],
)
: 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,
),
),
),
],
),
),
); );
});
} }
} }

View File

@ -8,6 +8,7 @@ import 'package:intl/intl.dart';
import 'package:solian/models/post.dart'; import 'package:solian/models/post.dart';
import 'package:solian/screens/posts/post_detail.dart'; import 'package:solian/screens/posts/post_detail.dart';
import 'package:solian/shells/title_shell.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_avatar.dart';
import 'package:solian/widgets/account/account_profile_popup.dart'; import 'package:solian/widgets/account/account_profile_popup.dart';
import 'package:solian/widgets/attachments/attachment_list.dart'; import 'package:solian/widgets/attachments/attachment_list.dart';
@ -302,7 +303,7 @@ class _PostItemState extends State<PostItem> {
autoload: false, autoload: false,
isGrid: true, isGrid: true,
).paddingOnly(left: 36, top: 4, bottom: 4); ).paddingOnly(left: 36, top: 4, bottom: 4);
} else if (attachments.length > 1) { } else if (attachments.length > 1 || AppTheme.isLargeScreen(context)) {
return AttachmentList( return AttachmentList(
parentId: widget.item.id.toString(), parentId: widget.item.id.toString(),
attachmentsId: attachments, attachmentsId: attachments,
@ -497,7 +498,10 @@ class _PostItemState extends State<PostItem> {
], ],
).paddingOnly( ).paddingOnly(
top: 10, top: 10,
bottom: attachments.length == 1 ? 10 : 0, bottom:
(attachments.length == 1 && !AppTheme.isLargeScreen(context))
? 10
: 0,
right: 16, right: 16,
left: 16, left: 16,
), ),
@ -514,8 +518,13 @@ class _PostItemState extends State<PostItem> {
}); });
}, },
).paddingOnly( ).paddingOnly(
top: attachments.length == 1 ? 10 : 6, top: (attachments.length == 1 && !AppTheme.isLargeScreen(context))
left: attachments.length == 1 ? 24 : 60, ? 10
: 6,
left:
(attachments.length == 1 && !AppTheme.isLargeScreen(context))
? 24
: 60,
right: 16, right: 16,
bottom: 10, bottom: 10,
) )

View File

@ -2,7 +2,7 @@ name: solian
description: "The Solar Network App" description: "The Solar Network App"
publish_to: "none" publish_to: "none"
version: 1.2.1+35 version: 1.2.1+36
environment: environment:
sdk: ">=3.3.4 <4.0.0" sdk: ">=3.3.4 <4.0.0"

View File

@ -35,7 +35,6 @@
<link rel="manifest" href="manifest.json"> <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"> <style id="splash-screen-style">
html { html {
height: 100% height: 100%