Compare commits
3 Commits
1153fbdeee
...
289aa17a7a
Author | SHA1 | Date | |
---|---|---|---|
289aa17a7a | |||
93f41bb523 | |||
09ec9d4a0c |
@ -44,7 +44,9 @@ class SnNetworkProvider {
|
|||||||
settings: const TalkerDioLoggerSettings(
|
settings: const TalkerDioLoggerSettings(
|
||||||
printRequestHeaders: false,
|
printRequestHeaders: false,
|
||||||
printResponseHeaders: false,
|
printResponseHeaders: false,
|
||||||
printResponseMessage: true,
|
printResponseMessage: false,
|
||||||
|
printResponseData: false,
|
||||||
|
printRequestData: false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -95,8 +95,9 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [],
|
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [],
|
||||||
);
|
);
|
||||||
final beforeId = config.prefs.getInt('int_last_publisher_id');
|
final beforeId = config.prefs.getInt('int_last_publisher_id');
|
||||||
_writeController
|
_writeController.setPublisher(
|
||||||
.setPublisher(_publishers?.where((ele) => ele.id == beforeId).firstOrNull ?? _publishers?.firstOrNull);
|
_publishers?.where((ele) => ele.id == beforeId).firstOrNull ??
|
||||||
|
_publishers?.firstOrNull);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
@ -125,7 +126,11 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
|
|
||||||
final HotKey _pasteHotKey = HotKey(
|
final HotKey _pasteHotKey = HotKey(
|
||||||
key: PhysicalKeyboardKey.keyV,
|
key: PhysicalKeyboardKey.keyV,
|
||||||
modifiers: [(!kIsWeb && Platform.isMacOS) ? HotKeyModifier.meta : HotKeyModifier.control],
|
modifiers: [
|
||||||
|
(!kIsWeb && Platform.isMacOS)
|
||||||
|
? HotKeyModifier.meta
|
||||||
|
: HotKeyModifier.control
|
||||||
|
],
|
||||||
scope: HotKeyScope.inapp,
|
scope: HotKeyScope.inapp,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -232,7 +237,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
if (widget.extraProps != null) {
|
if (widget.extraProps != null) {
|
||||||
_writeController.contentController.text = widget.extraProps!.text ?? '';
|
_writeController.contentController.text = widget.extraProps!.text ?? '';
|
||||||
_writeController.titleController.text = widget.extraProps!.title ?? '';
|
_writeController.titleController.text = widget.extraProps!.title ?? '';
|
||||||
_writeController.descriptionController.text = widget.extraProps!.description ?? '';
|
_writeController.descriptionController.text =
|
||||||
|
widget.extraProps!.description ?? '';
|
||||||
_writeController.addAttachments(widget.extraProps!.attachments ?? []);
|
_writeController.addAttachments(widget.extraProps!.attachments ?? []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -253,7 +259,9 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
text: TextSpan(children: [
|
text: TextSpan(children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: _writeController.title.isNotEmpty ? _writeController.title : 'untitled'.tr(),
|
text: _writeController.title.isNotEmpty
|
||||||
|
? _writeController.title
|
||||||
|
: 'untitled'.tr(),
|
||||||
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||||
),
|
),
|
||||||
@ -280,7 +288,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
children: [
|
children: [
|
||||||
if (_writeController.editingPost != null)
|
if (_writeController.editingPost != null)
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20, right: 20),
|
padding: const EdgeInsets.only(
|
||||||
|
top: 4, bottom: 4, left: 20, right: 20),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
bottom: BorderSide(
|
bottom: BorderSide(
|
||||||
@ -294,13 +303,16 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Icons.edit, size: 16),
|
const Icon(Icons.edit, size: 16),
|
||||||
const Gap(10),
|
const Gap(10),
|
||||||
Text('postEditingNotice').tr(args: ['@${_writeController.editingPost!.publisher.name}']),
|
Text('postEditingNotice').tr(args: [
|
||||||
|
'@${_writeController.editingPost!.publisher.name}'
|
||||||
|
]),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_writeController.replyingPost != null)
|
if (_writeController.replyingPost != null)
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20, right: 20),
|
padding: const EdgeInsets.only(
|
||||||
|
top: 4, bottom: 4, left: 20, right: 20),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
bottom: BorderSide(
|
bottom: BorderSide(
|
||||||
@ -314,7 +326,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.reply, size: 16),
|
const Icon(Symbols.reply, size: 16),
|
||||||
const Gap(10),
|
const Gap(10),
|
||||||
Text('@${_writeController.replyingPost!.publisher.name}').bold(),
|
Text('@${_writeController.replyingPost!.publisher.name}')
|
||||||
|
.bold(),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
@ -328,7 +341,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
),
|
),
|
||||||
if (_writeController.repostingPost != null)
|
if (_writeController.repostingPost != null)
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20, right: 20),
|
padding: const EdgeInsets.only(
|
||||||
|
top: 4, bottom: 4, left: 20, right: 20),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
bottom: BorderSide(
|
bottom: BorderSide(
|
||||||
@ -342,7 +356,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.forward, size: 16),
|
const Icon(Symbols.forward, size: 16),
|
||||||
const Gap(10),
|
const Gap(10),
|
||||||
Text('@${_writeController.repostingPost!.publisher.name}').bold(),
|
Text('@${_writeController.repostingPost!.publisher.name}')
|
||||||
|
.bold(),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
@ -384,7 +399,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
})
|
})
|
||||||
.padding(top: 8),
|
.padding(top: 8),
|
||||||
),
|
),
|
||||||
if (_writeController.attachments.isNotEmpty || _writeController.thumbnail != null)
|
if (_writeController.attachments.isNotEmpty ||
|
||||||
|
_writeController.thumbnail != null)
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
@ -393,16 +409,19 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
attachments: _writeController.attachments,
|
attachments: _writeController.attachments,
|
||||||
isBusy: _writeController.isBusy,
|
isBusy: _writeController.isBusy,
|
||||||
onUpload: (int idx) async {
|
onUpload: (int idx) async {
|
||||||
await _writeController.uploadSingleAttachment(context, idx);
|
await _writeController.uploadSingleAttachment(
|
||||||
|
context, idx);
|
||||||
},
|
},
|
||||||
onInsertLink: (int idx) async {
|
onInsertLink: (int idx) async {
|
||||||
_writeController.contentController.text +=
|
_writeController.contentController.text +=
|
||||||
'\n';
|
'\n';
|
||||||
},
|
},
|
||||||
onUpdate: (int idx, PostWriteMedia updatedMedia) async {
|
onUpdate:
|
||||||
|
(int idx, PostWriteMedia updatedMedia) async {
|
||||||
_writeController.setIsBusy(true);
|
_writeController.setIsBusy(true);
|
||||||
try {
|
try {
|
||||||
_writeController.setAttachmentAt(idx, updatedMedia);
|
_writeController.setAttachmentAt(
|
||||||
|
idx, updatedMedia);
|
||||||
} finally {
|
} finally {
|
||||||
_writeController.setIsBusy(false);
|
_writeController.setIsBusy(false);
|
||||||
}
|
}
|
||||||
@ -415,7 +434,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
_writeController.setIsBusy(false);
|
_writeController.setIsBusy(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onUpdateBusy: (state) => _writeController.setIsBusy(state),
|
onUpdateBusy: (state) =>
|
||||||
|
_writeController.setIsBusy(state),
|
||||||
).padding(bottom: 8),
|
).padding(bottom: 8),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -426,11 +446,13 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (_writeController.isBusy && _writeController.progress != null)
|
if (_writeController.isBusy &&
|
||||||
|
_writeController.progress != null)
|
||||||
TweenAnimationBuilder<double>(
|
TweenAnimationBuilder<double>(
|
||||||
tween: Tween(begin: 0, end: _writeController.progress),
|
tween: Tween(begin: 0, end: _writeController.progress),
|
||||||
duration: Duration(milliseconds: 300),
|
duration: Duration(milliseconds: 300),
|
||||||
builder: (context, value, _) => LinearProgressIndicator(value: value, minHeight: 2),
|
builder: (context, value, _) =>
|
||||||
|
LinearProgressIndicator(value: value, minHeight: 2),
|
||||||
)
|
)
|
||||||
else if (_writeController.isBusy)
|
else if (_writeController.isBusy)
|
||||||
const LinearProgressIndicator(value: null, minHeight: 2),
|
const LinearProgressIndicator(value: null, minHeight: 2),
|
||||||
@ -439,12 +461,14 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
Container(
|
Container(
|
||||||
child: _writeController.temporaryRestored
|
child: _writeController.temporaryRestored
|
||||||
? Container(
|
? Container(
|
||||||
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 28, right: 22),
|
padding: const EdgeInsets.only(
|
||||||
|
top: 4, bottom: 4, left: 28, right: 22),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
bottom: BorderSide(
|
bottom: BorderSide(
|
||||||
color: Theme.of(context).dividerColor,
|
color: Theme.of(context).dividerColor,
|
||||||
width: 1 / MediaQuery.of(context).devicePixelRatio,
|
width: 1 /
|
||||||
|
MediaQuery.of(context).devicePixelRatio,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -453,7 +477,9 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Icons.restore, size: 20),
|
const Icon(Icons.restore, size: 20),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Expanded(child: Text('postLocalDraftRestored').tr()),
|
Expanded(
|
||||||
|
child:
|
||||||
|
Text('postLocalDraftRestored').tr()),
|
||||||
InkWell(
|
InkWell(
|
||||||
child: Text('dialogDismiss').tr(),
|
child: Text('dialogDismiss').tr(),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -464,8 +490,10 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
))
|
))
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
)
|
)
|
||||||
.height(_writeController.temporaryRestored ? 32 : 0, animate: true)
|
.height(_writeController.temporaryRestored ? 32 : 0,
|
||||||
.animate(const Duration(milliseconds: 300), Curves.fastLinearToSlowEaseIn),
|
animate: true)
|
||||||
|
.animate(const Duration(milliseconds: 300),
|
||||||
|
Curves.fastLinearToSlowEaseIn),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
@ -485,11 +513,18 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
),
|
),
|
||||||
if (_writeController.mode == 'stories')
|
if (_writeController.mode == 'stories')
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Symbols.poll, color: Theme.of(context).colorScheme.primary),
|
icon: Icon(Symbols.poll,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary),
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
backgroundColor: _writeController.poll == null
|
backgroundColor:
|
||||||
? null
|
_writeController.poll == null
|
||||||
: WidgetStatePropertyAll(Theme.of(context).colorScheme.surfaceContainer),
|
? null
|
||||||
|
: WidgetStatePropertyAll(
|
||||||
|
Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.surfaceContainer),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_showPollEditorDialog();
|
_showPollEditorDialog();
|
||||||
@ -497,14 +532,22 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
),
|
),
|
||||||
if (_writeController.mode == 'articles')
|
if (_writeController.mode == 'articles')
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Symbols.full_coverage, color: Theme.of(context).colorScheme.primary),
|
icon: Icon(Symbols.full_coverage,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary),
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
backgroundColor: _writeController.thumbnail == null
|
backgroundColor:
|
||||||
? null
|
_writeController.thumbnail == null
|
||||||
: WidgetStatePropertyAll(Theme.of(context).colorScheme.surfaceContainer),
|
? null
|
||||||
|
: WidgetStatePropertyAll(
|
||||||
|
Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.surfaceContainer),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_writeController.thumbnail != null) {
|
if (_writeController.thumbnail !=
|
||||||
|
null) {
|
||||||
_writeController.setThumbnail(null);
|
_writeController.setThumbnail(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -517,7 +560,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: (_writeController.isBusy || _writeController.publisher == null)
|
onPressed: (_writeController.isBusy ||
|
||||||
|
_writeController.publisher == null)
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
_writeController.sendPost(context).then((_) {
|
_writeController.sendPost(context).then((_) {
|
||||||
@ -556,7 +600,8 @@ class _PostPublisherPopup extends StatelessWidget {
|
|||||||
final List<SnPublisher>? publishers;
|
final List<SnPublisher>? publishers;
|
||||||
final Function onUpdate;
|
final Function onUpdate;
|
||||||
|
|
||||||
const _PostPublisherPopup({required this.controller, this.publishers, required this.onUpdate});
|
const _PostPublisherPopup(
|
||||||
|
{required this.controller, this.publishers, required this.onUpdate});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -568,7 +613,9 @@ class _PostPublisherPopup extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.face, size: 24),
|
const Icon(Symbols.face, size: 24),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
Text('accountPublishers', style: Theme.of(context).textTheme.titleLarge).tr(),
|
Text('accountPublishers',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge)
|
||||||
|
.tr(),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 20, top: 16, bottom: 12),
|
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||||
ListTile(
|
ListTile(
|
||||||
@ -612,7 +659,8 @@ class _PostRealmPopup extends StatelessWidget {
|
|||||||
final List<SnRealm>? realms;
|
final List<SnRealm>? realms;
|
||||||
final Function onUpdate;
|
final Function onUpdate;
|
||||||
|
|
||||||
const _PostRealmPopup({required this.controller, this.realms, required this.onUpdate});
|
const _PostRealmPopup(
|
||||||
|
{required this.controller, this.realms, required this.onUpdate});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -624,7 +672,8 @@ class _PostRealmPopup extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.face, size: 24),
|
const Icon(Symbols.face, size: 24),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
Text('accountRealms', style: Theme.of(context).textTheme.titleLarge).tr(),
|
Text('accountRealms', style: Theme.of(context).textTheme.titleLarge)
|
||||||
|
.tr(),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 20, top: 16, bottom: 12),
|
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||||
ListTile(
|
ListTile(
|
||||||
@ -665,7 +714,8 @@ class _PostStoryEditor extends StatelessWidget {
|
|||||||
final Function? onTapPublisher;
|
final Function? onTapPublisher;
|
||||||
final Function? onTapRealm;
|
final Function? onTapRealm;
|
||||||
|
|
||||||
const _PostStoryEditor({required this.controller, this.onTapPublisher, this.onTapRealm});
|
const _PostStoryEditor(
|
||||||
|
{required this.controller, this.onTapPublisher, this.onTapRealm});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -717,7 +767,8 @@ class _PostStoryEditor extends StatelessWidget {
|
|||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
).padding(horizontal: 16),
|
).padding(horizontal: 16),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
TextField(
|
TextField(
|
||||||
@ -732,8 +783,10 @@ class _PostStoryEditor extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
contentInsertionConfiguration: controller.contentInsertionConfiguration,
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
contentInsertionConfiguration:
|
||||||
|
controller.contentInsertionConfiguration,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -749,7 +802,8 @@ class _PostArticleEditor extends StatelessWidget {
|
|||||||
final Function? onTapPublisher;
|
final Function? onTapPublisher;
|
||||||
final Function? onTapRealm;
|
final Function? onTapRealm;
|
||||||
|
|
||||||
const _PostArticleEditor({required this.controller, this.onTapPublisher, this.onTapRealm});
|
const _PostArticleEditor(
|
||||||
|
{required this.controller, this.onTapPublisher, this.onTapRealm});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -857,8 +911,10 @@ class _PostArticleEditor extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
contentInsertionConfiguration: controller.contentInsertionConfiguration,
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
contentInsertionConfiguration:
|
||||||
|
controller.contentInsertionConfiguration,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
@ -893,7 +949,8 @@ class _PostArticleEditor extends StatelessWidget {
|
|||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
contentInsertionConfiguration: controller.contentInsertionConfiguration,
|
contentInsertionConfiguration:
|
||||||
|
controller.contentInsertionConfiguration,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -906,7 +963,8 @@ class _PostQuestionEditor extends StatelessWidget {
|
|||||||
final Function? onTapPublisher;
|
final Function? onTapPublisher;
|
||||||
final Function? onTapRealm;
|
final Function? onTapRealm;
|
||||||
|
|
||||||
const _PostQuestionEditor({required this.controller, this.onTapPublisher, this.onTapRealm});
|
const _PostQuestionEditor(
|
||||||
|
{required this.controller, this.onTapPublisher, this.onTapRealm});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -958,7 +1016,8 @@ class _PostQuestionEditor extends StatelessWidget {
|
|||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
).padding(horizontal: 16),
|
).padding(horizontal: 16),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
TextField(
|
TextField(
|
||||||
@ -969,7 +1028,8 @@ class _PostQuestionEditor extends StatelessWidget {
|
|||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
isCollapsed: true,
|
isCollapsed: true,
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
).padding(horizontal: 16),
|
).padding(horizontal: 16),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
TextField(
|
TextField(
|
||||||
@ -984,8 +1044,10 @@ class _PostQuestionEditor extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
contentInsertionConfiguration: controller.contentInsertionConfiguration,
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
contentInsertionConfiguration:
|
||||||
|
controller.contentInsertionConfiguration,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -1001,7 +1063,8 @@ class _PostVideoEditor extends StatelessWidget {
|
|||||||
final Function? onTapPublisher;
|
final Function? onTapPublisher;
|
||||||
final Function? onTapRealm;
|
final Function? onTapRealm;
|
||||||
|
|
||||||
const _PostVideoEditor({required this.controller, this.onTapPublisher, this.onTapRealm});
|
const _PostVideoEditor(
|
||||||
|
{required this.controller, this.onTapPublisher, this.onTapRealm});
|
||||||
|
|
||||||
void _selectVideo(BuildContext context) async {
|
void _selectVideo(BuildContext context) async {
|
||||||
final video = await showDialog<SnAttachment?>(
|
final video = await showDialog<SnAttachment?>(
|
||||||
@ -1022,7 +1085,8 @@ class _PostVideoEditor extends StatelessWidget {
|
|||||||
|
|
||||||
final result = await showDialog<SnAttachment?>(
|
final result = await showDialog<SnAttachment?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => PendingAttachmentAltDialog(media: PostWriteMedia(controller.videoAttachment)),
|
builder: (context) => PendingAttachmentAltDialog(
|
||||||
|
media: PostWriteMedia(controller.videoAttachment)),
|
||||||
);
|
);
|
||||||
if (result == null) return;
|
if (result == null) return;
|
||||||
|
|
||||||
@ -1034,7 +1098,8 @@ class _PostVideoEditor extends StatelessWidget {
|
|||||||
|
|
||||||
final result = await showDialog<SnAttachmentBoost?>(
|
final result = await showDialog<SnAttachmentBoost?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => PendingAttachmentBoostDialog(media: PostWriteMedia(controller.videoAttachment)),
|
builder: (context) => PendingAttachmentBoostDialog(
|
||||||
|
media: PostWriteMedia(controller.videoAttachment)),
|
||||||
);
|
);
|
||||||
if (result == null) return;
|
if (result == null) return;
|
||||||
|
|
||||||
@ -1077,7 +1142,8 @@ class _PostVideoEditor extends StatelessWidget {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
await sn.client.delete('/cgi/uc/attachments/${controller.videoAttachment!.id}');
|
await sn.client
|
||||||
|
.delete('/cgi/uc/attachments/${controller.videoAttachment!.id}');
|
||||||
controller.setVideoAttachment(null);
|
controller.setVideoAttachment(null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
@ -1087,143 +1153,159 @@ class _PostVideoEditor extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Container(
|
||||||
children: [
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
Column(
|
constraints: const BoxConstraints(maxWidth: 640),
|
||||||
children: [
|
child: Row(
|
||||||
Material(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
elevation: 2,
|
children: [
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
Column(
|
||||||
child: GestureDetector(
|
children: [
|
||||||
onTap: () {
|
Material(
|
||||||
onTapPublisher?.call();
|
elevation: 2,
|
||||||
},
|
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||||
child: AccountImage(
|
child: GestureDetector(
|
||||||
content: controller.publisher?.avatar,
|
onTap: () {
|
||||||
|
onTapPublisher?.call();
|
||||||
|
},
|
||||||
|
child: AccountImage(
|
||||||
|
content: controller.publisher?.avatar,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const Gap(11),
|
||||||
const Gap(11),
|
Material(
|
||||||
Material(
|
elevation: 1,
|
||||||
elevation: 1,
|
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
child: GestureDetector(
|
||||||
child: GestureDetector(
|
onTap: () {
|
||||||
onTap: () {
|
onTapRealm?.call();
|
||||||
onTapRealm?.call();
|
},
|
||||||
},
|
child: AccountImage(
|
||||||
child: AccountImage(
|
content: controller.realm?.avatar,
|
||||||
content: controller.realm?.avatar,
|
fallbackWidget: const Icon(Symbols.globe, size: 20),
|
||||||
fallbackWidget: const Icon(Symbols.globe, size: 20),
|
radius: 14,
|
||||||
radius: 14,
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
|
||||||
),
|
|
||||||
const Gap(16),
|
|
||||||
TextField(
|
|
||||||
controller: controller.titleController,
|
|
||||||
decoration: InputDecoration.collapsed(
|
|
||||||
hintText: 'fieldPostTitle'.tr(),
|
|
||||||
border: InputBorder.none,
|
|
||||||
),
|
),
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
Expanded(
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
child: Column(
|
||||||
).padding(horizontal: 16),
|
children: [
|
||||||
const Gap(8),
|
const Gap(6),
|
||||||
TextField(
|
TextField(
|
||||||
controller: controller.descriptionController,
|
controller: controller.titleController,
|
||||||
decoration: InputDecoration.collapsed(
|
decoration: InputDecoration.collapsed(
|
||||||
hintText: 'fieldPostDescription'.tr(),
|
hintText: 'fieldPostTitle'.tr(),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
maxLines: null,
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
keyboardType: TextInputType.multiline,
|
onTapOutside: (_) =>
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
).padding(horizontal: 16),
|
||||||
).padding(horizontal: 16),
|
const Gap(8),
|
||||||
const Gap(12),
|
TextField(
|
||||||
Container(
|
controller: controller.descriptionController,
|
||||||
margin: const EdgeInsets.only(left: 16, right: 16),
|
decoration: InputDecoration.collapsed(
|
||||||
decoration: BoxDecoration(
|
hintText: 'fieldPostDescription'.tr(),
|
||||||
borderRadius: BorderRadius.circular(16),
|
border: InputBorder.none,
|
||||||
border: Border.all(color: Theme.of(context).dividerColor),
|
),
|
||||||
),
|
maxLines: null,
|
||||||
child: ContextMenuRegion(
|
keyboardType: TextInputType.multiline,
|
||||||
contextMenu: ContextMenu(
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
entries: [
|
onTapOutside: (_) =>
|
||||||
MenuItem(
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
label: 'attachmentSetAlt'.tr(),
|
).padding(horizontal: 16),
|
||||||
icon: Symbols.description,
|
const Gap(12),
|
||||||
onSelected: () {
|
Container(
|
||||||
_setAlt(context);
|
margin: const EdgeInsets.only(left: 16, right: 16),
|
||||||
},
|
decoration: BoxDecoration(
|
||||||
),
|
borderRadius: BorderRadius.circular(16),
|
||||||
MenuItem(
|
border: Border.all(color: Theme.of(context).dividerColor),
|
||||||
label: 'attachmentBoost'.tr(),
|
),
|
||||||
icon: Symbols.bolt,
|
child: ContextMenuRegion(
|
||||||
onSelected: () {
|
contextMenu: ContextMenu(
|
||||||
_createBoost(context);
|
entries: [
|
||||||
},
|
MenuItem(
|
||||||
),
|
label: 'attachmentSetAlt'.tr(),
|
||||||
MenuItem(
|
icon: Symbols.description,
|
||||||
label: 'attachmentSetThumbnail'.tr(),
|
onSelected: () {
|
||||||
icon: Symbols.image,
|
_setAlt(context);
|
||||||
onSelected: () {
|
},
|
||||||
_setThumbnail(context);
|
),
|
||||||
},
|
MenuItem(
|
||||||
),
|
label: 'attachmentBoost'.tr(),
|
||||||
MenuItem(
|
icon: Symbols.bolt,
|
||||||
label: 'attachmentCopyRandomId'.tr(),
|
onSelected: () {
|
||||||
icon: Symbols.content_copy,
|
_createBoost(context);
|
||||||
onSelected: () {
|
},
|
||||||
Clipboard.setData(ClipboardData(text: controller.videoAttachment!.rid));
|
),
|
||||||
},
|
MenuItem(
|
||||||
),
|
label: 'attachmentSetThumbnail'.tr(),
|
||||||
MenuItem(
|
icon: Symbols.image,
|
||||||
label: 'delete'.tr(),
|
onSelected: () {
|
||||||
icon: Symbols.delete,
|
_setThumbnail(context);
|
||||||
onSelected: () => _deleteAttachment(context),
|
},
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
label: 'unlink'.tr(),
|
label: 'attachmentCopyRandomId'.tr(),
|
||||||
icon: Symbols.link_off,
|
icon: Symbols.content_copy,
|
||||||
onSelected: () {
|
onSelected: () {
|
||||||
controller.setVideoAttachment(null);
|
Clipboard.setData(ClipboardData(
|
||||||
},
|
text: controller.videoAttachment!.rid));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MenuItem(
|
||||||
|
label: 'delete'.tr(),
|
||||||
|
icon: Symbols.delete,
|
||||||
|
onSelected: () => _deleteAttachment(context),
|
||||||
|
),
|
||||||
|
MenuItem(
|
||||||
|
label: 'unlink'.tr(),
|
||||||
|
icon: Symbols.link_off,
|
||||||
|
onSelected: () {
|
||||||
|
controller.setVideoAttachment(null);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
onTap: controller.videoAttachment == null
|
||||||
|
? () => _selectVideo(context)
|
||||||
|
: null,
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 16 / 9,
|
||||||
|
child: controller.videoAttachment == null
|
||||||
|
? Center(
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.add),
|
||||||
|
const Gap(4),
|
||||||
|
Text('postVideoUpload'.tr()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
child: AttachmentItem(
|
||||||
|
data: controller.videoAttachment!,
|
||||||
|
heroTag: const Uuid().v4(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: InkWell(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
onTap: controller.videoAttachment == null ? () => _selectVideo(context) : null,
|
|
||||||
child: AspectRatio(
|
|
||||||
aspectRatio: 16 / 9,
|
|
||||||
child: controller.videoAttachment == null
|
|
||||||
? Center(
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.add),
|
|
||||||
const Gap(4),
|
|
||||||
Text('postVideoUpload'.tr()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
child: AttachmentItem(
|
|
||||||
data: controller.videoAttachment!,
|
|
||||||
heroTag: const Uuid().v4(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -229,7 +229,10 @@ class ChatMessage extends StatelessWidget {
|
|||||||
bordered: true,
|
bordered: true,
|
||||||
maxHeight: 360,
|
maxHeight: 360,
|
||||||
maxWidth: 480 - 48 - padding.left,
|
maxWidth: 480 - 48 - padding.left,
|
||||||
padding: padding.copyWith(top: 8, left: 48 + padding.left),
|
padding: padding.copyWith(
|
||||||
|
top: 8,
|
||||||
|
left: isCompact ? padding.left : 48 + padding.left,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
if (!hasMerged && !isCompact)
|
if (!hasMerged && !isCompact)
|
||||||
const Gap(12)
|
const Gap(12)
|
||||||
|
@ -28,7 +28,8 @@ class ChatMessageInput extends StatefulWidget {
|
|||||||
final ChatMessageController controller;
|
final ChatMessageController controller;
|
||||||
final SnChannelMember? otherMember;
|
final SnChannelMember? otherMember;
|
||||||
|
|
||||||
const ChatMessageInput({super.key, required this.controller, this.otherMember});
|
const ChatMessageInput(
|
||||||
|
{super.key, required this.controller, this.otherMember});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ChatMessageInput> createState() => ChatMessageInputState();
|
State<ChatMessageInput> createState() => ChatMessageInputState();
|
||||||
@ -45,12 +46,20 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
|
|
||||||
final HotKey _pasteHotKey = HotKey(
|
final HotKey _pasteHotKey = HotKey(
|
||||||
key: PhysicalKeyboardKey.keyV,
|
key: PhysicalKeyboardKey.keyV,
|
||||||
modifiers: [(!kIsWeb && Platform.isMacOS) ? HotKeyModifier.meta : HotKeyModifier.control],
|
modifiers: [
|
||||||
|
(!kIsWeb && Platform.isMacOS)
|
||||||
|
? HotKeyModifier.meta
|
||||||
|
: HotKeyModifier.control
|
||||||
|
],
|
||||||
scope: HotKeyScope.inapp,
|
scope: HotKeyScope.inapp,
|
||||||
);
|
);
|
||||||
final HotKey _newLineHotKey = HotKey(
|
final HotKey _newLineHotKey = HotKey(
|
||||||
key: PhysicalKeyboardKey.enter,
|
key: PhysicalKeyboardKey.enter,
|
||||||
modifiers: [(!kIsWeb && Platform.isMacOS) ? HotKeyModifier.meta : HotKeyModifier.control],
|
modifiers: [
|
||||||
|
(!kIsWeb && Platform.isMacOS)
|
||||||
|
? HotKeyModifier.meta
|
||||||
|
: HotKeyModifier.control
|
||||||
|
],
|
||||||
scope: HotKeyScope.inapp,
|
scope: HotKeyScope.inapp,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -100,7 +109,8 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
void setEdit(SnChatMessage? value) {
|
void setEdit(SnChatMessage? value) {
|
||||||
_contentController.text = value?.body['text'] ?? '';
|
_contentController.text = value?.body['text'] ?? '';
|
||||||
_attachments.clear();
|
_attachments.clear();
|
||||||
_attachments.addAll(value?.preload?.attachments?.map((e) => PostWriteMedia(e)) ?? []);
|
_attachments.addAll(
|
||||||
|
value?.preload?.attachments?.map((e) => PostWriteMedia(e)) ?? []);
|
||||||
setState(() => _editingMessage = value);
|
setState(() => _editingMessage = value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +149,9 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
media.name,
|
media.name,
|
||||||
'messaging',
|
'messaging',
|
||||||
null,
|
null,
|
||||||
mimetype: media.raw != null && media.type == SnMediaType.image ? 'image/png' : null,
|
mimetype: media.raw != null && media.type == SnMediaType.image
|
||||||
|
? 'image/png'
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
final item = await attach.chunkedUploadParts(
|
final item = await attach.chunkedUploadParts(
|
||||||
@ -171,7 +183,10 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
widget.controller.sendMessage(
|
widget.controller.sendMessage(
|
||||||
_editingMessage != null ? 'messages.edit' : 'messages.new',
|
_editingMessage != null ? 'messages.edit' : 'messages.new',
|
||||||
_contentController.text,
|
_contentController.text,
|
||||||
attachments: _attachments.where((e) => e.attachment != null).map((e) => e.attachment!.rid).toList(),
|
attachments: _attachments
|
||||||
|
.where((e) => e.attachment != null)
|
||||||
|
.map((e) => e.attachment!.rid)
|
||||||
|
.toList(),
|
||||||
relatedId: _editingMessage?.id,
|
relatedId: _editingMessage?.id,
|
||||||
quoteId: _replyingMessage?.id,
|
quoteId: _replyingMessage?.id,
|
||||||
editingMessage: _editingMessage,
|
editingMessage: _editingMessage,
|
||||||
@ -232,12 +247,15 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
TweenAnimationBuilder<double>(
|
TweenAnimationBuilder<double>(
|
||||||
tween: Tween(begin: 0, end: _progress),
|
tween: Tween(begin: 0, end: _progress),
|
||||||
duration: Duration(milliseconds: 300),
|
duration: Duration(milliseconds: 300),
|
||||||
builder: (context, value, _) => LinearProgressIndicator(value: value, minHeight: 2),
|
builder: (context, value, _) =>
|
||||||
|
LinearProgressIndicator(value: value, minHeight: 2),
|
||||||
)
|
)
|
||||||
else if (_isBusy)
|
else if (_isBusy)
|
||||||
const LinearProgressIndicator(value: null, minHeight: 2),
|
const LinearProgressIndicator(value: null, minHeight: 2),
|
||||||
Padding(
|
Padding(
|
||||||
padding: _attachments.isNotEmpty ? const EdgeInsets.only(top: 8) : EdgeInsets.zero,
|
padding: _attachments.isNotEmpty
|
||||||
|
? const EdgeInsets.only(top: 8)
|
||||||
|
: EdgeInsets.zero,
|
||||||
child: PostMediaPendingList(
|
child: PostMediaPendingList(
|
||||||
attachments: _attachments,
|
attachments: _attachments,
|
||||||
isBusy: _isBusy,
|
isBusy: _isBusy,
|
||||||
@ -249,9 +267,8 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
},
|
},
|
||||||
onUpdateBusy: (state) => setState(() => _isBusy = state),
|
onUpdateBusy: (state) => setState(() => _isBusy = state),
|
||||||
),
|
),
|
||||||
)
|
).height(_attachments.isNotEmpty ? 80 + 8 : 0, animate: true).animate(
|
||||||
.height(_attachments.isNotEmpty ? 80 + 8 : 0, animate: true)
|
const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
|
||||||
.animate(const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
|
|
||||||
SingleChildScrollView(
|
SingleChildScrollView(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
child: _replyingMessage != null
|
child: _replyingMessage != null
|
||||||
@ -272,7 +289,8 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
const Gap(8),
|
const Gap(8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
_replyingMessage?.body['text'] ?? '${_replyingMessage?.sender.nick}',
|
_replyingMessage?.body['text'] ??
|
||||||
|
'${_replyingMessage?.sender.nick}',
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
@ -289,9 +307,8 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
).padding(vertical: 8),
|
).padding(vertical: 8),
|
||||||
)
|
)
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
)
|
).height(_replyingMessage != null ? 38 : 0, animate: true).animate(
|
||||||
.height(_replyingMessage != null ? 38 : 0, animate: true)
|
const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
|
||||||
.animate(const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
|
|
||||||
SingleChildScrollView(
|
SingleChildScrollView(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
child: _editingMessage != null
|
child: _editingMessage != null
|
||||||
@ -312,7 +329,8 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
const Gap(8),
|
const Gap(8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
_editingMessage?.body['text'] ?? '${_editingMessage?.sender.nick}',
|
_editingMessage?.body['text'] ??
|
||||||
|
'${_editingMessage?.sender.nick}',
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
@ -330,12 +348,13 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
).padding(vertical: 8),
|
).padding(vertical: 8),
|
||||||
)
|
)
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
)
|
).height(_editingMessage != null ? 38 : 0, animate: true).animate(
|
||||||
.height(_editingMessage != null ? 38 : 0, animate: true)
|
const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
|
||||||
.animate(const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
|
Container(
|
||||||
SizedBox(
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||||
height: 56,
|
constraints: BoxConstraints(minHeight: 56, maxHeight: 240),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
@ -347,11 +366,14 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
? 'fieldChatMessageDirect'.tr(args: [
|
? 'fieldChatMessageDirect'.tr(args: [
|
||||||
'@${ud.getAccountFromCache(widget.otherMember?.accountId)?.name}',
|
'@${ud.getAccountFromCache(widget.otherMember?.accountId)?.name}',
|
||||||
])
|
])
|
||||||
: 'fieldChatMessage'.tr(args: [widget.controller.channel?.name ?? 'loading'.tr()]),
|
: 'fieldChatMessage'.tr(args: [
|
||||||
|
widget.controller.channel?.name ?? 'loading'.tr()
|
||||||
|
]),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
textInputAction: TextInputAction.send,
|
textInputAction: TextInputAction.send,
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
onSubmitted: (_) {
|
onSubmitted: (_) {
|
||||||
if (_isBusy) return;
|
if (_isBusy) return;
|
||||||
_sendMessage();
|
_sendMessage();
|
||||||
@ -366,7 +388,8 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
Symbols.mood,
|
Symbols.mood,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
visualDensity:
|
||||||
|
const VisualDensity(horizontal: -4, vertical: -4),
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
constraints: const BoxConstraints(),
|
constraints: const BoxConstraints(),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -386,13 +409,14 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
Symbols.send,
|
Symbols.send,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
visualDensity:
|
||||||
|
const VisualDensity(horizontal: -4, vertical: -4),
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
constraints: const BoxConstraints(),
|
constraints: const BoxConstraints(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
).padding(horizontal: 16),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -403,7 +427,8 @@ class _StickerPicker extends StatelessWidget {
|
|||||||
final Function? onDismiss;
|
final Function? onDismiss;
|
||||||
final Function(String)? onInsert;
|
final Function(String)? onInsert;
|
||||||
|
|
||||||
const _StickerPicker({this.onDismiss, required this.originalText, this.onInsert});
|
const _StickerPicker(
|
||||||
|
{this.onDismiss, required this.originalText, this.onInsert});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -413,7 +438,9 @@ class _StickerPicker extends StatelessWidget {
|
|||||||
onDismiss?.call();
|
onDismiss?.call();
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: BoxConstraints(maxWidth: min(360, MediaQuery.of(context).size.width), maxHeight: 240),
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: min(360, MediaQuery.of(context).size.width),
|
||||||
|
maxHeight: 240),
|
||||||
child: Material(
|
child: Material(
|
||||||
elevation: 8,
|
elevation: 8,
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
@ -426,8 +453,10 @@ class _StickerPicker extends StatelessWidget {
|
|||||||
return <Widget>[
|
return <Widget>[
|
||||||
Container(
|
Container(
|
||||||
margin: EdgeInsets.only(bottom: 8),
|
margin: EdgeInsets.only(bottom: 8),
|
||||||
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
padding:
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@ -439,7 +468,8 @@ class _StickerPicker extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
GridView.builder(
|
GridView.builder(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8),
|
padding:
|
||||||
|
const EdgeInsets.only(left: 8, right: 8, bottom: 8),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
maxCrossAxisExtent: 48,
|
maxCrossAxisExtent: 48,
|
||||||
@ -462,7 +492,8 @@ class _StickerPicker extends StatelessWidget {
|
|||||||
richMessage: TextSpan(
|
richMessage: TextSpan(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: ':${element.pack.prefix}${element.alias}:\n',
|
text:
|
||||||
|
':${element.pack.prefix}${element.alias}:\n',
|
||||||
style: GoogleFonts.robotoMono()),
|
style: GoogleFonts.robotoMono()),
|
||||||
TextSpan(text: element.name).bold(),
|
TextSpan(text: element.name).bold(),
|
||||||
],
|
],
|
||||||
@ -471,11 +502,15 @@ class _StickerPicker extends StatelessWidget {
|
|||||||
width: 48,
|
width: 48,
|
||||||
height: 48,
|
height: 48,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
Radius.circular(8)),
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.surfaceContainerHigh,
|
||||||
),
|
),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(
|
||||||
|
Radius.circular(8)),
|
||||||
child: UniversalImage(
|
child: UniversalImage(
|
||||||
sn.getAttachmentUrl(element.attachment.rid),
|
sn.getAttachmentUrl(element.attachment.rid),
|
||||||
width: 48,
|
width: 48,
|
||||||
|
@ -67,7 +67,10 @@ class PostMediaPendingList extends StatelessWidget {
|
|||||||
|
|
||||||
if (result == null) return;
|
if (result == null) return;
|
||||||
|
|
||||||
final rawBytes = (await result.uiImage.toByteData(format: ImageByteFormat.png))!.buffer.asUint8List();
|
final rawBytes =
|
||||||
|
(await result.uiImage.toByteData(format: ImageByteFormat.png))!
|
||||||
|
.buffer
|
||||||
|
.asUint8List();
|
||||||
|
|
||||||
if (onUpdate != null) {
|
if (onUpdate != null) {
|
||||||
final updatedMedia = PostWriteMedia.fromBytes(
|
final updatedMedia = PostWriteMedia.fromBytes(
|
||||||
@ -133,7 +136,8 @@ class PostMediaPendingList extends StatelessWidget {
|
|||||||
|
|
||||||
final result = await showDialog<SnAttachmentBoost?>(
|
final result = await showDialog<SnAttachmentBoost?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => PendingAttachmentBoostDialog(media: attachments[idx]),
|
builder: (context) =>
|
||||||
|
PendingAttachmentBoostDialog(media: attachments[idx]),
|
||||||
);
|
);
|
||||||
if (result == null) return;
|
if (result == null) return;
|
||||||
|
|
||||||
@ -165,11 +169,15 @@ class PostMediaPendingList extends StatelessWidget {
|
|||||||
onUpdate!(idx, PostWriteMedia(result));
|
onUpdate!(idx, PostWriteMedia(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
ContextMenu _createContextMenu(BuildContext context, int idx, PostWriteMedia media) {
|
ContextMenu _createContextMenu(
|
||||||
final canCompressVideo = !kIsWeb && (Platform.isAndroid || Platform.isIOS || Platform.isMacOS);
|
BuildContext context, int idx, PostWriteMedia media) {
|
||||||
|
final canCompressVideo =
|
||||||
|
!kIsWeb && (Platform.isAndroid || Platform.isIOS || Platform.isMacOS);
|
||||||
return ContextMenu(
|
return ContextMenu(
|
||||||
entries: [
|
entries: [
|
||||||
if (media.attachment == null && media.type == SnMediaType.video && canCompressVideo)
|
if (media.attachment == null &&
|
||||||
|
media.type == SnMediaType.video &&
|
||||||
|
canCompressVideo)
|
||||||
MenuItem(
|
MenuItem(
|
||||||
label: 'attachmentCompressVideo'.tr(),
|
label: 'attachmentCompressVideo'.tr(),
|
||||||
icon: Symbols.compress,
|
icon: Symbols.compress,
|
||||||
@ -312,12 +320,15 @@ class _PostMediaPendingItem extends StatelessWidget {
|
|||||||
AspectRatio(
|
AspectRatio(
|
||||||
aspectRatio: 1,
|
aspectRatio: 1,
|
||||||
child: switch (media.type) {
|
child: switch (media.type) {
|
||||||
SnMediaType.image => LayoutBuilder(builder: (context, constraints) {
|
SnMediaType.image =>
|
||||||
|
LayoutBuilder(builder: (context, constraints) {
|
||||||
return Image(
|
return Image(
|
||||||
image: media.getImageProvider(
|
image: media.getImageProvider(
|
||||||
context,
|
context,
|
||||||
width: (constraints.maxWidth * devicePixelRatio).round(),
|
width:
|
||||||
height: (constraints.maxHeight * devicePixelRatio).round(),
|
(constraints.maxWidth * devicePixelRatio).round(),
|
||||||
|
height:
|
||||||
|
(constraints.maxHeight * devicePixelRatio).round(),
|
||||||
)!,
|
)!,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
);
|
);
|
||||||
@ -326,28 +337,34 @@ class _PostMediaPendingItem extends StatelessWidget {
|
|||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
if (media.attachment?.thumbnail != null)
|
if (media.attachment?.thumbnail != null)
|
||||||
AutoResizeUniversalImage(sn.getAttachmentUrl(media.attachment!.thumbnail!.rid)),
|
AutoResizeUniversalImage(sn.getAttachmentUrl(
|
||||||
const Icon(Symbols.videocam, color: Colors.white, shadows: [
|
media.attachment!.thumbnail!.rid)),
|
||||||
Shadow(
|
const Icon(Symbols.videocam,
|
||||||
offset: Offset(1, 1),
|
color: Colors.white,
|
||||||
blurRadius: 8.0,
|
shadows: [
|
||||||
color: Color.fromARGB(255, 0, 0, 0),
|
Shadow(
|
||||||
),
|
offset: Offset(1, 1),
|
||||||
]),
|
blurRadius: 8.0,
|
||||||
|
color: Color.fromARGB(255, 0, 0, 0),
|
||||||
|
),
|
||||||
|
]),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SnMediaType.audio => Stack(
|
SnMediaType.audio => Stack(
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
if (media.attachment?.thumbnail != null)
|
if (media.attachment?.thumbnail != null)
|
||||||
AutoResizeUniversalImage(sn.getAttachmentUrl(media.attachment!.thumbnail!.rid)),
|
AutoResizeUniversalImage(sn.getAttachmentUrl(
|
||||||
const Icon(Symbols.audio_file, color: Colors.white, shadows: [
|
media.attachment!.thumbnail!.rid)),
|
||||||
Shadow(
|
const Icon(Symbols.audio_file,
|
||||||
offset: Offset(1, 1),
|
color: Colors.white,
|
||||||
blurRadius: 8.0,
|
shadows: [
|
||||||
color: Color.fromARGB(255, 0, 0, 0),
|
Shadow(
|
||||||
),
|
offset: Offset(1, 1),
|
||||||
]),
|
blurRadius: 8.0,
|
||||||
|
color: Color.fromARGB(255, 0, 0, 0),
|
||||||
|
),
|
||||||
|
]),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
_ => Container(
|
_ => Container(
|
||||||
@ -356,7 +373,8 @@ class _PostMediaPendingItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (media.type != SnMediaType.image) const VerticalDivider(width: 1, thickness: 1),
|
if (media.type != SnMediaType.image)
|
||||||
|
const VerticalDivider(width: 1, thickness: 1),
|
||||||
if (media.type != SnMediaType.image)
|
if (media.type != SnMediaType.image)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 160,
|
width: 160,
|
||||||
@ -374,7 +392,8 @@ class _PostMediaPendingItem extends StatelessWidget {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
)
|
)
|
||||||
else if (media.file != null)
|
else if (media.file != null)
|
||||||
Text(media.file!.name, maxLines: 1, overflow: TextOverflow.ellipsis)
|
Text(media.file!.name,
|
||||||
|
maxLines: 1, overflow: TextOverflow.ellipsis)
|
||||||
else
|
else
|
||||||
Text('unknown'.tr()),
|
Text('unknown'.tr()),
|
||||||
if (media.attachment != null)
|
if (media.attachment != null)
|
||||||
@ -387,7 +406,8 @@ class _PostMediaPendingItem extends StatelessWidget {
|
|||||||
FutureBuilder<int?>(
|
FutureBuilder<int?>(
|
||||||
future: media.length(),
|
future: media.length(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData) return const SizedBox.shrink();
|
if (!snapshot.hasData)
|
||||||
|
return const SizedBox.shrink();
|
||||||
return Text(
|
return Text(
|
||||||
snapshot.data!.formatBytes(),
|
snapshot.data!.formatBytes(),
|
||||||
style: GoogleFonts.robotoMono(fontSize: 13),
|
style: GoogleFonts.robotoMono(fontSize: 13),
|
||||||
@ -398,7 +418,8 @@ class _PostMediaPendingItem extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (media.attachment != null && media.attachment!.boosts.isNotEmpty)
|
if (media.attachment != null &&
|
||||||
|
media.attachment!.boosts.isNotEmpty)
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Symbols.bolt, size: 16),
|
Icon(Symbols.bolt, size: 16),
|
||||||
@ -406,7 +427,8 @@ class _PostMediaPendingItem extends StatelessWidget {
|
|||||||
Text('attachmentGotBoosted').tr().fontSize(13),
|
Text('attachmentGotBoosted').tr().fontSize(13),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (media.attachment != null && media.attachment!.compressedId != null)
|
if (media.attachment != null &&
|
||||||
|
media.attachment!.compressedId != null)
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Symbols.compress, size: 16),
|
Icon(Symbols.compress, size: 16),
|
||||||
@ -527,12 +549,20 @@ class AddPostMediaButton extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return PopupMenuButton(
|
return PopupMenuButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
style: ButtonStyle(
|
||||||
|
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
|
||||||
|
),
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Symbols.add_photo_alternate,
|
Symbols.add_photo_alternate,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
itemBuilder: (context) => [
|
itemBuilder: (context) => [
|
||||||
if (!kIsWeb && !Platform.isLinux && !Platform.isMacOS && !Platform.isWindows)
|
if (!kIsWeb &&
|
||||||
|
!Platform.isLinux &&
|
||||||
|
!Platform.isMacOS &&
|
||||||
|
!Platform.isWindows)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
@ -545,7 +575,10 @@ class AddPostMediaButton extends StatelessWidget {
|
|||||||
_takeMedia(false);
|
_takeMedia(false);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (!kIsWeb && !Platform.isLinux && !Platform.isMacOS && !Platform.isWindows)
|
if (!kIsWeb &&
|
||||||
|
!Platform.isLinux &&
|
||||||
|
!Platform.isMacOS &&
|
||||||
|
!Platform.isWindows)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
Loading…
x
Reference in New Issue
Block a user