Skip to content

Commit

Permalink
增加预览功能
Browse files Browse the repository at this point in the history
  • Loading branch information
DargonLee committed Jan 8, 2025
1 parent 186656f commit c6825bb
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 55 deletions.
13 changes: 6 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,15 @@ sudo dpkg -i easy_pasta_linux_amd64.deb
- 点击收藏按钮,将内容添加到收藏列表
- 点击删除按钮,删除选中的内容

5. **关闭窗口**
5. **预览**

- 鼠标放在卡片上,按空格键预览

6. **关闭窗口**

- 使用快捷键 `Cmd+W` (macOS) / `Ctrl+W` (Windows/Linux)

5. **退出应用**
7. **退出应用**

- 使用快捷键 `Cmd+Q` (macOS) / `Ctrl+Q` (Windows/Linux)

Expand All @@ -120,11 +124,6 @@ sudo dpkg -i easy_pasta_linux_amd64.deb
- **启动选项**: 设置开机自启动
- **历史记录**: 配置历史记录保存数量

## TODO

- [ ] 支持 rtf 格式
- [ ] 选中点击空格预览

## 📄 许可证

本项目基于 MIT 许可证开源 - 查看 [LICENSE](LICENSE) 文件了解更多详情
Expand Down
119 changes: 72 additions & 47 deletions lib/page/grid_view.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import 'package:easy_pasta/model/clipboard_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:easy_pasta/model/pasteboard_model.dart';
import 'package:easy_pasta/page/pboard_card_view.dart';
import 'package:easy_pasta/page/empty_view.dart';
import 'package:easy_pasta/widget/preview_dialog.dart';

class PasteboardGridView extends StatefulWidget {
static const double _kGridSpacing = 8.0;
Expand Down Expand Up @@ -33,16 +36,20 @@ class PasteboardGridView extends StatefulWidget {
class _PasteboardGridViewState extends State<PasteboardGridView>
with AutomaticKeepAliveClientMixin {
final ScrollController _scrollController = ScrollController();

@override
bool get wantKeepAlive => true;
ClipboardItemModel? _hoveredItem;
final FocusNode _focusNode = FocusNode();

@override
void dispose() {
_scrollController.dispose();
_focusNode.dispose();
super.dispose();
}

void _showPreviewDialog(BuildContext context, ClipboardItemModel model) {
PreviewDialog.show(context, model);
}

@override
Widget build(BuildContext context) {
super.build(context);
Expand All @@ -51,54 +58,72 @@ class _PasteboardGridViewState extends State<PasteboardGridView>
return const EmptyStateView();
}

return LayoutBuilder(
builder: (context, constraints) {
// 计算每行最多能显示多少列
final maxColumns =
(constraints.maxWidth / PasteboardGridView._kMinCrossAxisExtent)
.floor();
// 限制列数在1-3之间
final columns = maxColumns.clamp(1, 3);
return KeyboardListener(
focusNode: _focusNode,
onKeyEvent: (event) {
if (event.logicalKey == LogicalKeyboardKey.space &&
_hoveredItem != null) {
_showPreviewDialog(context, _hoveredItem!);
}
},
child: LayoutBuilder(
builder: (context, constraints) {
final maxColumns =
(constraints.maxWidth / PasteboardGridView._kMinCrossAxisExtent)
.floor()
.clamp(1, 3);

// 根据列数计算实际的item宽度
final itemWidth = (constraints.maxWidth -
(columns - 1) * PasteboardGridView._kGridSpacing) /
columns;
// 设置宽高比
final aspectRatio = itemWidth / (itemWidth / 1.2);
final itemWidth = (constraints.maxWidth -
(maxColumns - 1) * PasteboardGridView._kGridSpacing) /
maxColumns;
final aspectRatio = itemWidth / (itemWidth / 1.2);

return Scrollbar(
controller: _scrollController,
child: GridView.builder(
key: const PageStorageKey<String>('pasteboard_grid'),
return Scrollbar(
controller: _scrollController,
physics: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics(),
child: GridView.builder(
key: const PageStorageKey<String>('pasteboard_grid'),
controller: _scrollController,
physics: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics(),
),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: maxColumns,
mainAxisSpacing: PasteboardGridView._kGridSpacing,
crossAxisSpacing: PasteboardGridView._kGridSpacing,
childAspectRatio: aspectRatio,
),
cacheExtent: 1000,
itemCount: widget.pboards.length,
itemBuilder: (context, index) {
final model = widget.pboards[index];
return MouseRegion(
onEnter: (_) {
setState(() => _hoveredItem = model);
_focusNode.requestFocus();
},
onExit: (_) {
setState(() => _hoveredItem = null);
_focusNode.unfocus();
},
child: NewPboardItemCard(
key: ValueKey(model.id),
model: model,
selectedId: widget.selectedId,
onTap: widget.onItemTap,
onDoubleTap: widget.onItemDoubleTap,
onCopy: widget.onCopy,
onFavorite: widget.onFavorite,
onDelete: widget.onDelete,
),
);
},
),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: columns,
mainAxisSpacing: PasteboardGridView._kGridSpacing,
crossAxisSpacing: PasteboardGridView._kGridSpacing,
childAspectRatio: aspectRatio,
),
cacheExtent: 1000,
itemCount: widget.pboards.length,
itemBuilder: (context, index) {
final model = widget.pboards[index];
return NewPboardItemCard(
key: ValueKey(model.id),
model: model,
selectedId: widget.selectedId,
onTap: widget.onItemTap,
onDoubleTap: widget.onItemDoubleTap,
onCopy: widget.onCopy,
onFavorite: widget.onFavorite,
onDelete: widget.onDelete,
);
},
),
);
},
);
},
),
);
}

@override
bool get wantKeepAlive => true;
}
2 changes: 1 addition & 1 deletion lib/page/pboard_card_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class NewPboardItemCard extends StatelessWidget {
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: isSelected
? BorderSide(color: Theme.of(context).primaryColor, width: 1)
? const BorderSide(color: Colors.blue, width: 1)
: BorderSide.none,
),
child: InkWell(
Expand Down
122 changes: 122 additions & 0 deletions lib/widget/preview_dialog.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import 'package:flutter/material.dart';
import 'package:easy_pasta/model/pasteboard_model.dart';
import 'package:easy_pasta/model/clipboard_type.dart';

class PreviewDialog extends StatelessWidget {
final ClipboardItemModel model;

const PreviewDialog({
super.key,
required this.model,
});

static Future<void> show(BuildContext context, ClipboardItemModel model) {
return showDialog(
context: context,
builder: (context) => PreviewDialog(model: model),
);
}

Widget _buildContent(BuildContext context) {
final theme = Theme.of(context);

switch (model.ptype) {
case ClipboardType.image:
return Center(
child: Image.memory(
model.imageBytes!,
fit: BoxFit.contain,
),
);
case ClipboardType.html:
case ClipboardType.text:
case ClipboardType.file:
default:
return SelectableText(
model.pvalue,
style: theme.textTheme.bodyMedium?.copyWith(
height: 1.5,
letterSpacing: 0.3,
),
);
}
}

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final isDark = theme.brightness == Brightness.dark;

return Dialog(
backgroundColor: isDark ? Colors.grey[900] : Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Container(
constraints: BoxConstraints(
maxWidth: model.ptype == ClipboardType.image ? 800 : 600,
maxHeight: model.ptype == ClipboardType.image ? 600 : 400,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 标题栏
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: isDark ? Colors.grey[800]! : Colors.grey[200]!,
width: 1,
),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'预览',
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w500,
),
),
IconButton(
icon: Icon(
Icons.close,
size: 20,
color: isDark ? Colors.white70 : Colors.black54,
),
onPressed: () => Navigator.of(context).pop(),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(
minWidth: 32,
minHeight: 32,
),
style: IconButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
),
),
],
),
),
// 内容区域
Expanded(
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(
parent: BouncingScrollPhysics(),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: _buildContent(context),
),
),
),
],
),
),
);
}
}

0 comments on commit c6825bb

Please sign in to comment.