├── README.md ├── screenshots └── main-page.png └── src └── lib ├── clipboard_channel.dart ├── main.dart └── settings └── settings.dart /README.md: -------------------------------------------------------------------------------- 1 | # DaClipbd 2 | 3 | A clipboard history app for macos from [DeskAngel](http://twitter.com/ideskangel). 4 | 5 | Download: Go to [release](https://github.com/deskangel/DaClipbd/releases/) to download. 6 | 7 | ## Screenshots 8 | ![main-page](screenshots/main-page.png) 9 | 10 | ## Usage 11 | ### Keybinding 12 | Show/Hide window(default): CMD + SHIFT + V 13 | 14 | ## Features: 15 | * Clipboard history 16 | * Double-click item to copy to system clipboard 17 | * Show the application icon and id that the content is from 18 | * Rich format preview 19 | * Keep syntax highlighting for source code 20 | 21 | -------------------------------------------------------------------------------- /screenshots/main-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deskangel/DaClipbd/68260cb7fb5fa004072aef23b3478c7d20a18ce8/screenshots/main-page.png -------------------------------------------------------------------------------- /src/lib/clipboard_channel.dart: -------------------------------------------------------------------------------- 1 | import 'package:daclipbd/utils.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | typedef Callback = void Function(dynamic result); 5 | 6 | class ClipboardChannel { 7 | static const MethodChannel _channel = MethodChannel('clipboard'); 8 | static const EventChannel _eventChannel = EventChannel('clipboard_event'); 9 | 10 | Callback? _monitingCallback; 11 | 12 | ClipboardChannel() { 13 | _eventChannel.receiveBroadcastStream().listen((event) { 14 | if (this._monitingCallback != null) { 15 | this._monitingCallback!(event); 16 | } 17 | }); 18 | } 19 | 20 | Future hideWindow() async { 21 | var result = await _channel.invokeMethod('hideWindow'); 22 | 23 | return result; 24 | } 25 | 26 | Future startMonitoring(Callback callback) async { 27 | this._monitingCallback = callback; 28 | 29 | var result = await _channel.invokeMethod('startMonitoring'); 30 | 31 | return result; 32 | } 33 | 34 | Future stopMonitoring() async { 35 | this._monitingCallback = null; 36 | 37 | var result = await _channel.invokeMethod('stopMonitoring'); 38 | 39 | return result; 40 | } 41 | 42 | Future setToClipboard({required Map data}) async { 43 | var result = await _channel.invokeMethod('setToClipboard', data); 44 | logger.d('set to clipboard: $result'); 45 | return result; 46 | } 47 | 48 | /////////////////////////////////////// 49 | ///settings 50 | 51 | Future enableBlurWindow() async { 52 | var result = await _channel.invokeMethod('enableBlurWindow'); 53 | 54 | return result; 55 | } 56 | 57 | Future setWindowRectAccordingScreen() async { 58 | var result = await _channel.invokeMethod('setWindowRectAccordingScreen'); 59 | 60 | return result; 61 | } 62 | 63 | Future showIconOnDock(bool flag) async { 64 | var result = await _channel.invokeMethod('showDockIcon', flag); 65 | return result; 66 | } 67 | 68 | Future setPositionValue(int pos) async { 69 | var result = await _channel.invokeMethod('setPositionValue', pos); 70 | return result; 71 | } 72 | 73 | Future setWindowSize(Rect rect) async { 74 | var result = await _channel.invokeMethod( 75 | 'setWindowSize', 76 | {'width': rect.width, 'height': rect.height, 'x': rect.left, 'y': rect.top - rect.height}, 77 | ); 78 | return result; 79 | } 80 | 81 | Future getScreenRect() async { 82 | final screen = await _channel.invokeMethod('getScreenRect'); 83 | if (screen is List && screen.length == 4) { 84 | return Rect.fromLTWH(screen[0], screen[1] + screen[3], screen[2], screen[3]); 85 | } 86 | throw screen; 87 | } 88 | 89 | Future launchAtLogin(bool flag) async { 90 | var result = await _channel.invokeMethod('launchAtLogin', flag); 91 | return result == 'success'; 92 | } 93 | 94 | Future getAppIcon(String bundleId) async { 95 | var result = await _channel.invokeMethod('getAppIcon', bundleId); 96 | return result; 97 | } 98 | 99 | Future getFileIcon(String path) async { 100 | var result = await _channel.invokeMethod('getFileIcon', path); 101 | return result; 102 | } 103 | 104 | Future hidesOnDeactivate(bool flag) async { 105 | await _channel.invokeMethod('hidesOnDeactivate', flag); 106 | } 107 | 108 | Future setShowWindowHotkey(String key, int modifiers) async { 109 | return await _channel.invokeMethod('setShowWindowHotkey',{'key': key, 'modifiers': modifiers}); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:da_utils/da_utils.dart'; 2 | import 'package:daclipbd/model/clipboard_controller.dart'; 3 | import 'package:daclipbd/model/dbhandler.dart'; 4 | import 'package:daclipbd/model/premium.dart'; 5 | import 'package:daclipbd/model/search_notifier.dart'; 6 | import 'package:daclipbd/model/theme_notifier.dart'; 7 | import 'package:daclipbd/settings/settings.dart'; 8 | import 'package:daclipbd/view/home.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:flutter/services.dart'; 11 | import 'package:provider/provider.dart'; 12 | 13 | void main(List args) async { 14 | WidgetsFlutterBinding.ensureInitialized(); 15 | 16 | await Settings.instance.init(); 17 | await DbHandler.instance.init(); 18 | 19 | await ClipboardController.instance.showIconOnDock(Settings.instance.showIconOnDock); 20 | await ClipboardController.instance.setWindowParkPosition(Settings.instance.position); 21 | await ClipboardController.instance.setShowWindowHotkey(Settings.instance.keybinding); 22 | 23 | await Premium.instance.initFirstRun(); 24 | 25 | runApp(const DaClipBoard()); 26 | } 27 | 28 | class HideWindow extends Intent { 29 | static void hideWindow() { 30 | debugPrint('hideWindow'); 31 | ClipboardController.instance.hideWindow(); 32 | } 33 | } 34 | 35 | class SearchItem extends Intent { 36 | static void search(BuildContext context) { 37 | debugPrint('search'); 38 | Provider.of(context, listen: false).searching = true; 39 | } 40 | } 41 | 42 | class OpenPreference extends Intent { 43 | static void openPreference() { 44 | EventBus.instance.fire(eventId: 'OPEN_PREFERENCE_EVENT'); 45 | } 46 | } 47 | 48 | class DaClipBoard extends StatelessWidget { 49 | const DaClipBoard({super.key}); 50 | 51 | @override 52 | Widget build(BuildContext context) { 53 | return MultiProvider( 54 | providers: [ 55 | ChangeNotifierProvider(create: (context) => ThemeChangeNotifier()), 56 | ChangeNotifierProvider(create: (context) => SearchNotifier()), 57 | ], 58 | child: Consumer( 59 | builder: (context, notifier, child) => MaterialApp( 60 | title: 'DaClipBoard', 61 | debugShowCheckedModeBanner: false, 62 | theme: ThemeData.light(), 63 | darkTheme: ThemeData.dark(), 64 | themeMode: notifier.themeMode, 65 | shortcuts: { 66 | LogicalKeySet(LogicalKeyboardKey.escape): HideWindow(), 67 | LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.keyF): SearchItem(), 68 | LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.comma): OpenPreference(), 69 | }, 70 | actions: { 71 | HideWindow: CallbackAction(onInvoke: (intent) => HideWindow.hideWindow()), 72 | SearchItem: CallbackAction(onInvoke: (intent) => SearchItem.search(context)), 73 | OpenPreference: CallbackAction(onInvoke: (intent) => OpenPreference.openPreference()), 74 | }, 75 | home: const HomePage(), 76 | ), 77 | ), 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/lib/settings/settings.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:daclipbd/model/keybinding.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:shared_preferences/shared_preferences.dart'; 6 | 7 | enum ParkPosition { LEFT, TOP, RIGHT, BOTTOM, WITH_CURSOR } 8 | 9 | class Settings { 10 | factory Settings() => _getInstance(); 11 | static Settings get instance => _getInstance(); 12 | static Settings? _instance; 13 | 14 | Settings._internal(); 15 | 16 | static Settings _getInstance() { 17 | _instance ??= Settings._internal(); 18 | 19 | return _instance!; 20 | } 21 | 22 | bool get isDebug => kDebugMode; 23 | 24 | late SharedPreferences prefs; 25 | Future init() async { 26 | this.prefs = await SharedPreferences.getInstance(); 27 | 28 | _enableBlur = this.prefs.getBool('enableBlur') ?? false; 29 | _showIconOnDock = this.prefs.getBool('showIconOnDock') ?? false; 30 | _launchAtLogin = this.prefs.getBool('launchAtLogin') ?? false; 31 | _position = ParkPosition.values[this.prefs.getInt('position') ?? ParkPosition.BOTTOM.index]; 32 | 33 | _historyCount = this.prefs.getInt('historyCount') ?? 10; 34 | _keepDuplicates = this.prefs.getBool('keepDuplicates') ?? true; 35 | 36 | _keybinding = Keybinding.fromJsonString(this.prefs.getString('keybinding') ?? '{}'); 37 | 38 | themeMode = this.prefs.getInt('themeMode') ?? _themeMode; 39 | } 40 | 41 | //////////////////////////////////////////////////////////////////// 42 | /// window and ui settings 43 | /// 44 | bool? _enableBlur; 45 | bool get enableBlur => _enableBlur ?? false; 46 | set enableBlur(bool enableBlur) { 47 | _enableBlur = enableBlur; 48 | 49 | this.prefs.setBool('enableBlur', enableBlur); 50 | } 51 | 52 | bool? _showIconOnDock; 53 | bool get showIconOnDock => _showIconOnDock ?? false; 54 | set showIconOnDock(bool showIconOnDock) { 55 | _showIconOnDock = showIconOnDock; 56 | 57 | this.prefs.setBool('showIconOnDock', showIconOnDock); 58 | } 59 | 60 | bool? _launchAtLogin; 61 | bool get launchAtLogin => _launchAtLogin ?? false; 62 | set launchAtLogin(bool launchAtLogin) { 63 | _launchAtLogin = launchAtLogin; 64 | this.prefs.setBool('launchAtLogin', launchAtLogin); 65 | } 66 | 67 | ParkPosition? _position; 68 | ParkPosition get position => _position ?? ParkPosition.BOTTOM; 69 | set position(ParkPosition position) { 70 | _position = position; 71 | this.prefs.setInt('position', position.index); 72 | } 73 | 74 | int _themeMode = 0; 75 | int get themeMode => _themeMode; 76 | set themeMode(int value) { 77 | _themeMode = value; 78 | this.prefs.setInt('themeMode', value); 79 | } 80 | 81 | //////////////////////////////////////////////////////////////////// 82 | /// clipboard related settings 83 | 84 | // the last one means infinity 85 | static const HISTORY_COUNTS = [10, 100, 200, 300, 400]; 86 | 87 | static const int COPYRIGHT_DATE = 2024; 88 | 89 | /// 90 | /// 0: infinite 91 | /// 92 | int? _historyCount; 93 | int get historyCount => (_historyCount == null || _historyCount! < 10) ? 10 : _historyCount!; 94 | set historyCount(int historyCount) { 95 | _historyCount = historyCount; 96 | this.prefs.setInt('historyCount', historyCount); 97 | } 98 | 99 | bool get isInfinity => historyCount >= HISTORY_COUNTS.last; 100 | 101 | /// keep duplicated items or not 102 | bool? _keepDuplicates; 103 | bool get keepDuplicates => _keepDuplicates ?? true; 104 | set keepDuplicates(bool value) { 105 | _keepDuplicates = value; 106 | this.prefs.setBool('keepDuplicates', value); 107 | } 108 | 109 | Keybinding? _keybinding; 110 | Keybinding get keybinding => _keybinding ?? Keybinding(); 111 | set keybinding(Keybinding value) { 112 | _keybinding = value; 113 | this.prefs.setString('keybinding', jsonEncode(value)); 114 | } 115 | } 116 | 117 | --------------------------------------------------------------------------------