Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
8ec33ccbf4 | |||
d267316a35 | |||
138da60e55 | |||
4562c2f991 | |||
8009f4ca9b | |||
54dee9702b | |||
94385564bd | |||
0b2309816f | |||
8283272a3b | |||
eb02a47e9a |
@ -52,6 +52,17 @@ class Realm {
|
|||||||
'is_community': isCommunity,
|
'is_community': isCommunity,
|
||||||
'account_id': accountId,
|
'account_id': accountId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (other is Realm) {
|
||||||
|
return other.id == id;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => id;
|
||||||
}
|
}
|
||||||
|
|
||||||
class RealmMember {
|
class RealmMember {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:solian/models/account.dart';
|
import 'package:solian/models/account.dart';
|
||||||
import 'package:solian/models/attachment.dart';
|
import 'package:solian/models/attachment.dart';
|
||||||
import 'package:solian/services.dart';
|
import 'package:solian/services.dart';
|
||||||
@ -32,7 +31,7 @@ class Sticker {
|
|||||||
required this.account,
|
required this.account,
|
||||||
});
|
});
|
||||||
|
|
||||||
String get textPlaceholder => '${pack?.prefix}$alias'.camelCase!;
|
String get textPlaceholder => '${pack?.prefix}$alias';
|
||||||
String get textWarpedPlaceholder => ':$textPlaceholder:';
|
String get textWarpedPlaceholder => ':$textPlaceholder:';
|
||||||
|
|
||||||
String get imageUrl => ServiceFinder.buildUrl(
|
String get imageUrl => ServiceFinder.buildUrl(
|
||||||
|
@ -8,6 +8,9 @@ class StickerProvider extends GetxController {
|
|||||||
final RxList<Sticker> availableStickers = RxList.empty(growable: true);
|
final RxList<Sticker> availableStickers = RxList.empty(growable: true);
|
||||||
|
|
||||||
Future<void> refreshAvailableStickers() async {
|
Future<void> refreshAvailableStickers() async {
|
||||||
|
availableStickers.clear();
|
||||||
|
aliasImageMapping.clear();
|
||||||
|
|
||||||
final client = ServiceFinder.configureClient('files');
|
final client = ServiceFinder.configureClient('files');
|
||||||
final resp = await client.get(
|
final resp = await client.get(
|
||||||
'/stickers/manifest?take=100',
|
'/stickers/manifest?take=100',
|
||||||
@ -20,7 +23,7 @@ class StickerProvider extends GetxController {
|
|||||||
for (final pack in out) {
|
for (final pack in out) {
|
||||||
for (final sticker in (pack.stickers ?? List<Sticker>.empty())) {
|
for (final sticker in (pack.stickers ?? List<Sticker>.empty())) {
|
||||||
sticker.pack = pack;
|
sticker.pack = pack;
|
||||||
aliasImageMapping['${pack.prefix}${sticker.alias}'.camelCase!] =
|
aliasImageMapping[sticker.textPlaceholder.toUpperCase()] =
|
||||||
sticker.imageUrl;
|
sticker.imageUrl;
|
||||||
availableStickers.add(sticker);
|
availableStickers.add(sticker);
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ class _StickerScreenState extends State<StickerScreen> {
|
|||||||
);
|
);
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(item.name),
|
title: Text(item.name),
|
||||||
subtitle: Text(':${'$prefix${item.alias}'.camelCase}:'),
|
subtitle: Text(item.textWarpedPlaceholder),
|
||||||
contentPadding: const EdgeInsets.only(left: 16, right: 14),
|
contentPadding: const EdgeInsets.only(left: 16, right: 14),
|
||||||
trailing: Row(
|
trailing: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -174,9 +174,10 @@ class _StickerScreenState extends State<StickerScreen> {
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
children: item.stickers
|
children: item.stickers?.map((x) {
|
||||||
?.map((x) => _buildEmoteEntry(x, item.prefix))
|
x.pack = item;
|
||||||
.toList() ??
|
return _buildEmoteEntry(x, item.prefix);
|
||||||
|
}).toList() ??
|
||||||
List.empty(),
|
List.empty(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -70,7 +70,7 @@ class _AttachmentFullScreenState extends State<AttachmentFullScreen> {
|
|||||||
'/attachments/${widget.item.id}',
|
'/attachments/${widget.item.id}',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (PlatformInfo.isWeb) {
|
if (PlatformInfo.isWeb || PlatformInfo.isDesktop) {
|
||||||
await launchUrlString(url);
|
await launchUrlString(url);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -244,6 +244,7 @@ class _AttachmentItemVideoState extends State<_AttachmentItemVideo> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_showContent = widget.autoload;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -3,7 +3,7 @@ import 'dart:ui';
|
|||||||
|
|
||||||
import 'package:carousel_slider/carousel_slider.dart';
|
import 'package:carousel_slider/carousel_slider.dart';
|
||||||
import 'package:dismissible_page/dismissible_page.dart';
|
import 'package:dismissible_page/dismissible_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart' hide CarouselController;
|
||||||
import 'package:flutter_animate/flutter_animate.dart';
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:solian/models/attachment.dart';
|
import 'package:solian/models/attachment.dart';
|
||||||
|
@ -23,16 +23,19 @@ class ChatEventMessage extends StatelessWidget {
|
|||||||
final body = EventMessageBody.fromJson(item.body);
|
final body = EventMessageBody.fromJson(item.body);
|
||||||
final hasAttachment = body.attachments?.isNotEmpty ?? false;
|
final hasAttachment = body.attachments?.isNotEmpty ?? false;
|
||||||
|
|
||||||
if (body.text.isEmpty && hasAttachment) {
|
if (body.text.isEmpty &&
|
||||||
|
hasAttachment &&
|
||||||
|
!isContentPreviewing &&
|
||||||
|
!isQuote) {
|
||||||
final unFocusColor =
|
final unFocusColor =
|
||||||
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
|
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
Icons.attachment,
|
Icons.file_copy,
|
||||||
size: 18,
|
size: 15,
|
||||||
color: unFocusColor,
|
color: unFocusColor,
|
||||||
).paddingOnly(right: 6),
|
).paddingOnly(right: 5),
|
||||||
Text(
|
Text(
|
||||||
'attachmentHint'.trParams(
|
'attachmentHint'.trParams(
|
||||||
{'count': body.attachments?.length.toString() ?? 0.toString()},
|
{'count': body.attachments?.length.toString() ?? 0.toString()},
|
||||||
|
@ -129,12 +129,14 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
|
|
||||||
client = auth.configureClient('messaging');
|
client = auth.configureClient('messaging');
|
||||||
|
|
||||||
|
if (_textController.text.trim().isEmpty && _attachments.isEmpty) return;
|
||||||
|
|
||||||
const uuid = Uuid();
|
const uuid = Uuid();
|
||||||
final payload = {
|
final payload = {
|
||||||
'uuid': uuid.v4(),
|
'uuid': uuid.v4(),
|
||||||
'type': _editTo == null ? 'messages.new' : 'messages.edit',
|
'type': _editTo == null ? 'messages.new' : 'messages.edit',
|
||||||
'body': {
|
'body': {
|
||||||
'text': _textController.text,
|
'text': _textController.text.trim(),
|
||||||
'algorithm': 'plain',
|
'algorithm': 'plain',
|
||||||
'attachments': List.from(_attachments),
|
'attachments': List.from(_attachments),
|
||||||
'related_users': [
|
'related_users': [
|
||||||
@ -239,7 +241,7 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
if (suggestion.type == 'emotes') {
|
if (suggestion.type == 'emotes') {
|
||||||
insertText = suggestion.content;
|
insertText = suggestion.content;
|
||||||
startText = replaceText.replaceFirstMapped(
|
startText = replaceText.replaceFirstMapped(
|
||||||
RegExp(r':(?:([a-z0-9_+-]+)~)?([a-z0-9_+-]+)$'),
|
RegExp(r':(?:([-\w]+)~)?([-\w]+)$'),
|
||||||
(Match m) => insertText,
|
(Match m) => insertText,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -247,7 +249,7 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
if (suggestion.type == 'users') {
|
if (suggestion.type == 'users') {
|
||||||
insertText = suggestion.content;
|
insertText = suggestion.content;
|
||||||
startText = replaceText.replaceFirstMapped(
|
startText = replaceText.replaceFirstMapped(
|
||||||
RegExp(r'(?:\s|^)@([a-z0-9_+-]+)$'),
|
RegExp(r'(?:\s|^)@([-\w]+)$'),
|
||||||
(Match m) => insertText,
|
(Match m) => insertText,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -349,15 +351,17 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
final searchText = _textController.text
|
final searchText = _textController.text
|
||||||
.substring(0, _textController.selection.baseOffset);
|
.substring(0, _textController.selection.baseOffset);
|
||||||
|
|
||||||
final emojiMatch =
|
final emojiMatch = RegExp(r':(?:([-\w]+)~)?([-\w]+)$')
|
||||||
RegExp(r':(?:([a-z0-9_+-]+)~)?([a-z0-9_+-]+)$')
|
.firstMatch(searchText);
|
||||||
.firstMatch(searchText);
|
|
||||||
if (emojiMatch != null) {
|
if (emojiMatch != null) {
|
||||||
final StickerProvider stickers = Get.find();
|
final StickerProvider stickers = Get.find();
|
||||||
final emoteSearch = emojiMatch[2]!;
|
final emoteSearch = emojiMatch[2]!;
|
||||||
return stickers.availableStickers
|
return stickers.availableStickers
|
||||||
.where((x) =>
|
.where(
|
||||||
x.textWarpedPlaceholder.contains(emoteSearch))
|
(x) => x.textWarpedPlaceholder
|
||||||
|
.toUpperCase()
|
||||||
|
.contains(emoteSearch.toUpperCase()),
|
||||||
|
)
|
||||||
.map(
|
.map(
|
||||||
(x) => ChatMessageSuggestion(
|
(x) => ChatMessageSuggestion(
|
||||||
type: 'emotes',
|
type: 'emotes',
|
||||||
@ -379,8 +383,8 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
final userMatch = RegExp(r'(?:\s|^)@([a-z0-9_+-]+)$')
|
final userMatch =
|
||||||
.firstMatch(searchText);
|
RegExp(r'(?:\s|^)@([-\w]+)$').firstMatch(searchText);
|
||||||
if (userMatch != null) {
|
if (userMatch != null) {
|
||||||
final userSearch = userMatch[1]!.toLowerCase();
|
final userSearch = userMatch[1]!.toLowerCase();
|
||||||
final AuthProvider auth = Get.find();
|
final AuthProvider auth = Get.find();
|
||||||
|
@ -24,9 +24,9 @@ class MarkdownTextContent extends StatelessWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Widget _buildContent(BuildContext context) {
|
Widget _buildContent(BuildContext context) {
|
||||||
final emojiMatch = RegExp(r':([a-z0-9_+-]+):').allMatches(content);
|
final emojiRegex = RegExp(r':([-\w]+):');
|
||||||
final isOnlyEmoji =
|
final emojiMatch = emojiRegex.allMatches(content);
|
||||||
content.replaceAll(RegExp(r':([a-z0-9_+-]+):'), '').trim().isEmpty;
|
final isOnlyEmoji = content.replaceAll(emojiRegex, '').trim().isEmpty;
|
||||||
|
|
||||||
return Markdown(
|
return Markdown(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
@ -88,20 +88,40 @@ class MarkdownTextContent extends StatelessWidget {
|
|||||||
final segments = url.replaceFirst('solink://', '').split('/');
|
final segments = url.replaceFirst('solink://', '').split('/');
|
||||||
switch (segments[0]) {
|
switch (segments[0]) {
|
||||||
case 'stickers':
|
case 'stickers':
|
||||||
|
double radius = 8;
|
||||||
final StickerProvider sticker = Get.find();
|
final StickerProvider sticker = Get.find();
|
||||||
url = sticker.aliasImageMapping[segments[1]]!;
|
url = sticker.aliasImageMapping[segments[1].toUpperCase()]!;
|
||||||
if (emojiMatch.length <= 1 && isOnlyEmoji) {
|
if (emojiMatch.length <= 1 && isOnlyEmoji) {
|
||||||
width = 112;
|
width = 128;
|
||||||
height = 112;
|
height = 128;
|
||||||
} else if (emojiMatch.length <= 3 && isOnlyEmoji) {
|
} else if (emojiMatch.length <= 3 && isOnlyEmoji) {
|
||||||
width = 56;
|
width = 32;
|
||||||
height = 56;
|
height = 32;
|
||||||
} else {
|
} else {
|
||||||
width = 28;
|
radius = 4;
|
||||||
height = 28;
|
width = 16;
|
||||||
|
height = 16;
|
||||||
}
|
}
|
||||||
fit = BoxFit.contain;
|
fit = BoxFit.contain;
|
||||||
break;
|
return ClipRRect(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(radius)),
|
||||||
|
child: Container(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
|
child: PlatformInfo.canCacheImage
|
||||||
|
? CachedNetworkImage(
|
||||||
|
imageUrl: url,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
fit: fit,
|
||||||
|
)
|
||||||
|
: Image.network(
|
||||||
|
url,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
fit: fit,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).paddingSymmetric(vertical: 4);
|
||||||
case 'attachments':
|
case 'attachments':
|
||||||
const radius = BorderRadius.all(Radius.circular(8));
|
const radius = BorderRadius.all(Radius.circular(8));
|
||||||
return LimitedBox(
|
return LimitedBox(
|
||||||
@ -161,12 +181,12 @@ class _UserNameCardInlineSyntax extends InlineSyntax {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _CustomEmoteInlineSyntax extends InlineSyntax {
|
class _CustomEmoteInlineSyntax extends InlineSyntax {
|
||||||
_CustomEmoteInlineSyntax() : super(r':([a-z0-9_+-]+):');
|
_CustomEmoteInlineSyntax() : super(r':([-\w]+):');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool onMatch(markdown.InlineParser parser, Match match) {
|
bool onMatch(markdown.InlineParser parser, Match match) {
|
||||||
final StickerProvider sticker = Get.find();
|
final StickerProvider sticker = Get.find();
|
||||||
final alias = match[1]!;
|
final alias = match[1]!.toUpperCase();
|
||||||
if (sticker.aliasImageMapping[alias] == null) {
|
if (sticker.aliasImageMapping[alias] == null) {
|
||||||
parser.advanceBy(1);
|
parser.advanceBy(1);
|
||||||
return false;
|
return false;
|
||||||
|
@ -3,16 +3,15 @@ import 'package:get/get.dart';
|
|||||||
import 'package:solian/models/account_status.dart';
|
import 'package:solian/models/account_status.dart';
|
||||||
import 'package:solian/providers/account_status.dart';
|
import 'package:solian/providers/account_status.dart';
|
||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
import 'package:solian/providers/content/channel.dart';
|
|
||||||
import 'package:solian/providers/relation.dart';
|
import 'package:solian/providers/relation.dart';
|
||||||
import 'package:solian/router.dart';
|
import 'package:solian/router.dart';
|
||||||
import 'package:solian/shells/root_shell.dart';
|
import 'package:solian/shells/root_shell.dart';
|
||||||
import 'package:solian/theme.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_status_action.dart';
|
import 'package:solian/widgets/account/account_status_action.dart';
|
||||||
import 'package:solian/widgets/channel/channel_list.dart';
|
|
||||||
import 'package:solian/widgets/navigation/app_navigation.dart';
|
import 'package:solian/widgets/navigation/app_navigation.dart';
|
||||||
import 'package:badges/badges.dart' as badges;
|
import 'package:badges/badges.dart' as badges;
|
||||||
|
import 'package:solian/widgets/navigation/app_navigation_regions.dart';
|
||||||
|
|
||||||
class AppNavigationDrawer extends StatefulWidget {
|
class AppNavigationDrawer extends StatefulWidget {
|
||||||
final String? routeName;
|
final String? routeName;
|
||||||
@ -24,12 +23,8 @@ class AppNavigationDrawer extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
|
class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
|
||||||
int? _selectedIndex = 0;
|
|
||||||
|
|
||||||
AccountStatus? _accountStatus;
|
AccountStatus? _accountStatus;
|
||||||
|
|
||||||
late final ChannelProvider _channels;
|
|
||||||
|
|
||||||
Future<void> _getStatus() async {
|
Future<void> _getStatus() async {
|
||||||
final StatusProvider provider = Get.find();
|
final StatusProvider provider = Get.find();
|
||||||
|
|
||||||
@ -41,15 +36,6 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _detectSelectedIndex() {
|
|
||||||
if (widget.routeName == null) return;
|
|
||||||
|
|
||||||
final nameList = AppNavigation.destinations.map((x) => x.page).toList();
|
|
||||||
final idx = nameList.indexOf(widget.routeName!);
|
|
||||||
|
|
||||||
_selectedIndex = idx != -1 ? idx : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _closeDrawer() {
|
void _closeDrawer() {
|
||||||
rootScaffoldKey.currentState!.closeDrawer();
|
rootScaffoldKey.currentState!.closeDrawer();
|
||||||
}
|
}
|
||||||
@ -59,7 +45,6 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
|
|||||||
icon: const Icon(Icons.settings),
|
icon: const Icon(Icons.settings),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
AppRouter.instance.pushNamed('settings');
|
AppRouter.instance.pushNamed('settings');
|
||||||
setState(() => _selectedIndex = null);
|
|
||||||
_closeDrawer();
|
_closeDrawer();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -67,175 +52,139 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_channels = Get.find();
|
|
||||||
_detectSelectedIndex();
|
|
||||||
_getStatus();
|
_getStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeDependencies() {
|
|
||||||
super.didChangeDependencies();
|
|
||||||
_detectSelectedIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final AuthProvider auth = Get.find();
|
final AuthProvider auth = Get.find();
|
||||||
|
|
||||||
return NavigationDrawer(
|
return Drawer(
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
SolianTheme.isLargeScreen(context) ? Colors.transparent : null,
|
SolianTheme.isLargeScreen(context) ? Colors.transparent : null,
|
||||||
selectedIndex: _selectedIndex,
|
child: SafeArea(
|
||||||
onDestinationSelected: (idx) {
|
child: Column(
|
||||||
setState(() => _selectedIndex = idx);
|
children: [
|
||||||
AppRouter.instance.goNamed(AppNavigation.destinations[idx].page);
|
Obx(() {
|
||||||
_closeDrawer();
|
if (auth.isAuthorized.isFalse || auth.userProfile.value == null) {
|
||||||
},
|
return ListTile(
|
||||||
children: [
|
contentPadding: const EdgeInsets.symmetric(horizontal: 28),
|
||||||
Obx(() {
|
leading: const Icon(Icons.account_circle),
|
||||||
if (auth.isAuthorized.isFalse || auth.userProfile.value == null) {
|
title: Text('guest'.tr),
|
||||||
return ListTile(
|
subtitle: Text('unsignedIn'.tr),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 28),
|
trailing: _buildSettingButton(),
|
||||||
leading: const Icon(Icons.account_circle),
|
onTap: () {
|
||||||
title: Text('guest'.tr),
|
AppRouter.instance.goNamed('account');
|
||||||
subtitle: Text('unsignedIn'.tr),
|
_closeDrawer();
|
||||||
trailing: _buildSettingButton(),
|
},
|
||||||
onTap: () {
|
|
||||||
AppRouter.instance.goNamed('account');
|
|
||||||
setState(() => _selectedIndex = null);
|
|
||||||
_closeDrawer();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ListTile(
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
title: Text(
|
|
||||||
auth.userProfile.value!['nick'],
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.fade,
|
|
||||||
),
|
|
||||||
subtitle: Builder(
|
|
||||||
builder: (context) {
|
|
||||||
if (_accountStatus == null) {
|
|
||||||
return Text('loading'.tr);
|
|
||||||
}
|
|
||||||
final info = StatusProvider.determineStatus(
|
|
||||||
_accountStatus!,
|
|
||||||
);
|
);
|
||||||
return Text(
|
}
|
||||||
info.$3,
|
|
||||||
|
return ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
title: Text(
|
||||||
|
auth.userProfile.value!['nick'],
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.fade,
|
overflow: TextOverflow.fade,
|
||||||
);
|
),
|
||||||
},
|
subtitle: Builder(
|
||||||
),
|
builder: (context) {
|
||||||
leading: Obx(() {
|
if (_accountStatus == null) {
|
||||||
final statusBadgeColor = _accountStatus != null
|
return Text('loading'.tr);
|
||||||
? StatusProvider.determineStatus(
|
}
|
||||||
|
final info = StatusProvider.determineStatus(
|
||||||
_accountStatus!,
|
_accountStatus!,
|
||||||
).$2
|
);
|
||||||
: Colors.grey;
|
return Text(
|
||||||
|
info.$3,
|
||||||
final RelationshipProvider relations = Get.find();
|
maxLines: 1,
|
||||||
final accountNotifications = relations.friendRequestCount.value;
|
overflow: TextOverflow.fade,
|
||||||
|
);
|
||||||
return badges.Badge(
|
},
|
||||||
badgeContent: Text(
|
|
||||||
accountNotifications.toString(),
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
),
|
),
|
||||||
showBadge: accountNotifications > 0,
|
leading: Obx(() {
|
||||||
position: badges.BadgePosition.topEnd(
|
final statusBadgeColor = _accountStatus != null
|
||||||
top: -10,
|
? StatusProvider.determineStatus(
|
||||||
end: -6,
|
_accountStatus!,
|
||||||
),
|
).$2
|
||||||
child: badges.Badge(
|
: Colors.grey;
|
||||||
showBadge: _accountStatus != null,
|
|
||||||
badgeStyle: badges.BadgeStyle(badgeColor: statusBadgeColor),
|
|
||||||
position: badges.BadgePosition.bottomEnd(
|
|
||||||
bottom: 0,
|
|
||||||
end: -2,
|
|
||||||
),
|
|
||||||
child: AccountAvatar(
|
|
||||||
content: auth.userProfile.value!['avatar'],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
trailing: _buildSettingButton(),
|
|
||||||
onTap: () {
|
|
||||||
AppRouter.instance.goNamed('account');
|
|
||||||
setState(() => _selectedIndex = null);
|
|
||||||
_closeDrawer();
|
|
||||||
},
|
|
||||||
onLongPress: () {
|
|
||||||
showModalBottomSheet(
|
|
||||||
useRootNavigator: true,
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AccountStatusAction(
|
|
||||||
currentStatus: _accountStatus!.status,
|
|
||||||
),
|
|
||||||
).then((val) {
|
|
||||||
if (val == true) _getStatus();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}).paddingOnly(top: 8),
|
|
||||||
const Divider(thickness: 0.3, height: 1).paddingOnly(
|
|
||||||
bottom: 12,
|
|
||||||
top: 8,
|
|
||||||
),
|
|
||||||
...AppNavigation.destinations.map(
|
|
||||||
(e) => NavigationDrawerDestination(
|
|
||||||
icon: e.icon,
|
|
||||||
label: Text(e.label),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Divider(thickness: 0.3, height: 1).paddingOnly(
|
|
||||||
top: 12,
|
|
||||||
),
|
|
||||||
Obx(() {
|
|
||||||
if (auth.isAuthorized.isFalse || auth.userProfile.value == null) {
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
|
|
||||||
final selfId = auth.userProfile.value!['id'];
|
final RelationshipProvider relations = Get.find();
|
||||||
|
final accountNotifications =
|
||||||
|
relations.friendRequestCount.value;
|
||||||
|
|
||||||
return Column(
|
return badges.Badge(
|
||||||
children: [
|
badgeContent: Text(
|
||||||
Theme(
|
accountNotifications.toString(),
|
||||||
data: Theme.of(context)
|
style: const TextStyle(color: Colors.white),
|
||||||
.copyWith(dividerColor: Colors.transparent),
|
),
|
||||||
child: ExpansionTile(
|
showBadge: accountNotifications > 0,
|
||||||
title: Text('channels'.tr),
|
position: badges.BadgePosition.topEnd(
|
||||||
tilePadding: const EdgeInsets.symmetric(horizontal: 24),
|
top: -10,
|
||||||
children: [
|
end: -6,
|
||||||
Obx(
|
),
|
||||||
() => SizedBox(
|
child: badges.Badge(
|
||||||
height: 360,
|
showBadge: _accountStatus != null,
|
||||||
child: RefreshIndicator(
|
badgeStyle:
|
||||||
onRefresh: () => _channels.refreshAvailableChannel(),
|
badges.BadgeStyle(badgeColor: statusBadgeColor),
|
||||||
child: ChannelListWidget(
|
position: badges.BadgePosition.bottomEnd(
|
||||||
channels: _channels.groupChannels,
|
bottom: 0,
|
||||||
selfId: selfId,
|
end: -2,
|
||||||
isDense: true,
|
),
|
||||||
useReplace: true,
|
child: AccountAvatar(
|
||||||
onSelected: (_) {
|
content: auth.userProfile.value!['avatar'],
|
||||||
setState(() => _selectedIndex = null);
|
|
||||||
_closeDrawer();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
);
|
||||||
),
|
}),
|
||||||
|
trailing: _buildSettingButton(),
|
||||||
|
onTap: () {
|
||||||
|
AppRouter.instance.goNamed('account');
|
||||||
|
_closeDrawer();
|
||||||
|
},
|
||||||
|
onLongPress: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
useRootNavigator: true,
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AccountStatusAction(
|
||||||
|
currentStatus: _accountStatus!.status,
|
||||||
|
),
|
||||||
|
).then((val) {
|
||||||
|
if (val == true) _getStatus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}).paddingOnly(top: 8),
|
||||||
|
const Divider(thickness: 0.3, height: 1),
|
||||||
|
Column(
|
||||||
|
children: AppNavigation.destinations
|
||||||
|
.map(
|
||||||
|
(e) => ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 20,
|
||||||
|
),
|
||||||
|
leading: e.icon,
|
||||||
|
title: Text(e.label),
|
||||||
|
enabled: true,
|
||||||
|
onTap: () {
|
||||||
|
AppRouter.instance.goNamed(e.page);
|
||||||
|
_closeDrawer();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
).paddingSymmetric(vertical: 8),
|
||||||
|
const Divider(thickness: 0.3, height: 1),
|
||||||
|
Expanded(
|
||||||
|
child: AppNavigationRegions(
|
||||||
|
onSelected: (item) {
|
||||||
|
_closeDrawer();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
);
|
],
|
||||||
}),
|
),
|
||||||
],
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
87
lib/widgets/navigation/app_navigation_regions.dart
Normal file
87
lib/widgets/navigation/app_navigation_regions.dart
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:solian/models/channel.dart';
|
||||||
|
import 'package:solian/providers/content/channel.dart';
|
||||||
|
import 'package:solian/router.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
|
class AppNavigationRegions extends StatelessWidget {
|
||||||
|
final Function(Channel item) onSelected;
|
||||||
|
|
||||||
|
const AppNavigationRegions({super.key, required this.onSelected});
|
||||||
|
|
||||||
|
void _gotoChannel(Channel item) {
|
||||||
|
AppRouter.instance.pushReplacementNamed(
|
||||||
|
'channelChat',
|
||||||
|
pathParameters: {'alias': item.alias},
|
||||||
|
queryParameters: {
|
||||||
|
if (item.realmId != null) 'realm': item.realm!.alias,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
onSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildEntry(BuildContext context, Channel item) {
|
||||||
|
const padding = EdgeInsets.symmetric(horizontal: 20);
|
||||||
|
|
||||||
|
return ListTile(
|
||||||
|
minTileHeight: 0,
|
||||||
|
leading: const Icon(Icons.tag_outlined),
|
||||||
|
contentPadding: padding,
|
||||||
|
title: Text(item.name),
|
||||||
|
subtitle: Text(
|
||||||
|
item.description,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
onTap: () => _gotoChannel(item),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final ChannelProvider channels = 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();
|
||||||
|
|
||||||
|
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,
|
||||||
|
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(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -436,6 +436,7 @@ class _PostItemState extends State<PostItem> {
|
|||||||
left: 16,
|
left: 16,
|
||||||
),
|
),
|
||||||
AttachmentList(
|
AttachmentList(
|
||||||
|
flatMaxHeight: MediaQuery.of(context).size.width,
|
||||||
parentId: widget.item.id.toString(),
|
parentId: widget.item.id.toString(),
|
||||||
attachmentsId: attachments,
|
attachmentsId: attachments,
|
||||||
isGrid: attachments.length > 1,
|
isGrid: attachments.length > 1,
|
||||||
|
30
pubspec.lock
30
pubspec.lock
@ -226,7 +226,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "4.10.0"
|
version: "4.10.0"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: collection
|
name: collection
|
||||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||||
@ -405,10 +405,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: file_picker
|
name: file_picker
|
||||||
sha256: "824f5b9f389bfc4dddac3dea76cd70c51092d9dff0b2ece7ef4f53db8547d258"
|
sha256: "825aec673606875c33cd8d3c4083f1a3c3999015a84178b317b7ef396b7384f3"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.0.6"
|
version: "8.0.7"
|
||||||
file_selector_linux:
|
file_selector_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -740,10 +740,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_webrtc
|
name: flutter_webrtc
|
||||||
sha256: f46bd76cef6e8d787dc707d0c591f0e89c912a2970c7b5e68a55b9cca1bdde4c
|
sha256: "67faa07cf49392b50b1aa14590a83caa64d2109345fabd29899dcd8da8538348"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.11.6"
|
version: "0.11.6+hotfix.1"
|
||||||
font_awesome_flutter:
|
font_awesome_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -788,10 +788,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: go_router
|
name: go_router
|
||||||
sha256: d380de0355788c5c784fe9f81b43fc833b903991c25ecc4e2a416a67faefa722
|
sha256: ddc16d34b0d74cb313986918c0f0885a7ba2fc24d8fb8419de75f0015144ccfe
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.2.2"
|
version: "14.2.3"
|
||||||
graphs:
|
graphs:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -876,10 +876,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_android
|
name: image_picker_android
|
||||||
sha256: "3fe99e3a459b969f657565a853353bdea7e3d3cae34f7dd124836267d426ec87"
|
sha256: c0e72ecd170b00a5590bb71238d57dc8ad22ee14c60c6b0d1a4e05cafbc5db4b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.12+10"
|
version: "0.8.12+11"
|
||||||
image_picker_for_web:
|
image_picker_for_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1276,10 +1276,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_android
|
name: permission_handler_android
|
||||||
sha256: b29a799ca03be9f999aa6c39f7de5209482d638e6f857f6b93b0875c618b7e54
|
sha256: eaf2a1ec4472775451e88ca6a7b86559ef2f1d1ed903942ed135e38ea0097dca
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "12.0.7"
|
version: "12.0.8"
|
||||||
permission_handler_apple:
|
permission_handler_apple:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1300,10 +1300,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_platform_interface
|
name: permission_handler_platform_interface
|
||||||
sha256: "48d4fcf201a1dad93ee869ab0d4101d084f49136ec82a8a06ed9cfeacab9fd20"
|
sha256: fe0ffe274d665be8e34f9c59705441a7d248edebbe5d9e3ec2665f88b79358ea
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.1"
|
version: "4.2.2"
|
||||||
permission_handler_windows:
|
permission_handler_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1897,10 +1897,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_web
|
name: url_launcher_web
|
||||||
sha256: a36e2d7981122fa185006b216eb6b5b97ede3f9a54b7a511bc966971ab98d049
|
sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.3.3"
|
||||||
url_launcher_windows:
|
url_launcher_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -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+8
|
version: 1.2.1+9
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.3.4 <4.0.0"
|
sdk: ">=3.3.4 <4.0.0"
|
||||||
@ -68,6 +68,7 @@ dependencies:
|
|||||||
async: ^2.11.0
|
async: ^2.11.0
|
||||||
field_suggestion: ^0.2.5
|
field_suggestion: ^0.2.5
|
||||||
flutter_typeahead: ^5.2.0
|
flutter_typeahead: ^5.2.0
|
||||||
|
collection: ^1.18.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Reference in New Issue
Block a user