Compare commits
26 Commits
Author | SHA1 | Date | |
---|---|---|---|
29ca263130 | |||
7332f68d9c | |||
e9e6f3313e | |||
85764c37c2 | |||
ef1f29f905 | |||
22026efa7d | |||
4a3e6a9e15 | |||
00092ba7b6 | |||
b5da8ece4a | |||
dfe9165bc9 | |||
3d45b54236 | |||
7f63fe7f0e | |||
bc5dbab9c5 | |||
9910fc7a92 | |||
2356eac118 | |||
0135b8d838 | |||
8ec33ccbf4 | |||
d267316a35 | |||
138da60e55 | |||
4562c2f991 | |||
8009f4ca9b | |||
54dee9702b | |||
94385564bd | |||
0b2309816f | |||
8283272a3b | |||
eb02a47e9a |
@ -12,6 +12,17 @@ subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
subprojects {
|
||||
// TO FIX LIVEKIT ISSUE BY THIS
|
||||
// https://github.com/livekit/client-sdk-flutter/issues/569#issuecomment-2275686786
|
||||
afterEvaluate { project ->
|
||||
if (project.plugins.hasPlugin("com.android.application") ||
|
||||
project.plugins.hasPlugin("com.android.library")) {
|
||||
project.android {
|
||||
compileSdkVersion 34
|
||||
buildToolsVersion "34.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
|
@ -145,14 +145,14 @@ PODS:
|
||||
- Flutter
|
||||
- screen_brightness_ios (0.1.0):
|
||||
- Flutter
|
||||
- SDWebImage (5.19.4):
|
||||
- SDWebImage/Core (= 5.19.4)
|
||||
- SDWebImage/Core (5.19.4)
|
||||
- Sentry/HybridSDK (8.32.0)
|
||||
- sentry_flutter (8.6.0):
|
||||
- SDWebImage (5.19.6):
|
||||
- SDWebImage/Core (= 5.19.6)
|
||||
- SDWebImage/Core (5.19.6)
|
||||
- Sentry/HybridSDK (8.33.0)
|
||||
- sentry_flutter (8.7.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- Sentry/HybridSDK (= 8.32.0)
|
||||
- Sentry/HybridSDK (= 8.33.0)
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
@ -319,9 +319,9 @@ SPEC CHECKSUMS:
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
protocol_handler_ios: a5db8abc38526ee326988b808be621e5fd568990
|
||||
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
||||
SDWebImage: 066c47b573f408f18caa467d71deace7c0f8280d
|
||||
Sentry: 96ae1dcdf01a644bc3a3b1dc279cecaf48a833fb
|
||||
sentry_flutter: 090351ce1ff5f96a4b33ef9455b7e3b28185387d
|
||||
SDWebImage: a79252b60f4678812d94316c91da69ec83089c9f
|
||||
Sentry: 8560050221424aef0bebc8e31eedf00af80f90a6
|
||||
sentry_flutter: e26b861f744e5037a3faf9bf56603ec65d658a61
|
||||
share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||
|
@ -1,7 +1,7 @@
|
||||
import UIKit
|
||||
import Flutter
|
||||
|
||||
@UIApplicationMain
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
|
@ -10,6 +10,7 @@ import 'package:solian/widgets/posts/editor/post_editor_categories_tags.dart';
|
||||
import 'package:solian/widgets/posts/editor/post_editor_date.dart';
|
||||
import 'package:solian/widgets/posts/editor/post_editor_overview.dart';
|
||||
import 'package:solian/widgets/posts/editor/post_editor_publish_zone.dart';
|
||||
import 'package:solian/widgets/posts/editor/post_editor_thumbnail.dart';
|
||||
import 'package:solian/widgets/posts/editor/post_editor_visibility.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
@ -31,6 +32,7 @@ class PostEditorController extends GetxController {
|
||||
Rx<DateTime?> publishedUntil = Rx(null);
|
||||
RxList<int> attachments = RxList<int>.empty(growable: true);
|
||||
RxList<String> tags = RxList<String>.empty(growable: true);
|
||||
Rx<int?> thumbnail = Rx(null);
|
||||
|
||||
RxList<int> visibleUsers = RxList.empty(growable: true);
|
||||
RxList<int> invisibleUsers = RxList.empty(growable: true);
|
||||
@ -125,6 +127,15 @@ class PostEditorController extends GetxController {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> editThumbnail(BuildContext context) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) => PostEditorThumbnailDialog(
|
||||
controller: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void toggleDraftMode() {
|
||||
isDraft.value = !isDraft.value;
|
||||
}
|
||||
@ -165,6 +176,7 @@ class PostEditorController extends GetxController {
|
||||
visibleUsers.clear();
|
||||
invisibleUsers.clear();
|
||||
visibility.value = 0;
|
||||
thumbnail.value = null;
|
||||
publishedAt.value = null;
|
||||
publishedUntil.value = null;
|
||||
isDraft.value = false;
|
||||
@ -196,6 +208,7 @@ class PostEditorController extends GetxController {
|
||||
tags.refresh();
|
||||
attachments.value = value.body['attachments']?.cast<int>() ?? List.empty();
|
||||
attachments.refresh();
|
||||
thumbnail.value = value.body['thumbnail'];
|
||||
|
||||
contentLength.value = contentController.text.length;
|
||||
}
|
||||
@ -246,6 +259,7 @@ class PostEditorController extends GetxController {
|
||||
'title': title,
|
||||
'description': description,
|
||||
'content': contentController.text,
|
||||
'thumbnail': thumbnail.value,
|
||||
'tags': tags.map((x) => {'alias': x}).toList(),
|
||||
'attachments': attachments,
|
||||
'visible_users': visibleUsers,
|
||||
@ -269,6 +283,7 @@ class PostEditorController extends GetxController {
|
||||
contentController.text = value['content'] ?? '';
|
||||
attachments.value = value['attachments'].cast<int>() ?? List.empty();
|
||||
attachments.refresh();
|
||||
thumbnail.value = value['thumbnail'];
|
||||
visibility.value = value['visibility'];
|
||||
isDraft.value = value['is_draft'];
|
||||
if (value['visible_users'] != null) {
|
||||
@ -308,7 +323,8 @@ class PostEditorController extends GetxController {
|
||||
descriptionController.text.isNotEmpty,
|
||||
contentController.text.isNotEmpty,
|
||||
attachments.isNotEmpty,
|
||||
tags.isNotEmpty
|
||||
tags.isNotEmpty,
|
||||
thumbnail.value != null,
|
||||
].any((x) => x);
|
||||
}
|
||||
|
||||
|
@ -52,6 +52,17 @@ class Realm {
|
||||
'is_community': isCommunity,
|
||||
'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 {
|
||||
|
@ -1,4 +1,3 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:solian/models/account.dart';
|
||||
import 'package:solian/models/attachment.dart';
|
||||
import 'package:solian/services.dart';
|
||||
@ -32,7 +31,7 @@ class Sticker {
|
||||
required this.account,
|
||||
});
|
||||
|
||||
String get textPlaceholder => '${pack?.prefix}$alias'.camelCase!;
|
||||
String get textPlaceholder => '${pack?.prefix}$alias';
|
||||
String get textWarpedPlaceholder => ':$textPlaceholder:';
|
||||
|
||||
String get imageUrl => ServiceFinder.buildUrl(
|
||||
|
@ -138,9 +138,7 @@ class AttachmentUploaderController extends GetxController {
|
||||
queueOfUpload[idx].isCompleted = true;
|
||||
}
|
||||
|
||||
queueOfUpload.value = queueOfUpload
|
||||
.where((x) => x.error == null && x.isCompleted)
|
||||
.toList(growable: true);
|
||||
queueOfUpload.removeWhere((x) => x.error == null);
|
||||
_stopProgressSyncTimer();
|
||||
_syncProgress();
|
||||
|
||||
|
@ -8,6 +8,9 @@ class StickerProvider extends GetxController {
|
||||
final RxList<Sticker> availableStickers = RxList.empty(growable: true);
|
||||
|
||||
Future<void> refreshAvailableStickers() async {
|
||||
availableStickers.clear();
|
||||
aliasImageMapping.clear();
|
||||
|
||||
final client = ServiceFinder.configureClient('files');
|
||||
final resp = await client.get(
|
||||
'/stickers/manifest?take=100',
|
||||
@ -20,7 +23,7 @@ class StickerProvider extends GetxController {
|
||||
for (final pack in out) {
|
||||
for (final sticker in (pack.stickers ?? List<Sticker>.empty())) {
|
||||
sticker.pack = pack;
|
||||
aliasImageMapping['${pack.prefix}${sticker.alias}'.camelCase!] =
|
||||
aliasImageMapping[sticker.textPlaceholder.toUpperCase()] =
|
||||
sticker.imageUrl;
|
||||
availableStickers.add(sticker);
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ class _StickerScreenState extends State<StickerScreen> {
|
||||
);
|
||||
return ListTile(
|
||||
title: Text(item.name),
|
||||
subtitle: Text(':${'$prefix${item.alias}'.camelCase}:'),
|
||||
subtitle: Text(item.textWarpedPlaceholder),
|
||||
contentPadding: const EdgeInsets.only(left: 16, right: 14),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@ -174,9 +174,10 @@ class _StickerScreenState extends State<StickerScreen> {
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
children: item.stickers
|
||||
?.map((x) => _buildEmoteEntry(x, item.prefix))
|
||||
.toList() ??
|
||||
children: item.stickers?.map((x) {
|
||||
x.pack = item;
|
||||
return _buildEmoteEntry(x, item.prefix);
|
||||
}).toList() ??
|
||||
List.empty(),
|
||||
);
|
||||
},
|
||||
|
@ -79,8 +79,8 @@ class _SignInPopupState extends State<SignInPopup> with ProtocolListener {
|
||||
onPressed: () {
|
||||
const redirect = 'solink://auth?status=done';
|
||||
launchUrlString(
|
||||
ServiceFinder.buildUrl('passport',
|
||||
'/mfa?redirect_uri=$redirect&ticketId=${e.ticketId}'),
|
||||
ServiceFinder.buildUrl('capital',
|
||||
'/auth/mfa?redirect_uri=$redirect&ticketId=${e.ticketId}'),
|
||||
mode: LaunchMode.inAppWebView,
|
||||
);
|
||||
Navigator.pop(context);
|
||||
|
@ -20,7 +20,6 @@ import 'package:solian/widgets/app_bar_leading.dart';
|
||||
import 'package:solian/widgets/app_bar_title.dart';
|
||||
import 'package:solian/widgets/channel/channel_call_indicator.dart';
|
||||
import 'package:solian/widgets/chat/call/chat_call_action.dart';
|
||||
import 'package:solian/widgets/chat/chat_event.dart';
|
||||
import 'package:solian/widgets/chat/chat_event_list.dart';
|
||||
import 'package:solian/widgets/chat/chat_message_input.dart';
|
||||
import 'package:solian/widgets/current_state_action.dart';
|
||||
@ -39,7 +38,10 @@ class ChannelChatScreen extends StatefulWidget {
|
||||
State<ChannelChatScreen> createState() => _ChannelChatScreenState();
|
||||
}
|
||||
|
||||
class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
class _ChannelChatScreenState extends State<ChannelChatScreen>
|
||||
with WidgetsBindingObserver {
|
||||
DateTime? _isOutOfSyncSince;
|
||||
|
||||
bool _isBusy = false;
|
||||
int? _accountId;
|
||||
|
||||
@ -123,20 +125,38 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
});
|
||||
}
|
||||
|
||||
void _keepUpdateWithServer() {
|
||||
_getOngoingCall();
|
||||
_chatController.getEvents(_channel!, widget.realm);
|
||||
setState(() => _isOutOfSyncSince = null);
|
||||
}
|
||||
|
||||
Event? _messageToReplying;
|
||||
Event? _messageToEditing;
|
||||
|
||||
Widget buildHistoryBody(Event item, {bool isMerged = false}) {
|
||||
return ChatEvent(
|
||||
key: Key('m${item.uuid}'),
|
||||
item: item,
|
||||
isMerged: isMerged,
|
||||
chatController: _chatController,
|
||||
);
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
switch (state) {
|
||||
case AppLifecycleState.resumed:
|
||||
if (_isOutOfSyncSince == null) break;
|
||||
if (DateTime.now().difference(_isOutOfSyncSince!).inSeconds < 60) break;
|
||||
_keepUpdateWithServer();
|
||||
break;
|
||||
case AppLifecycleState.paused:
|
||||
if (mounted) {
|
||||
setState(() => _isOutOfSyncSince = DateTime.now());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
||||
_accountId = Get.find<AuthProvider>().userProfile.value!['id'];
|
||||
|
||||
_chatController = ChatEventController();
|
||||
@ -147,21 +167,10 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
_chatController.getEvents(_channel!, widget.realm);
|
||||
_listenMessages();
|
||||
});
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_isBusy || _channel == null) {
|
||||
return Material(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String title = _channel?.name ?? 'loading'.tr;
|
||||
String? placeholder;
|
||||
|
||||
@ -185,7 +194,8 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
actions: [
|
||||
const BackgroundStateWidget(),
|
||||
Builder(builder: (context) {
|
||||
if (_isBusy) return const SizedBox();
|
||||
if (_isBusy || _channel == null) return const SizedBox();
|
||||
|
||||
return ChatCallButton(
|
||||
realm: _channel!.realm,
|
||||
channel: _channel!,
|
||||
@ -195,6 +205,8 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
IconButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
onPressed: () {
|
||||
if (_channel == null) return;
|
||||
|
||||
AppRouter.instance
|
||||
.pushNamed(
|
||||
'channelDetail',
|
||||
@ -219,66 +231,94 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
if (_ongoingCall != null)
|
||||
ChannelCallIndicator(
|
||||
channel: _channel!,
|
||||
ongoingCall: _ongoingCall!,
|
||||
body: Builder(builder: (context) {
|
||||
if (_isBusy || _channel == null) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
if (_ongoingCall != null)
|
||||
ChannelCallIndicator(
|
||||
channel: _channel!,
|
||||
ongoingCall: _ongoingCall!,
|
||||
),
|
||||
Expanded(
|
||||
child: ChatEventList(
|
||||
scope: widget.realm,
|
||||
channel: _channel!,
|
||||
chatController: _chatController,
|
||||
onEdit: (item) {
|
||||
setState(() => _messageToEditing = item);
|
||||
},
|
||||
onReply: (item) {
|
||||
setState(() => _messageToReplying = item);
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ChatEventList(
|
||||
scope: widget.realm,
|
||||
channel: _channel!,
|
||||
chatController: _chatController,
|
||||
onEdit: (item) {
|
||||
setState(() => _messageToEditing = item);
|
||||
},
|
||||
onReply: (item) {
|
||||
setState(() => _messageToReplying = item);
|
||||
},
|
||||
),
|
||||
),
|
||||
Obx(() {
|
||||
if (_chatController.isLoading.isTrue) {
|
||||
return const LinearProgressIndicator().animate().slideY();
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
}),
|
||||
ClipRect(
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 50, sigmaY: 50),
|
||||
child: SafeArea(
|
||||
child: ChatMessageInput(
|
||||
edit: _messageToEditing,
|
||||
reply: _messageToReplying,
|
||||
realm: widget.realm,
|
||||
placeholder: placeholder,
|
||||
channel: _channel!,
|
||||
onSent: (Event item) {
|
||||
setState(() {
|
||||
_chatController.addPendingEvent(item);
|
||||
});
|
||||
},
|
||||
onReset: () {
|
||||
setState(() {
|
||||
_messageToReplying = null;
|
||||
_messageToEditing = null;
|
||||
});
|
||||
if (_isOutOfSyncSince != null)
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.only(left: 16, right: 8),
|
||||
tileColor: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||
leading: const Icon(Icons.history_toggle_off),
|
||||
title: Text('messageOutOfSync'.tr),
|
||||
subtitle: Text('messageOutOfSyncCaption'.tr),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () {
|
||||
setState(() => _isOutOfSyncSince = null);
|
||||
},
|
||||
),
|
||||
onTap: _isBusy
|
||||
? null
|
||||
: () {
|
||||
_keepUpdateWithServer();
|
||||
},
|
||||
),
|
||||
Obx(() {
|
||||
if (_chatController.isLoading.isTrue) {
|
||||
return const LinearProgressIndicator().animate().slideY();
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
}),
|
||||
ClipRect(
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 50, sigmaY: 50),
|
||||
child: SafeArea(
|
||||
child: ChatMessageInput(
|
||||
edit: _messageToEditing,
|
||||
reply: _messageToReplying,
|
||||
realm: widget.realm,
|
||||
placeholder: placeholder,
|
||||
channel: _channel!,
|
||||
onSent: (Event item) {
|
||||
setState(() {
|
||||
_chatController.addPendingEvent(item);
|
||||
});
|
||||
},
|
||||
onReset: () {
|
||||
setState(() {
|
||||
_messageToReplying = null;
|
||||
_messageToEditing = null;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_subscription?.cancel();
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -208,7 +208,12 @@ class PostCreatePopup extends StatelessWidget {
|
||||
children: [
|
||||
x.icon,
|
||||
const SizedBox(height: 8),
|
||||
Text(x.label),
|
||||
Expanded(
|
||||
child: Text(
|
||||
x.label,
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
),
|
||||
],
|
||||
).paddingAll(18),
|
||||
),
|
||||
|
@ -14,6 +14,7 @@ import 'package:solian/router.dart';
|
||||
import 'package:solian/theme.dart';
|
||||
import 'package:solian/widgets/app_bar_leading.dart';
|
||||
import 'package:solian/widgets/app_bar_title.dart';
|
||||
import 'package:solian/widgets/markdown_text_content.dart';
|
||||
import 'package:solian/widgets/posts/post_item.dart';
|
||||
import 'package:badges/badges.dart' as badges;
|
||||
|
||||
@ -90,6 +91,7 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
|
||||
context.showErrorDialog(resp.bodyString);
|
||||
} else {
|
||||
_editorController.localClear();
|
||||
_editorController.currentClear();
|
||||
AppRouter.instance.pop(resp.body);
|
||||
}
|
||||
|
||||
@ -254,30 +256,51 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (_isBusy)
|
||||
const LinearProgressIndicator().animate().scaleX(),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
child: TextField(
|
||||
maxLines: null,
|
||||
autofocus: true,
|
||||
autocorrect: true,
|
||||
keyboardType: TextInputType.multiline,
|
||||
controller: _editorController.contentController,
|
||||
focusNode: _contentFocusNode,
|
||||
decoration: InputDecoration.collapsed(
|
||||
hintText: 'postContentPlaceholder'.tr,
|
||||
),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
children: [
|
||||
if (_isBusy)
|
||||
const LinearProgressIndicator().animate().scaleX(),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
child: TextField(
|
||||
maxLines: null,
|
||||
autofocus: true,
|
||||
autocorrect: true,
|
||||
keyboardType: TextInputType.multiline,
|
||||
controller: _editorController.contentController,
|
||||
focusNode: _contentFocusNode,
|
||||
decoration: InputDecoration.collapsed(
|
||||
hintText: 'postContentPlaceholder'.tr,
|
||||
),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 120)
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 120)
|
||||
if (SolianTheme.isLargeScreen(context))
|
||||
const VerticalDivider(width: 0.3, thickness: 0.3)
|
||||
.paddingSymmetric(
|
||||
horizontal: 8,
|
||||
),
|
||||
if (SolianTheme.isLargeScreen(context))
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: MarkdownTextContent(
|
||||
content: _editorController.contentController.text,
|
||||
parentId: 'post-editor-preview',
|
||||
).paddingOnly(top: 8),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -453,6 +476,23 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
|
||||
_editorController.editPublishZone(context);
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: Obx(() {
|
||||
return badges.Badge(
|
||||
showBadge:
|
||||
_editorController.thumbnail.value != null,
|
||||
position: badges.BadgePosition.topEnd(
|
||||
top: -4,
|
||||
end: -6,
|
||||
),
|
||||
child: const Icon(Icons.preview),
|
||||
);
|
||||
}),
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
onPressed: () {
|
||||
_editorController.editThumbnail(context);
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: Obx(() {
|
||||
return badges.Badge(
|
||||
@ -475,14 +515,15 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
|
||||
MarkdownToolbar(
|
||||
hideImage: true,
|
||||
useIncludedTextField: false,
|
||||
backgroundColor: Colors.transparent,
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.surface,
|
||||
iconColor: Theme.of(context).colorScheme.onSurface,
|
||||
controller: _editorController.contentController,
|
||||
focusNode: _contentFocusNode,
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(20)),
|
||||
width: 40,
|
||||
).paddingSymmetric(horizontal: 4),
|
||||
).paddingSymmetric(horizontal: 2),
|
||||
],
|
||||
).paddingSymmetric(horizontal: 6, vertical: 8),
|
||||
),
|
||||
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:solian/controllers/chat_events_controller.dart';
|
||||
import 'package:solian/exts.dart';
|
||||
import 'package:solian/providers/theme_switcher.dart';
|
||||
import 'package:solian/router.dart';
|
||||
@ -93,6 +94,18 @@ class _SettingScreenState extends State<SettingScreen> {
|
||||
AppRouter.instance.pushNamed('about');
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
style: const ButtonStyle(
|
||||
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
|
||||
),
|
||||
child: Text('messageHistoryWipe'.tr),
|
||||
onPressed: () {
|
||||
final chatHistory = ChatEventController();
|
||||
chatHistory.initialize().then((_) async {
|
||||
await chatHistory.database.localEvents.wipeLocalEvents();
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
).paddingSymmetric(horizontal: 12, vertical: 8),
|
||||
],
|
||||
|
@ -5,15 +5,15 @@ abstract class ServiceFinder {
|
||||
|
||||
static const String dealerUrl =
|
||||
devFlag ? 'http://localhost:8442' : 'https://api.sn.solsynth.dev';
|
||||
static const String passportUrl =
|
||||
devFlag ? 'http://localhost:8444' : 'https://id.solsynth.dev';
|
||||
static const String capitalUrl =
|
||||
devFlag ? 'http://localhost:8444' : 'https://solsynth.dev';
|
||||
|
||||
static String buildUrl(String serviceName, String? append) {
|
||||
append ??= '';
|
||||
if (serviceName == 'dealer') {
|
||||
return '$dealerUrl$append';
|
||||
} else if (serviceName == 'passport') {
|
||||
return '$passportUrl$append';
|
||||
} else if (serviceName == 'capital') {
|
||||
return '$capitalUrl$append';
|
||||
}
|
||||
return '$dealerUrl/cgi/$serviceName$append';
|
||||
}
|
||||
|
@ -119,6 +119,9 @@ const i18nEnglish = {
|
||||
'postVisibleUsers': 'Visible users',
|
||||
'postInvisibleUsers': 'Invisible users',
|
||||
'postOverview': 'Overview',
|
||||
'postThumbnail': 'Thumbnail',
|
||||
'postThumbnailAttachmentNew': 'Upload thumbnail',
|
||||
'postThumbnailAttachment': 'Attachment serial number',
|
||||
'postPinned': 'Pinned',
|
||||
'postListNews': 'News',
|
||||
'postListShuffle': 'Random',
|
||||
@ -351,7 +354,7 @@ const i18nEnglish = {
|
||||
'attachmentSaved': 'Attachment saved to your system album.',
|
||||
'cropImage': 'Crop Image',
|
||||
'stickerUploader': 'Upload sticker',
|
||||
'stickerUploaderAttachmentNew': 'Upload new attachment',
|
||||
'stickerUploaderAttachmentNew': 'Upload sticker',
|
||||
'stickerUploaderAttachment': 'Attachment serial number',
|
||||
'stickerUploaderPack': 'Sticker pack serial number',
|
||||
'stickerUploaderPackHint':
|
||||
@ -370,4 +373,8 @@ const i18nEnglish = {
|
||||
'callStatusDisconnected': 'Disconnected',
|
||||
'callStatusConnecting': 'Connecting',
|
||||
'callStatusReconnected': 'Reconnecting',
|
||||
'messageOutOfSync': 'May Out of Sync with Server',
|
||||
'messageOutOfSyncCaption':
|
||||
'Since the App has entered the background, there may be a time difference between the message list and the server. Click to Refresh.',
|
||||
'messageHistoryWipe': 'Wipe local message history',
|
||||
};
|
||||
|
@ -113,6 +113,9 @@ const i18nSimplifiedChinese = {
|
||||
'postVisibleUsers': '可见帖子者',
|
||||
'postInvisibleUsers': '隐藏帖子者',
|
||||
'postOverview': '帖子概览',
|
||||
'postThumbnail': '帖子缩略图',
|
||||
'postThumbnailAttachmentNew': '上传附件作为缩略图',
|
||||
'postThumbnailAttachment': '附件序列号',
|
||||
'postPinned': '已置顶',
|
||||
'postEditorModeStory': '发个帖子',
|
||||
'postEditorModeArticle': '撰写文章',
|
||||
@ -322,7 +325,7 @@ const i18nSimplifiedChinese = {
|
||||
'attachmentSaved': '附件已保存到系统相册',
|
||||
'cropImage': '裁剪图片',
|
||||
'stickerUploader': '上传贴图',
|
||||
'stickerUploaderAttachmentNew': '上传附件',
|
||||
'stickerUploaderAttachmentNew': '上传附件作为贴图',
|
||||
'stickerUploaderAttachment': '附件序列号',
|
||||
'stickerUploaderPack': '贴图包序号',
|
||||
'stickerUploaderPackHint': '没有该序号?请转到我们的创作者平台创建一个贴图包。',
|
||||
@ -337,4 +340,7 @@ const i18nSimplifiedChinese = {
|
||||
'callStatusDisconnected': '已断开',
|
||||
'callStatusConnecting': '连接中',
|
||||
'callStatusReconnected': '重连中',
|
||||
'messageOutOfSync': '消息可能与服务器脱节',
|
||||
'messageOutOfSyncCaption': '由于 App 进入后台,消息列表可能与服务器存在时差,点击刷新。',
|
||||
'messageHistoryWipe': '清除消息记录',
|
||||
};
|
||||
|
@ -70,7 +70,7 @@ class _AttachmentFullScreenState extends State<AttachmentFullScreen> {
|
||||
'/attachments/${widget.item.id}',
|
||||
);
|
||||
|
||||
if (PlatformInfo.isWeb) {
|
||||
if (PlatformInfo.isWeb || PlatformInfo.isDesktop) {
|
||||
await launchUrlString(url);
|
||||
return;
|
||||
}
|
||||
|
@ -244,6 +244,7 @@ class _AttachmentItemVideoState extends State<_AttachmentItemVideo> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_showContent = widget.autoload;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -3,7 +3,7 @@ import 'dart:ui';
|
||||
|
||||
import 'package:carousel_slider/carousel_slider.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:get/get.dart';
|
||||
import 'package:solian/models/attachment.dart';
|
||||
|
@ -159,8 +159,9 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
||||
}
|
||||
var track = await LocalVideoTrack.createScreenShareTrack(
|
||||
ScreenShareCaptureOptions(
|
||||
captureScreenAudio: true,
|
||||
sourceId: source.id,
|
||||
maxFrameRate: 15.0,
|
||||
maxFrameRate: 30.0,
|
||||
),
|
||||
);
|
||||
await _participant.publishVideoTrack(track);
|
||||
@ -174,6 +175,7 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
||||
var track = await LocalVideoTrack.createScreenShareTrack(
|
||||
const ScreenShareCaptureOptions(
|
||||
useiOSBroadcastExtension: true,
|
||||
captureScreenAudio: true,
|
||||
maxFrameRate: 30.0,
|
||||
),
|
||||
);
|
||||
|
@ -23,16 +23,19 @@ class ChatEventMessage extends StatelessWidget {
|
||||
final body = EventMessageBody.fromJson(item.body);
|
||||
final hasAttachment = body.attachments?.isNotEmpty ?? false;
|
||||
|
||||
if (body.text.isEmpty && hasAttachment) {
|
||||
if (body.text.isEmpty &&
|
||||
hasAttachment &&
|
||||
!isContentPreviewing &&
|
||||
!isQuote) {
|
||||
final unFocusColor =
|
||||
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
|
||||
return Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.attachment,
|
||||
size: 18,
|
||||
Icons.file_copy,
|
||||
size: 15,
|
||||
color: unFocusColor,
|
||||
).paddingOnly(right: 6),
|
||||
).paddingOnly(right: 5),
|
||||
Text(
|
||||
'attachmentHint'.trParams(
|
||||
{'count': body.attachments?.length.toString() ?? 0.toString()},
|
||||
|
@ -129,12 +129,14 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
|
||||
|
||||
client = auth.configureClient('messaging');
|
||||
|
||||
if (_textController.text.trim().isEmpty && _attachments.isEmpty) return;
|
||||
|
||||
const uuid = Uuid();
|
||||
final payload = {
|
||||
'uuid': uuid.v4(),
|
||||
'type': _editTo == null ? 'messages.new' : 'messages.edit',
|
||||
'body': {
|
||||
'text': _textController.text,
|
||||
'text': _textController.text.trim(),
|
||||
'algorithm': 'plain',
|
||||
'attachments': List.from(_attachments),
|
||||
'related_users': [
|
||||
@ -239,7 +241,7 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
|
||||
if (suggestion.type == 'emotes') {
|
||||
insertText = suggestion.content;
|
||||
startText = replaceText.replaceFirstMapped(
|
||||
RegExp(r':(?:([a-z0-9_+-]+)~)?([a-z0-9_+-]+)$'),
|
||||
RegExp(r':(?:([-\w]+)~)?([-\w]+)$'),
|
||||
(Match m) => insertText,
|
||||
);
|
||||
}
|
||||
@ -247,7 +249,7 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
|
||||
if (suggestion.type == 'users') {
|
||||
insertText = suggestion.content;
|
||||
startText = replaceText.replaceFirstMapped(
|
||||
RegExp(r'(?:\s|^)@([a-z0-9_+-]+)$'),
|
||||
RegExp(r'(?:\s|^)@([-\w]+)$'),
|
||||
(Match m) => insertText,
|
||||
);
|
||||
}
|
||||
@ -349,15 +351,17 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
|
||||
final searchText = _textController.text
|
||||
.substring(0, _textController.selection.baseOffset);
|
||||
|
||||
final emojiMatch =
|
||||
RegExp(r':(?:([a-z0-9_+-]+)~)?([a-z0-9_+-]+)$')
|
||||
.firstMatch(searchText);
|
||||
final emojiMatch = RegExp(r':(?:([-\w]+)~)?([-\w]+)$')
|
||||
.firstMatch(searchText);
|
||||
if (emojiMatch != null) {
|
||||
final StickerProvider stickers = Get.find();
|
||||
final emoteSearch = emojiMatch[2]!;
|
||||
return stickers.availableStickers
|
||||
.where((x) =>
|
||||
x.textWarpedPlaceholder.contains(emoteSearch))
|
||||
.where(
|
||||
(x) => x.textWarpedPlaceholder
|
||||
.toUpperCase()
|
||||
.contains(emoteSearch.toUpperCase()),
|
||||
)
|
||||
.map(
|
||||
(x) => ChatMessageSuggestion(
|
||||
type: 'emotes',
|
||||
@ -379,8 +383,8 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
|
||||
.toList();
|
||||
}
|
||||
|
||||
final userMatch = RegExp(r'(?:\s|^)@([a-z0-9_+-]+)$')
|
||||
.firstMatch(searchText);
|
||||
final userMatch =
|
||||
RegExp(r'(?:\s|^)@([-\w]+)$').firstMatch(searchText);
|
||||
if (userMatch != null) {
|
||||
final userSearch = userMatch[1]!.toLowerCase();
|
||||
final AuthProvider auth = Get.find();
|
||||
|
@ -24,9 +24,9 @@ class MarkdownTextContent extends StatelessWidget {
|
||||
});
|
||||
|
||||
Widget _buildContent(BuildContext context) {
|
||||
final emojiMatch = RegExp(r':([a-z0-9_+-]+):').allMatches(content);
|
||||
final isOnlyEmoji =
|
||||
content.replaceAll(RegExp(r':([a-z0-9_+-]+):'), '').trim().isEmpty;
|
||||
final emojiRegex = RegExp(r':([-\w]+):');
|
||||
final emojiMatch = emojiRegex.allMatches(content);
|
||||
final isOnlyEmoji = content.replaceAll(emojiRegex, '').trim().isEmpty;
|
||||
|
||||
return Markdown(
|
||||
shrinkWrap: true,
|
||||
@ -88,20 +88,40 @@ class MarkdownTextContent extends StatelessWidget {
|
||||
final segments = url.replaceFirst('solink://', '').split('/');
|
||||
switch (segments[0]) {
|
||||
case 'stickers':
|
||||
double radius = 8;
|
||||
final StickerProvider sticker = Get.find();
|
||||
url = sticker.aliasImageMapping[segments[1]]!;
|
||||
url = sticker.aliasImageMapping[segments[1].toUpperCase()]!;
|
||||
if (emojiMatch.length <= 1 && isOnlyEmoji) {
|
||||
width = 112;
|
||||
height = 112;
|
||||
width = 128;
|
||||
height = 128;
|
||||
} else if (emojiMatch.length <= 3 && isOnlyEmoji) {
|
||||
width = 56;
|
||||
height = 56;
|
||||
width = 32;
|
||||
height = 32;
|
||||
} else {
|
||||
width = 28;
|
||||
height = 28;
|
||||
radius = 4;
|
||||
width = 16;
|
||||
height = 16;
|
||||
}
|
||||
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':
|
||||
const radius = BorderRadius.all(Radius.circular(8));
|
||||
return LimitedBox(
|
||||
@ -161,12 +181,12 @@ class _UserNameCardInlineSyntax extends InlineSyntax {
|
||||
}
|
||||
|
||||
class _CustomEmoteInlineSyntax extends InlineSyntax {
|
||||
_CustomEmoteInlineSyntax() : super(r':([a-z0-9_+-]+):');
|
||||
_CustomEmoteInlineSyntax() : super(r':([-\w]+):');
|
||||
|
||||
@override
|
||||
bool onMatch(markdown.InlineParser parser, Match match) {
|
||||
final StickerProvider sticker = Get.find();
|
||||
final alias = match[1]!;
|
||||
final alias = match[1]!.toUpperCase();
|
||||
if (sticker.aliasImageMapping[alias] == null) {
|
||||
parser.advanceBy(1);
|
||||
return false;
|
||||
|
@ -4,17 +4,17 @@ import 'package:get/utils.dart';
|
||||
abstract class AppNavigation {
|
||||
static List<AppNavigationDestination> destinations = [
|
||||
AppNavigationDestination(
|
||||
icon: const Icon(Icons.home),
|
||||
icon: Icons.home,
|
||||
label: 'home'.tr,
|
||||
page: 'home',
|
||||
),
|
||||
AppNavigationDestination(
|
||||
icon: const Icon(Icons.workspaces),
|
||||
icon: Icons.workspaces,
|
||||
label: 'realms'.tr,
|
||||
page: 'realms',
|
||||
),
|
||||
AppNavigationDestination(
|
||||
icon: const Icon(Icons.forum),
|
||||
icon: Icons.forum,
|
||||
label: 'channelTypeDirect'.tr,
|
||||
page: 'chat',
|
||||
),
|
||||
@ -25,7 +25,7 @@ abstract class AppNavigation {
|
||||
}
|
||||
|
||||
class AppNavigationDestination {
|
||||
final Widget icon;
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final String page;
|
||||
|
||||
|
@ -1,18 +1,19 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:solian/models/account_status.dart';
|
||||
import 'package:solian/providers/account_status.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:solian/providers/content/channel.dart';
|
||||
import 'package:solian/providers/relation.dart';
|
||||
import 'package:solian/router.dart';
|
||||
import 'package:solian/shells/root_shell.dart';
|
||||
import 'package:solian/theme.dart';
|
||||
import 'package:solian/widgets/account/account_avatar.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:badges/badges.dart' as badges;
|
||||
import 'package:solian/widgets/navigation/app_navigation_regions.dart';
|
||||
|
||||
class AppNavigationDrawer extends StatefulWidget {
|
||||
final String? routeName;
|
||||
@ -24,12 +25,8 @@ class AppNavigationDrawer extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
|
||||
int? _selectedIndex = 0;
|
||||
|
||||
AccountStatus? _accountStatus;
|
||||
|
||||
late final ChannelProvider _channels;
|
||||
|
||||
Future<void> _getStatus() async {
|
||||
final StatusProvider provider = Get.find();
|
||||
|
||||
@ -41,201 +38,165 @@ 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() {
|
||||
rootScaffoldKey.currentState!.closeDrawer();
|
||||
}
|
||||
|
||||
Widget _buildSettingButton() {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.settings),
|
||||
onPressed: () {
|
||||
AppRouter.instance.pushNamed('settings');
|
||||
setState(() => _selectedIndex = null);
|
||||
_closeDrawer();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_channels = Get.find();
|
||||
_detectSelectedIndex();
|
||||
_getStatus();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_detectSelectedIndex();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final AuthProvider auth = Get.find();
|
||||
|
||||
return NavigationDrawer(
|
||||
return Drawer(
|
||||
backgroundColor:
|
||||
SolianTheme.isLargeScreen(context) ? Colors.transparent : null,
|
||||
selectedIndex: _selectedIndex,
|
||||
onDestinationSelected: (idx) {
|
||||
setState(() => _selectedIndex = idx);
|
||||
AppRouter.instance.goNamed(AppNavigation.destinations[idx].page);
|
||||
_closeDrawer();
|
||||
},
|
||||
children: [
|
||||
Obx(() {
|
||||
if (auth.isAuthorized.isFalse || auth.userProfile.value == null) {
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 28),
|
||||
leading: const Icon(Icons.account_circle),
|
||||
title: Text('guest'.tr),
|
||||
subtitle: Text('unsignedIn'.tr),
|
||||
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!,
|
||||
child: SafeArea(
|
||||
bottom: false,
|
||||
child: Column(
|
||||
children: [
|
||||
Obx(() {
|
||||
if (auth.isAuthorized.isFalse || auth.userProfile.value == null) {
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 28),
|
||||
leading: const Icon(Icons.account_circle),
|
||||
title: Text('guest'.tr),
|
||||
subtitle: Text('unsignedIn'.tr),
|
||||
onTap: () {
|
||||
AppRouter.instance.goNamed('account');
|
||||
_closeDrawer();
|
||||
},
|
||||
);
|
||||
return Text(
|
||||
info.$3,
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.only(left: 20, right: 20),
|
||||
title: Text(
|
||||
auth.userProfile.value!['nick'],
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.fade,
|
||||
);
|
||||
},
|
||||
),
|
||||
leading: Obx(() {
|
||||
final statusBadgeColor = _accountStatus != null
|
||||
? StatusProvider.determineStatus(
|
||||
),
|
||||
subtitle: Builder(
|
||||
builder: (context) {
|
||||
if (_accountStatus == null) {
|
||||
return Text('loading'.tr);
|
||||
}
|
||||
final info = StatusProvider.determineStatus(
|
||||
_accountStatus!,
|
||||
).$2
|
||||
: Colors.grey;
|
||||
|
||||
final RelationshipProvider relations = Get.find();
|
||||
final accountNotifications = relations.friendRequestCount.value;
|
||||
|
||||
return badges.Badge(
|
||||
badgeContent: Text(
|
||||
accountNotifications.toString(),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
);
|
||||
return Text(
|
||||
info.$3,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.fade,
|
||||
);
|
||||
},
|
||||
),
|
||||
showBadge: accountNotifications > 0,
|
||||
position: badges.BadgePosition.topEnd(
|
||||
top: -10,
|
||||
end: -6,
|
||||
),
|
||||
child: badges.Badge(
|
||||
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();
|
||||
}
|
||||
leading: Obx(() {
|
||||
final statusBadgeColor = _accountStatus != null
|
||||
? StatusProvider.determineStatus(
|
||||
_accountStatus!,
|
||||
).$2
|
||||
: Colors.grey;
|
||||
|
||||
final selfId = auth.userProfile.value!['id'];
|
||||
final RelationshipProvider relations = Get.find();
|
||||
final accountNotifications =
|
||||
relations.friendRequestCount.value;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Theme(
|
||||
data: Theme.of(context)
|
||||
.copyWith(dividerColor: Colors.transparent),
|
||||
child: ExpansionTile(
|
||||
title: Text('channels'.tr),
|
||||
tilePadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
children: [
|
||||
Obx(
|
||||
() => SizedBox(
|
||||
height: 360,
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () => _channels.refreshAvailableChannel(),
|
||||
child: ChannelListWidget(
|
||||
channels: _channels.groupChannels,
|
||||
selfId: selfId,
|
||||
isDense: true,
|
||||
useReplace: true,
|
||||
onSelected: (_) {
|
||||
setState(() => _selectedIndex = null);
|
||||
_closeDrawer();
|
||||
},
|
||||
),
|
||||
),
|
||||
return badges.Badge(
|
||||
badgeContent: Text(
|
||||
accountNotifications.toString(),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
showBadge: accountNotifications > 0,
|
||||
position: badges.BadgePosition.topEnd(
|
||||
top: -10,
|
||||
end: -6,
|
||||
),
|
||||
child: badges.Badge(
|
||||
showBadge: _accountStatus != null,
|
||||
badgeStyle:
|
||||
badges.BadgeStyle(badgeColor: statusBadgeColor),
|
||||
position: badges.BadgePosition.bottomEnd(
|
||||
bottom: 0,
|
||||
end: -2,
|
||||
),
|
||||
child: AccountAvatar(
|
||||
content: auth.userProfile.value!['avatar'],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
onTap: () {
|
||||
AppRouter.instance.goNamed('account');
|
||||
_closeDrawer();
|
||||
},
|
||||
onLongPress: () {
|
||||
showModalBottomSheet(
|
||||
useRootNavigator: true,
|
||||
context: context,
|
||||
builder: (context) => AccountStatusAction(
|
||||
currentStatus: _accountStatus!.status,
|
||||
),
|
||||
).then((val) {
|
||||
if (val == true) _getStatus();
|
||||
});
|
||||
},
|
||||
);
|
||||
}).paddingSymmetric(vertical: 8),
|
||||
const Divider(thickness: 0.3, height: 1),
|
||||
Column(
|
||||
children: AppNavigation.destinations
|
||||
.map(
|
||||
(e) => ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
),
|
||||
leading: Icon(e.icon, size: 20).paddingAll(2),
|
||||
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();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
const Divider(thickness: 0.3, height: 1),
|
||||
Column(
|
||||
children: [
|
||||
ListTile(
|
||||
minTileHeight: 0,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
),
|
||||
leading: const Icon(Icons.settings, size: 20).paddingAll(2),
|
||||
title: Text('settings'.tr),
|
||||
onTap: () {
|
||||
AppRouter.instance.pushNamed('settings');
|
||||
_closeDrawer();
|
||||
},
|
||||
),
|
||||
],
|
||||
).paddingOnly(
|
||||
top: 8,
|
||||
bottom: math.max(8, MediaQuery.of(context).padding.bottom),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
89
lib/widgets/navigation/app_navigation_regions.dart
Normal file
89
lib/widgets/navigation/app_navigation_regions.dart
Normal file
@ -0,0 +1,89 @@
|
||||
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,
|
||||
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)),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
96
lib/widgets/posts/editor/post_editor_thumbnail.dart
Normal file
96
lib/widgets/posts/editor/post_editor_thumbnail.dart
Normal file
@ -0,0 +1,96 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:solian/controllers/post_editor_controller.dart';
|
||||
import 'package:solian/widgets/attachments/attachment_editor.dart';
|
||||
|
||||
class PostEditorThumbnailDialog extends StatefulWidget {
|
||||
final PostEditorController controller;
|
||||
|
||||
const PostEditorThumbnailDialog({super.key, required this.controller});
|
||||
|
||||
@override
|
||||
State<PostEditorThumbnailDialog> createState() =>
|
||||
_PostEditorThumbnailDialogState();
|
||||
}
|
||||
|
||||
class _PostEditorThumbnailDialogState extends State<PostEditorThumbnailDialog> {
|
||||
final TextEditingController _attachmentController = TextEditingController();
|
||||
|
||||
void _promptUploadNewAttachment() {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => AttachmentEditorPopup(
|
||||
usage: 'i.attachment',
|
||||
singleMode: true,
|
||||
imageOnly: true,
|
||||
autoUpload: true,
|
||||
onAdd: (value) {
|
||||
setState(() {
|
||||
_attachmentController.text = value.toString();
|
||||
});
|
||||
|
||||
widget.controller.thumbnail.value = value;
|
||||
},
|
||||
initialAttachments: const [],
|
||||
onRemove: (_) {},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_attachmentController.text =
|
||||
widget.controller.thumbnail.value?.toString() ?? '';
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_attachmentController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('postThumbnail'.tr),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
title: Text('postThumbnailAttachmentNew'.tr),
|
||||
contentPadding: const EdgeInsets.only(left: 16, right: 13),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||
),
|
||||
onTap: () {
|
||||
_promptUploadNewAttachment();
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: _attachmentController,
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
border: const OutlineInputBorder(),
|
||||
prefixText: '#',
|
||||
labelText: 'postThumbnailAttachment'.tr,
|
||||
),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
widget.controller.thumbnail.value =
|
||||
int.tryParse(_attachmentController.text);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text('confirm'.tr),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -43,7 +43,7 @@ class _PostActionState extends State<PostAction> {
|
||||
final box = context.findRenderObject() as RenderBox?;
|
||||
if ((PlatformInfo.isAndroid || PlatformInfo.isIOS) && !noUri) {
|
||||
await Share.shareUri(
|
||||
Uri.parse('https://sn.solsynth.dev/posts/view/${widget.item.id}'),
|
||||
Uri.parse('https://solsynth.dev/posts/${widget.item.id}'),
|
||||
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
||||
);
|
||||
} else {
|
||||
@ -57,7 +57,7 @@ class _PostActionState extends State<PostAction> {
|
||||
'username': widget.item.author.nick,
|
||||
'content':
|
||||
'${extraContent.join('\n')}${isExtraNotEmpty ? '\n\n' : ''}${widget.item.body['content'] ?? 'no content'}',
|
||||
'link': 'https://sn.solsynth.dev/posts/view/${widget.item.id}',
|
||||
'link': 'https://solsynth.dev/posts/${widget.item.id}',
|
||||
}),
|
||||
subject: 'postShareSubject'.trParams({
|
||||
'username': widget.item.author.nick,
|
||||
|
@ -76,6 +76,30 @@ class _PostItemState extends State<PostItem> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildThumbnail() {
|
||||
if (widget.item.body['thumbnail'] == null) return const SizedBox();
|
||||
const radius = BorderRadius.all(Radius.circular(8));
|
||||
return AspectRatio(
|
||||
aspectRatio: 16 / 9,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 0.3,
|
||||
),
|
||||
borderRadius: radius,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: radius,
|
||||
child: AttachmentSelfContainedEntry(
|
||||
id: widget.item.body['thumbnail'],
|
||||
parentId: 'p${item.id}-thumbnail',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -283,6 +307,7 @@ class _PostItemState extends State<PostItem> {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildThumbnail().paddingSymmetric(horizontal: 12, vertical: 4),
|
||||
_buildHeader().paddingSymmetric(horizontal: 12),
|
||||
_buildHeaderDivider().paddingSymmetric(horizontal: 12),
|
||||
Stack(
|
||||
@ -356,6 +381,7 @@ class _PostItemState extends State<PostItem> {
|
||||
closedBuilder: (_, openContainer) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildThumbnail().paddingSymmetric(horizontal: 12, vertical: 4),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -390,7 +416,7 @@ class _PostItemState extends State<PostItem> {
|
||||
setState(() => _contentHeight = size.height);
|
||||
},
|
||||
child: MarkdownTextContent(
|
||||
parentId: 'p${item.id}',
|
||||
parentId: 'p${item.id}-embed',
|
||||
content: item.body['content'],
|
||||
isSelectable: widget.isContentSelectable,
|
||||
).paddingOnly(left: 12, right: 8),
|
||||
@ -436,6 +462,7 @@ class _PostItemState extends State<PostItem> {
|
||||
left: 16,
|
||||
),
|
||||
AttachmentList(
|
||||
flatMaxHeight: MediaQuery.of(context).size.width,
|
||||
parentId: widget.item.id.toString(),
|
||||
attachmentsId: attachments,
|
||||
isGrid: attachments.length > 1,
|
||||
|
@ -107,11 +107,11 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- screen_brightness_macos (0.1.0):
|
||||
- FlutterMacOS
|
||||
- Sentry/HybridSDK (8.32.0)
|
||||
- sentry_flutter (8.6.0):
|
||||
- Sentry/HybridSDK (8.33.0)
|
||||
- sentry_flutter (8.7.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- Sentry/HybridSDK (= 8.32.0)
|
||||
- Sentry/HybridSDK (= 8.33.0)
|
||||
- share_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
@ -252,8 +252,8 @@ SPEC CHECKSUMS:
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
protocol_handler_macos: d10a6c01d6373389ffd2278013ab4c47ed6d6daa
|
||||
screen_brightness_macos: 2d6d3af2165592d9a55ffcd95b7550970e41ebda
|
||||
Sentry: 96ae1dcdf01a644bc3a3b1dc279cecaf48a833fb
|
||||
sentry_flutter: 090351ce1ff5f96a4b33ef9455b7e3b28185387d
|
||||
Sentry: 8560050221424aef0bebc8e31eedf00af80f90a6
|
||||
sentry_flutter: e26b861f744e5037a3faf9bf56603ec65d658a61
|
||||
share_plus: 36537c04ce0c3e3f5bd297ce4318b6d5ee5fd6cf
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
|
137
pubspec.lock
137
pubspec.lock
@ -5,10 +5,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7"
|
||||
sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "67.0.0"
|
||||
version: "72.0.0"
|
||||
_flutterfire_internals:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -17,14 +17,19 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.40"
|
||||
_macros:
|
||||
dependency: transitive
|
||||
description: dart
|
||||
source: sdk
|
||||
version: "0.3.2"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d"
|
||||
sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.4.1"
|
||||
version: "6.7.0"
|
||||
animations:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -117,18 +122,18 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7"
|
||||
sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.11"
|
||||
version: "2.4.12"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
sha256: e3c79f69a64bdfcd8a776a3c28db4eb6e3fb5356d013ae5eb2e52007706d5dbe
|
||||
sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.3.1"
|
||||
version: "7.3.2"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -173,10 +178,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: carousel_slider
|
||||
sha256: "9c695cc963bf1d04a47bd6021f68befce8970bcd61d24938e1fb0918cf5d9c42"
|
||||
sha256: "7b006ec356205054af5beaef62e2221160ea36b90fb70a35e4deacd49d0349ae"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.1"
|
||||
version: "5.0.0"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -226,7 +231,7 @@ packages:
|
||||
source: hosted
|
||||
version: "4.10.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: collection
|
||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||
@ -237,10 +242,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: connectivity_plus
|
||||
sha256: "3e7d1d9dbae40ae82cbe6c23c518f0c4ffe32764ee9749b9a99d32cbac8734f6"
|
||||
sha256: "2056db5241f96cdc0126bd94459fc4cdc13876753768fc7a31c425e50a7177d0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.4"
|
||||
version: "6.0.5"
|
||||
connectivity_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -325,10 +330,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: device_info_plus
|
||||
sha256: "93429694c9253d2871b3af80cf11b3cbb5c65660d402ed7bf69854ce4a089f82"
|
||||
sha256: a7fd703482b391a87d60b6061d04dfdeab07826b96f9abd8f5ed98068acc0074
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.1.1"
|
||||
version: "10.1.2"
|
||||
device_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -405,10 +410,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: "824f5b9f389bfc4dddac3dea76cd70c51092d9dff0b2ece7ef4f53db8547d258"
|
||||
sha256: "825aec673606875c33cd8d3c4083f1a3c3999015a84178b317b7ef396b7384f3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.0.6"
|
||||
version: "8.0.7"
|
||||
file_selector_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -634,10 +639,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_markdown
|
||||
sha256: "2e8a801b1ded5ea001a4529c97b1f213dcb11c6b20668e081cafb23468593514"
|
||||
sha256: a23c41ee57573e62fc2190a1f36a0480c4d90bde3a8a8d7126e5d5992fb53fb7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.3"
|
||||
version: "0.7.3+1"
|
||||
flutter_markdown_selectionarea:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -740,10 +745,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_webrtc
|
||||
sha256: f46bd76cef6e8d787dc707d0c591f0e89c912a2970c7b5e68a55b9cca1bdde4c
|
||||
sha256: "67faa07cf49392b50b1aa14590a83caa64d2109345fabd29899dcd8da8538348"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.6"
|
||||
version: "0.11.6+hotfix.1"
|
||||
font_awesome_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -788,10 +793,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: go_router
|
||||
sha256: d380de0355788c5c784fe9f81b43fc833b903991c25ecc4e2a416a67faefa722
|
||||
sha256: ddc16d34b0d74cb313986918c0f0885a7ba2fc24d8fb8419de75f0015144ccfe
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.2"
|
||||
version: "14.2.3"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -876,10 +881,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_android
|
||||
sha256: "3fe99e3a459b969f657565a853353bdea7e3d3cae34f7dd124836267d426ec87"
|
||||
sha256: c0e72ecd170b00a5590bb71238d57dc8ad22ee14c60c6b0d1a4e05cafbc5db4b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.12+10"
|
||||
version: "0.8.12+11"
|
||||
image_picker_for_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -972,18 +977,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
|
||||
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.4"
|
||||
version: "10.0.5"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
|
||||
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
version: "3.0.5"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1032,6 +1037,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
macros:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: macros
|
||||
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.2-main.4"
|
||||
markdown:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1060,10 +1073,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.0"
|
||||
version: "0.11.1"
|
||||
media_kit:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1140,10 +1153,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
|
||||
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.0"
|
||||
version: "1.15.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1188,10 +1201,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: "4de6c36df77ffbcef0a5aefe04669d33f2d18397fea228277b852a2d4e58e860"
|
||||
sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.0.1"
|
||||
version: "8.0.2"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1276,10 +1289,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: b29a799ca03be9f999aa6c39f7de5209482d638e6f857f6b93b0875c618b7e54
|
||||
sha256: eaf2a1ec4472775451e88ca6a7b86559ef2f1d1ed903942ed135e38ea0097dca
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "12.0.7"
|
||||
version: "12.0.8"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1300,10 +1313,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
sha256: "48d4fcf201a1dad93ee869ab0d4101d084f49136ec82a8a06ed9cfeacab9fd20"
|
||||
sha256: fe0ffe274d665be8e34f9c59705441a7d248edebbe5d9e3ec2665f88b79358ea
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.1"
|
||||
version: "4.2.2"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1548,18 +1561,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sentry
|
||||
sha256: "76ad4fab90ff82427c26939bd79dc4df345a081e2b1cd5954b947e340b9af9a5"
|
||||
sha256: "0f787e27ff617e4f88f7074977240406a9c5509444bac64a4dfa5b3200fb5632"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.6.0"
|
||||
version: "8.7.0"
|
||||
sentry_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sentry_flutter
|
||||
sha256: a7c92014701093a7c0a373e1a47c54ec428d8468d8bf2b793fee7ea1b085c21b
|
||||
sha256: fbbb47d72ccca48be25bf3c2ced6ab6e872991af3a0ba78e54be8d138f2e053f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.6.0"
|
||||
version: "8.7.0"
|
||||
share_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1588,26 +1601,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "041be4d9d2dc6079cf342bc8b761b03787e3b71192d658220a56cac9c04a0294"
|
||||
sha256: a7e8467e9181cef109f601e3f65765685786c1a738a83d7fbbde377589c0d974
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
version: "2.3.1"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "671e7a931f55a08aa45be2a13fe7247f2a41237897df434b30d2012388191833"
|
||||
sha256: "776786cff96324851b656777648f36ac772d88bc4c669acff97b7fce5de3c849"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.0"
|
||||
version: "2.5.1"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "2ba0510d3017f91655b7543e9ee46d48619de2a2af38e5c790423f7007c7ccc1"
|
||||
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
version: "2.4.1"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1620,18 +1633,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: "59dc807b94d29d52ddbb1b3c0d3b9d0a67fc535a64e62a5542c8db0513fcb6c2"
|
||||
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
version: "2.4.2"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "398084b47b7f92110683cac45c6dc4aae853db47e470e5ddcd52cab7f7196ab2"
|
||||
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
version: "2.4.1"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1793,10 +1806,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
|
||||
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
version: "0.7.2"
|
||||
timeago:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1873,10 +1886,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
|
||||
sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
version: "3.2.0"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1897,10 +1910,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: a36e2d7981122fa185006b216eb6b5b97ede3f9a54b7a511bc966971ab98d049
|
||||
sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
version: "2.3.3"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1929,10 +1942,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
|
||||
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.1"
|
||||
version: "14.2.4"
|
||||
volume_controller:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -2038,5 +2051,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.4.0 <4.0.0"
|
||||
dart: ">=3.5.0-259.0.dev <4.0.0"
|
||||
flutter: ">=3.22.0"
|
||||
|
@ -2,7 +2,7 @@ name: solian
|
||||
description: "The Solar Network App"
|
||||
publish_to: "none"
|
||||
|
||||
version: 1.2.1+8
|
||||
version: 1.2.1+13
|
||||
|
||||
environment:
|
||||
sdk: ">=3.3.4 <4.0.0"
|
||||
@ -19,7 +19,7 @@ dependencies:
|
||||
flutter_markdown: ^0.7.1
|
||||
flutter_animate: ^4.5.0
|
||||
flutter_secure_storage: ^9.2.1
|
||||
carousel_slider: ^4.2.1
|
||||
carousel_slider: ^5.0.0
|
||||
url_launcher: ^6.2.6
|
||||
infinite_scroll_pagination: ^4.0.0
|
||||
image_picker: ^1.1.1
|
||||
@ -68,6 +68,7 @@ dependencies:
|
||||
async: ^2.11.0
|
||||
field_suggestion: ^0.2.5
|
||||
flutter_typeahead: ^5.2.0
|
||||
collection: ^1.18.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
Reference in New Issue
Block a user