🐛 Fixes attachments

This commit is contained in:
2025-08-08 15:04:50 +08:00
parent fa210dd98f
commit 43c90da4e3
4 changed files with 162 additions and 162 deletions

View File

@@ -1061,14 +1061,14 @@ class _ChatInput extends HookConsumerWidget {
children: [ children: [
if (attachments.isNotEmpty) if (attachments.isNotEmpty)
SizedBox( SizedBox(
height: 324, height: 280,
child: ListView.separated( child: ListView.separated(
padding: EdgeInsets.symmetric(horizontal: 12), padding: EdgeInsets.symmetric(horizontal: 12),
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
itemCount: attachments.length, itemCount: attachments.length,
itemBuilder: (context, idx) { itemBuilder: (context, idx) {
return SizedBox( return SizedBox(
height: 320, height: 280,
width: 280, width: 280,
child: AttachmentPreview( child: AttachmentPreview(
item: attachments[idx], item: attachments[idx],

View File

@@ -367,63 +367,63 @@ class PostComposeScreen extends HookConsumerWidget {
// Post content form // Post content form
Expanded( Expanded(
child: SingleChildScrollView( child: KeyboardListener(
padding: const EdgeInsets.symmetric(vertical: 16), focusNode: FocusNode(),
child: Column( onKeyEvent:
crossAxisAlignment: CrossAxisAlignment.start, (event) => ComposeLogic.handleKeyPress(
children: [ event,
TextField( state,
controller: state.titleController, ref,
decoration: InputDecoration( context,
hintText: 'postTitle'.tr(), originalPost: originalPost,
border: InputBorder.none, repliedPost: repliedPost,
isCollapsed: true, forwardedPost: forwardedPost,
contentPadding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 8,
),
),
style: theme.textTheme.titleMedium,
onTapOutside:
(_) =>
FocusManager.instance.primaryFocus
?.unfocus(),
), ),
TextField( child: SingleChildScrollView(
controller: state.descriptionController, padding: const EdgeInsets.symmetric(vertical: 16),
decoration: InputDecoration( child: Column(
hintText: 'postDescription'.tr(), crossAxisAlignment: CrossAxisAlignment.start,
border: InputBorder.none, children: [
isCollapsed: true, TextField(
contentPadding: const EdgeInsets.fromLTRB( controller: state.titleController,
8, decoration: InputDecoration(
4, hintText: 'postTitle'.tr(),
8, border: InputBorder.none,
12, isCollapsed: true,
), contentPadding: const EdgeInsets.symmetric(
), vertical: 8,
style: theme.textTheme.bodyMedium, horizontal: 8,
minLines: 1,
maxLines: 3,
onTapOutside:
(_) =>
FocusManager.instance.primaryFocus
?.unfocus(),
),
// Content field with borderless design
KeyboardListener(
focusNode: FocusNode(),
onKeyEvent:
(event) => ComposeLogic.handleKeyPress(
event,
state,
ref,
context,
originalPost: originalPost,
repliedPost: repliedPost,
forwardedPost: forwardedPost,
), ),
child: TextField( ),
style: theme.textTheme.titleMedium,
onTapOutside:
(_) =>
FocusManager.instance.primaryFocus
?.unfocus(),
),
TextField(
controller: state.descriptionController,
decoration: InputDecoration(
hintText: 'postDescription'.tr(),
border: InputBorder.none,
isCollapsed: true,
contentPadding: const EdgeInsets.fromLTRB(
8,
4,
8,
12,
),
),
style: theme.textTheme.bodyMedium,
minLines: 1,
maxLines: 3,
onTapOutside:
(_) =>
FocusManager.instance.primaryFocus
?.unfocus(),
),
// Content field with borderless design
TextField(
controller: state.contentController, controller: state.contentController,
style: theme.textTheme.bodyMedium, style: theme.textTheme.bodyMedium,
decoration: InputDecoration( decoration: InputDecoration(
@@ -441,23 +441,23 @@ class PostComposeScreen extends HookConsumerWidget {
FocusManager.instance.primaryFocus FocusManager.instance.primaryFocus
?.unfocus(), ?.unfocus(),
), ),
),
const Gap(8), const Gap(8),
// Attachments preview // Attachments preview
if (state.attachments.value.isNotEmpty) if (state.attachments.value.isNotEmpty)
LayoutBuilder( LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
final isWide = isWideScreen(context); final isWide = isWideScreen(context);
return isWide return isWide
? buildWideAttachmentGrid() ? buildWideAttachmentGrid()
: buildNarrowAttachmentList(); : buildNarrowAttachmentList();
}, },
) )
else else
const SizedBox.shrink(), const SizedBox.shrink(),
], ],
),
), ),
), ),
), ),

View File

@@ -272,8 +272,96 @@ class AttachmentPreview extends HookConsumerWidget {
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
child: Container( child: Container(
color: Theme.of(context).colorScheme.surfaceContainer, color: Theme.of(context).colorScheme.surfaceContainer,
child: Column( child: Stack(
children: [ children: [
AspectRatio(
aspectRatio: ratio,
child: Stack(
fit: StackFit.expand,
children: [
Builder(
key: ValueKey(item.hashCode),
builder: (context) {
if (item.isOnCloud) {
return CloudFileWidget(item: item.data);
} else if (item.data is XFile) {
final file = item.data as XFile;
if (file.path.isEmpty) {
return FutureBuilder<Uint8List>(
future: file.readAsBytes(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Image.memory(snapshot.data!);
}
return const Center(
child: CircularProgressIndicator(),
);
},
);
}
switch (item.type) {
case UniversalFileType.image:
return kIsWeb
? Image.network(file.path)
: Image.file(File(file.path));
default:
return Column(
children: [
const Icon(Symbols.document_scanner),
Text(file.name),
],
);
}
} else if (item is List<int> || item is Uint8List) {
switch (item.type) {
case UniversalFileType.image:
return Image.memory(item.data);
default:
return Column(
children: [const Icon(Symbols.document_scanner)],
);
}
}
return Placeholder();
},
),
if (progress != null)
Positioned.fill(
child: Container(
color: Colors.black.withOpacity(0.3),
padding: EdgeInsets.symmetric(
horizontal: 40,
vertical: 16,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (progress != null)
Text(
'${progress!.toStringAsFixed(2)}%',
style: TextStyle(color: Colors.white),
)
else
Text(
'uploading'.tr(),
style: TextStyle(color: Colors.white),
),
Gap(6),
Center(
child: LinearProgressIndicator(
value:
progress != null ? progress! / 100.0 : null,
),
),
],
),
),
),
],
),
),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@@ -397,94 +485,6 @@ class AttachmentPreview extends HookConsumerWidget {
), ),
], ],
).padding(horizontal: 12, vertical: 8), ).padding(horizontal: 12, vertical: 8),
AspectRatio(
aspectRatio: ratio,
child: Stack(
fit: StackFit.expand,
children: [
Builder(
key: ValueKey(item.hashCode),
builder: (context) {
if (item.isOnCloud) {
return CloudFileWidget(item: item.data);
} else if (item.data is XFile) {
final file = item.data as XFile;
if (file.path.isEmpty) {
return FutureBuilder<Uint8List>(
future: file.readAsBytes(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Image.memory(snapshot.data!);
}
return const Center(
child: CircularProgressIndicator(),
);
},
);
}
switch (item.type) {
case UniversalFileType.image:
return kIsWeb
? Image.network(file.path)
: Image.file(File(file.path));
default:
return Column(
children: [
const Icon(Symbols.document_scanner),
Text(file.name),
],
);
}
} else if (item is List<int> || item is Uint8List) {
switch (item.type) {
case UniversalFileType.image:
return Image.memory(item.data);
default:
return Column(
children: [const Icon(Symbols.document_scanner)],
);
}
}
return Placeholder();
},
),
if (progress != null)
Positioned.fill(
child: Container(
color: Colors.black.withOpacity(0.3),
padding: EdgeInsets.symmetric(
horizontal: 40,
vertical: 16,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (progress != null)
Text(
'${progress!.toStringAsFixed(2)}%',
style: TextStyle(color: Colors.white),
)
else
Text(
'uploading'.tr(),
style: TextStyle(color: Colors.white),
),
Gap(6),
Center(
child: LinearProgressIndicator(
value:
progress != null ? progress! / 100.0 : null,
),
),
],
),
),
),
],
),
),
], ],
), ),
), ),

View File

@@ -697,7 +697,7 @@ class ComposeLogic {
SnPost? repliedPost, SnPost? repliedPost,
SnPost? forwardedPost, SnPost? forwardedPost,
}) { }) {
if (event is! RawKeyDownEvent) return; if (event is! KeyDownEvent) return;
final isPaste = event.logicalKey == LogicalKeyboardKey.keyV; final isPaste = event.logicalKey == LogicalKeyboardKey.keyV;
final isSave = event.logicalKey == LogicalKeyboardKey.keyS; final isSave = event.logicalKey == LogicalKeyboardKey.keyS;