feat: polish app icons and presentation exports
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 3 KiB |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 464 KiB After Width: | Height: | Size: 518 KiB |
|
|
@ -1 +1,2 @@
|
|||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
|
|
|
|||
43
ios/Podfile
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# Uncomment this line to define a global platform for your project
|
||||
# platform :ios, '13.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
project 'Runner', {
|
||||
'Debug' => :debug,
|
||||
'Profile' => :release,
|
||||
'Release' => :release,
|
||||
}
|
||||
|
||||
def flutter_root
|
||||
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
|
||||
unless File.exist?(generated_xcode_build_settings_path)
|
||||
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
|
||||
end
|
||||
|
||||
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||
return matches[1].strip if matches
|
||||
end
|
||||
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
|
||||
end
|
||||
|
||||
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||
|
||||
flutter_ios_podfile_setup
|
||||
|
||||
target 'Runner' do
|
||||
use_frameworks!
|
||||
|
||||
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||
target 'RunnerTests' do
|
||||
inherit! :search_paths
|
||||
end
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_ios_build_settings(target)
|
||||
end
|
||||
end
|
||||
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 418 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 811 B |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 5 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 16 KiB |
|
|
@ -25,6 +25,11 @@ class ThemeProfile {
|
|||
/// Horizontale positie van de footer: left, center of right.
|
||||
final String footerPosition;
|
||||
|
||||
/// Optional markdown slide that is appended when presenting/exporting with
|
||||
/// this theme profile. It stays out of the editable deck slide list.
|
||||
final bool closingSlideEnabled;
|
||||
final String closingSlideMarkdown;
|
||||
|
||||
const ThemeProfile({
|
||||
this.name = 'Standaard',
|
||||
this.slideBackgroundColor = '#FFFFFF',
|
||||
|
|
@ -42,6 +47,8 @@ class ThemeProfile {
|
|||
this.footerText = '',
|
||||
this.footerShowPageNumbers = false,
|
||||
this.footerPosition = 'right',
|
||||
this.closingSlideEnabled = false,
|
||||
this.closingSlideMarkdown = '# Bedankt\n\nVragen?',
|
||||
}) : tableTextColor = tableTextColor ?? textColor;
|
||||
|
||||
static const logoPositions = [
|
||||
|
|
@ -70,6 +77,8 @@ class ThemeProfile {
|
|||
String? footerText,
|
||||
bool? footerShowPageNumbers,
|
||||
String? footerPosition,
|
||||
bool? closingSlideEnabled,
|
||||
String? closingSlideMarkdown,
|
||||
bool clearLogo = false,
|
||||
}) {
|
||||
return ThemeProfile(
|
||||
|
|
@ -91,6 +100,8 @@ class ThemeProfile {
|
|||
footerShowPageNumbers:
|
||||
footerShowPageNumbers ?? this.footerShowPageNumbers,
|
||||
footerPosition: footerPosition ?? this.footerPosition,
|
||||
closingSlideEnabled: closingSlideEnabled ?? this.closingSlideEnabled,
|
||||
closingSlideMarkdown: closingSlideMarkdown ?? this.closingSlideMarkdown,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -112,6 +123,8 @@ class ThemeProfile {
|
|||
'footerText': footerText,
|
||||
'footerShowPageNumbers': footerShowPageNumbers,
|
||||
'footerPosition': footerPosition,
|
||||
'closingSlideEnabled': closingSlideEnabled,
|
||||
'closingSlideMarkdown': closingSlideMarkdown,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -140,6 +153,9 @@ class ThemeProfile {
|
|||
footerText: json['footerText'] as String? ?? '',
|
||||
footerShowPageNumbers: json['footerShowPageNumbers'] as bool? ?? false,
|
||||
footerPosition: json['footerPosition'] as String? ?? 'right',
|
||||
closingSlideEnabled: json['closingSlideEnabled'] as bool? ?? false,
|
||||
closingSlideMarkdown:
|
||||
json['closingSlideMarkdown'] as String? ?? '# Bedankt\n\nVragen?',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ class SlideRasterizer {
|
|||
TlpLevel tlp = TlpLevel.none,
|
||||
int targetWidth = 1920,
|
||||
void Function(int done, int total)? onProgress,
|
||||
void Function(String phase, int done, int total)? onStage,
|
||||
}) async {
|
||||
final overlay = Overlay.of(context, rootOverlay: true);
|
||||
final pixelRatio = targetWidth / logicalSize.width;
|
||||
|
|
@ -54,6 +55,7 @@ class SlideRasterizer {
|
|||
final results = <Uint8List>[];
|
||||
try {
|
||||
for (var i = 0; i < slides.length; i++) {
|
||||
onStage?.call('prepare', i, slides.length);
|
||||
// Warm this slide's images immediately before capturing it. Doing it
|
||||
// per slide (instead of once up front) guarantees the bitmap is decoded
|
||||
// and resident in the cache at capture time, no matter how many images
|
||||
|
|
@ -66,6 +68,7 @@ class SlideRasterizer {
|
|||
]);
|
||||
if (!context.mounted) break;
|
||||
|
||||
onStage?.call('render', i, slides.length);
|
||||
final key = GlobalKey();
|
||||
final entry = OverlayEntry(
|
||||
builder: (_) => Positioned(
|
||||
|
|
@ -96,6 +99,7 @@ class SlideRasterizer {
|
|||
} finally {
|
||||
entry.remove();
|
||||
}
|
||||
onStage?.call('done', i + 1, slides.length);
|
||||
onProgress?.call(i + 1, slides.length);
|
||||
}
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -138,6 +138,19 @@ List<String> _imageUsages(WidgetRef ref, String absolutePath) {
|
|||
return usages;
|
||||
}
|
||||
|
||||
List<Slide> _slidesForPresentationOrExport(Deck deck) {
|
||||
final slides = deck.slides.where((s) => !s.skipped).toList();
|
||||
final closingMarkdown = deck.themeProfile.closingSlideMarkdown.trim();
|
||||
if (deck.themeProfile.closingSlideEnabled && closingMarkdown.isNotEmpty) {
|
||||
slides.add(
|
||||
Slide.create(
|
||||
SlideType.freeMarkdown,
|
||||
).copyWith(customMarkdown: closingMarkdown),
|
||||
);
|
||||
}
|
||||
return slides;
|
||||
}
|
||||
|
||||
// ── App shell ─────────────────────────────────────────────────────────────────
|
||||
|
||||
class AppShell extends ConsumerStatefulWidget {
|
||||
|
|
@ -904,7 +917,8 @@ class _MainLayoutState extends ConsumerState<_MainLayout> {
|
|||
for (var i = 0; i < deck.slides.length; i++)
|
||||
if (!deck.slides[i].skipped) i,
|
||||
];
|
||||
if (visible.isEmpty) {
|
||||
final slides = _slidesForPresentationOrExport(deck);
|
||||
if (slides.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
|
|
@ -916,9 +930,10 @@ class _MainLayoutState extends ConsumerState<_MainLayout> {
|
|||
}
|
||||
var initial = visible.indexWhere((i) => i >= editor.selectedIndex);
|
||||
if (initial < 0) initial = visible.length - 1;
|
||||
if (initial < 0) initial = 0;
|
||||
FullscreenPresenter.show(
|
||||
context,
|
||||
slides: [for (final i in visible) deck.slides[i]],
|
||||
slides: slides,
|
||||
projectPath: deck.projectPath,
|
||||
themeProfile: deck.themeProfile,
|
||||
initialIndex: initial,
|
||||
|
|
@ -927,7 +942,7 @@ class _MainLayoutState extends ConsumerState<_MainLayout> {
|
|||
}
|
||||
|
||||
void exportDeck() {
|
||||
final slides = deck.slides.where((s) => !s.skipped).toList();
|
||||
final slides = _slidesForPresentationOrExport(deck);
|
||||
if (slides.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
|
|
@ -947,7 +962,9 @@ class _MainLayoutState extends ConsumerState<_MainLayout> {
|
|||
exportService: widget.exportService,
|
||||
tlp: deck.tlp,
|
||||
exportDirectory: ref.read(settingsProvider).exportDirectory,
|
||||
markdown: deckNotifier.generateMarkdown(),
|
||||
markdown: ref
|
||||
.read(markdownServiceProvider)
|
||||
.generateDeck(deck.copyWith(slides: slides)),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -91,6 +91,12 @@ class _ExportDialogState extends State<ExportDialog> {
|
|||
_total = needsRaster ? widget.slides.length : 0;
|
||||
});
|
||||
|
||||
// Give the dialog a frame to paint before the potentially expensive first
|
||||
// image decode/raster pass starts.
|
||||
await WidgetsBinding.instance.endOfFrame;
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
if (!mounted) return;
|
||||
|
||||
final images = needsRaster
|
||||
? await SlideRasterizer.rasterize(
|
||||
context: context,
|
||||
|
|
@ -101,6 +107,14 @@ class _ExportDialogState extends State<ExportDialog> {
|
|||
onProgress: (done, total) {
|
||||
if (mounted) setState(() => _done = done);
|
||||
},
|
||||
onStage: (phase, done, total) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_phase = _stageText(phase, done, total);
|
||||
_done = done;
|
||||
_total = total;
|
||||
});
|
||||
},
|
||||
)
|
||||
: const <Uint8List>[];
|
||||
|
||||
|
|
@ -129,6 +143,23 @@ class _ExportDialogState extends State<ExportDialog> {
|
|||
});
|
||||
}
|
||||
|
||||
String _stageText(String phase, int done, int total) {
|
||||
final l10n = context.l10n;
|
||||
final number = (done + 1).clamp(1, total);
|
||||
switch (phase) {
|
||||
case 'prepare':
|
||||
return '${l10n.d('Slide')} $number ${l10n.d('voorbereiden…')}';
|
||||
case 'render':
|
||||
return '${l10n.d('Slide')} $number ${l10n.d('renderen…')}';
|
||||
case 'done':
|
||||
return done >= total
|
||||
? l10n.d('Slides gerenderd.')
|
||||
: '${l10n.d('Slide')} $done ${l10n.d('gerenderd.')}';
|
||||
default:
|
||||
return l10n.t('renderingSlides');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
|||
late TextEditingController _profileName;
|
||||
late TextEditingController _logoSize;
|
||||
late TextEditingController _footerText;
|
||||
late TextEditingController _closingSlideMarkdown;
|
||||
|
||||
/// Whether the user changed the active profile in this session. Used to
|
||||
/// decide whether to apply the profile to the currently open presentation.
|
||||
|
|
@ -74,6 +75,9 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
|||
_profileName = TextEditingController(text: _themeProfile.name);
|
||||
_logoSize = TextEditingController(text: _themeProfile.logoSize.toString());
|
||||
_footerText = TextEditingController(text: _themeProfile.footerText);
|
||||
_closingSlideMarkdown = TextEditingController(
|
||||
text: _themeProfile.closingSlideMarkdown,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -81,6 +85,7 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
|||
_profileName.dispose();
|
||||
_logoSize.dispose();
|
||||
_footerText.dispose();
|
||||
_closingSlideMarkdown.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
@ -130,6 +135,7 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
|||
_profileName.text = profile.name;
|
||||
_logoSize.text = profile.logoSize.toString();
|
||||
_footerText.text = profile.footerText;
|
||||
_closingSlideMarkdown.text = profile.closingSlideMarkdown;
|
||||
_profileTouched = true;
|
||||
});
|
||||
}
|
||||
|
|
@ -142,6 +148,7 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
|||
name: name.isEmpty ? 'Stijlprofiel' : name,
|
||||
logoSize: size,
|
||||
footerText: _footerText.text,
|
||||
closingSlideMarkdown: _closingSlideMarkdown.text,
|
||||
);
|
||||
notifier.setHomeDirectory(_homeDirectory);
|
||||
notifier.setExportDirectory(_exportDirectory);
|
||||
|
|
@ -295,6 +302,7 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
|||
_profileName.text = profile.name;
|
||||
_logoSize.text = profile.logoSize.toString();
|
||||
_footerText.text = profile.footerText;
|
||||
_closingSlideMarkdown.text = profile.closingSlideMarkdown;
|
||||
_profileTouched = true;
|
||||
});
|
||||
},
|
||||
|
|
@ -312,6 +320,7 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
|||
_profileName.text = profile.name;
|
||||
_logoSize.text = profile.logoSize.toString();
|
||||
_footerText.text = profile.footerText;
|
||||
_closingSlideMarkdown.text = profile.closingSlideMarkdown;
|
||||
_profileTouched = true;
|
||||
});
|
||||
}
|
||||
|
|
@ -327,6 +336,7 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
|||
_profileName.text = created.name;
|
||||
_logoSize.text = created.logoSize.toString();
|
||||
_footerText.text = created.footerText;
|
||||
_closingSlideMarkdown.text = created.closingSlideMarkdown;
|
||||
_profileTouched = true;
|
||||
});
|
||||
}
|
||||
|
|
@ -690,6 +700,47 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
|||
contentPadding: EdgeInsets.zero,
|
||||
dense: true,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
_sectionTitle(l10n.d('Laatste slide')),
|
||||
SwitchListTile(
|
||||
value: _themeProfile.closingSlideEnabled,
|
||||
onChanged: (v) => setState(() {
|
||||
_themeProfile = _themeProfile.copyWith(
|
||||
closingSlideEnabled: v,
|
||||
closingSlideMarkdown: _closingSlideMarkdown.text,
|
||||
);
|
||||
_profileTouched = true;
|
||||
}),
|
||||
title: Text(
|
||||
l10n.d('Standaard laatste slide gebruiken'),
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
subtitle: Text(
|
||||
l10n.d(
|
||||
'Wordt automatisch toegevoegd bij presenteren en exporteren.',
|
||||
),
|
||||
style: const TextStyle(fontSize: 11, color: Color(0xFF64748B)),
|
||||
),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
dense: true,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: _closingSlideMarkdown,
|
||||
enabled: _themeProfile.closingSlideEnabled,
|
||||
minLines: 4,
|
||||
maxLines: 8,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.d('Markdown voor laatste slide'),
|
||||
hintText: '# Bedankt\n\nVragen?',
|
||||
alignLabelWithHint: true,
|
||||
isDense: true,
|
||||
),
|
||||
onChanged: (value) {
|
||||
_themeProfile = _themeProfile.copyWith(closingSlideMarkdown: value);
|
||||
_profileTouched = true;
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:screen_retriever/screen_retriever.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import '../../models/deck.dart';
|
||||
import '../../models/settings.dart';
|
||||
|
|
@ -124,6 +125,7 @@ class _FullscreenPresenterState extends State<FullscreenPresenter> {
|
|||
_clockTimer = Timer.periodic(const Duration(seconds: 1), (_) {
|
||||
if (mounted && _presenterView) setState(() {});
|
||||
});
|
||||
_enableWakeLock();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_focusNode.requestFocus();
|
||||
_loadDisplays();
|
||||
|
|
@ -136,11 +138,28 @@ class _FullscreenPresenterState extends State<FullscreenPresenter> {
|
|||
_advanceTimer?.cancel();
|
||||
_clockTimer?.cancel();
|
||||
_typedTimer?.cancel();
|
||||
_disableWakeLock();
|
||||
_gridScroll.dispose();
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _enableWakeLock() async {
|
||||
try {
|
||||
await WakelockPlus.enable();
|
||||
} catch (_) {
|
||||
// Best-effort: unsupported platforms should not interrupt presenting.
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _disableWakeLock() async {
|
||||
try {
|
||||
await WakelockPlus.disable();
|
||||
} catch (_) {
|
||||
// Best-effort cleanup.
|
||||
}
|
||||
}
|
||||
|
||||
void _scheduleAdvance() {
|
||||
_advanceTimer?.cancel();
|
||||
_advanceTimer = null;
|
||||
|
|
@ -268,6 +287,7 @@ class _FullscreenPresenterState extends State<FullscreenPresenter> {
|
|||
|
||||
Future<void> _exit() async {
|
||||
_advanceTimer?.cancel();
|
||||
await _disableWakeLock();
|
||||
await windowManager.setFullScreen(false);
|
||||
if (mounted) Navigator.pop(context);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,6 +97,10 @@ install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
|||
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/runner/resources/app_icon.png"
|
||||
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}/icons"
|
||||
COMPONENT Runtime)
|
||||
|
||||
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,20 @@ struct _MyApplication {
|
|||
|
||||
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
|
||||
|
||||
static void set_window_icon(GtkWindow* window) {
|
||||
gtk_window_set_default_icon_name(APPLICATION_ID);
|
||||
|
||||
g_autofree gchar* executable_path = g_file_read_link("/proc/self/exe", nullptr);
|
||||
if (executable_path == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_autofree gchar* executable_dir = g_path_get_dirname(executable_path);
|
||||
g_autofree gchar* icon_path =
|
||||
g_build_filename(executable_dir, "data", "icons", "app_icon.png", nullptr);
|
||||
gtk_window_set_icon_from_file(window, icon_path, nullptr);
|
||||
}
|
||||
|
||||
// Called when first Flutter frame received.
|
||||
static void first_frame_cb(MyApplication* self, FlView* view) {
|
||||
gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view)));
|
||||
|
|
@ -24,6 +38,7 @@ static void my_application_activate(GApplication* application) {
|
|||
MyApplication* self = MY_APPLICATION(application);
|
||||
GtkWindow* window =
|
||||
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
|
||||
set_window_icon(window);
|
||||
|
||||
// Use a header bar when running in GNOME as this is the common style used
|
||||
// by applications and is the setup most users will be using (e.g. Ubuntu
|
||||
|
|
|
|||
BIN
linux/runner/resources/app_icon.png
Normal file
|
After Width: | Height: | Size: 154 KiB |
|
|
@ -7,20 +7,24 @@ import Foundation
|
|||
|
||||
import desktop_drop
|
||||
import file_picker
|
||||
import package_info_plus
|
||||
import pasteboard
|
||||
import screen_retriever_macos
|
||||
import shared_preferences_foundation
|
||||
import url_launcher_macos
|
||||
import video_player_avfoundation
|
||||
import wakelock_plus
|
||||
import window_manager
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin"))
|
||||
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin"))
|
||||
ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
VideoPlayerPlugin.register(with: registry.registrar(forPlugin: "VideoPlayerPlugin"))
|
||||
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
|
||||
WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,5 +38,16 @@ end
|
|||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_macos_build_settings(target)
|
||||
|
||||
next unless target.name == 'video_player_avfoundation'
|
||||
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['GCC_WARN_INHIBIT_ALL_WARNINGS'] = 'YES'
|
||||
config.build_settings['SWIFT_SUPPRESS_WARNINGS'] = 'YES'
|
||||
config.build_settings['OTHER_CFLAGS'] = [
|
||||
'$(inherited)',
|
||||
'-Wno-deprecated-declarations',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,22 +1,78 @@
|
|||
PODS:
|
||||
- desktop_drop (0.0.1):
|
||||
- FlutterMacOS
|
||||
- file_picker (0.0.1):
|
||||
- FlutterMacOS
|
||||
- FlutterMacOS (1.0.0)
|
||||
- package_info_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- pasteboard (0.0.1):
|
||||
- FlutterMacOS
|
||||
- screen_retriever_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- url_launcher_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- video_player_avfoundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- wakelock_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- window_manager (0.5.0):
|
||||
- FlutterMacOS
|
||||
|
||||
DEPENDENCIES:
|
||||
- desktop_drop (from `Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos`)
|
||||
- file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`)
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
|
||||
- pasteboard (from `Flutter/ephemeral/.symlinks/plugins/pasteboard/macos`)
|
||||
- screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`)
|
||||
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||
- video_player_avfoundation (from `Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin`)
|
||||
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
|
||||
- window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
desktop_drop:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos
|
||||
file_picker:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos
|
||||
FlutterMacOS:
|
||||
:path: Flutter/ephemeral
|
||||
package_info_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
|
||||
pasteboard:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/pasteboard/macos
|
||||
screen_retriever_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos
|
||||
shared_preferences_foundation:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
||||
url_launcher_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||
video_player_avfoundation:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin
|
||||
wakelock_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos
|
||||
window_manager:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
desktop_drop: 10a3e6a7fa9dbe350541f2574092fecfa345a07b
|
||||
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
|
||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
||||
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
|
||||
pasteboard: b594eaf838d930b276d7a35a44a32b4f489170cb
|
||||
screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd
|
||||
video_player_avfoundation: 3453f792138786248960ca029747fcd9f318ef52
|
||||
wakelock_plus: 917609be14d812ddd9e9528876538b2263aaa03b
|
||||
window_manager: b729e31d38fb04905235df9ea896128991cad99e
|
||||
|
||||
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
|
||||
PODFILE CHECKSUM: b3f873403194e4bfbc7fa8ecc38abe8f55968093
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
|
|
|||
|
|
@ -375,6 +375,7 @@
|
|||
};
|
||||
33CC111E2044C6BF0003C045 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
|
|
@ -589,6 +590,10 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
OTHER_CFLAGS = (
|
||||
"$(inherited)",
|
||||
"-Wno-deprecated-declarations",
|
||||
);
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
|
|
@ -721,6 +726,10 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
OTHER_CFLAGS = (
|
||||
"$(inherited)",
|
||||
"-Wno-deprecated-declarations",
|
||||
);
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
|
@ -741,6 +750,10 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
OTHER_CFLAGS = (
|
||||
"$(inherited)",
|
||||
"-Wno-deprecated-declarations",
|
||||
);
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,6 +3,14 @@ import FlutterMacOS
|
|||
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
if let iconPath = Bundle.main.path(forResource: "AppIcon", ofType: "icns") {
|
||||
NSApp.applicationIconImage = NSImage(contentsOfFile: iconPath)
|
||||
}
|
||||
|
||||
super.applicationDidFinishLaunching(notification)
|
||||
}
|
||||
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 518 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 844 B |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 413 KiB After Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 4.5 KiB |
41
pubspec.lock
|
|
@ -493,6 +493,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
package_info_plus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: "468c26b4254ab01979fa5e4a98cb343ea3631b9acee6f21028997419a80e1a20"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.0.1"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_platform_interface
|
||||
sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
pasteboard:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -670,12 +686,11 @@ packages:
|
|||
source: hosted
|
||||
version: "0.2.0"
|
||||
screen_retriever_macos:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: screen_retriever_macos
|
||||
sha256: "71f956e65c97315dd661d71f828708bd97b6d358e776f1a30d5aa7d22d78a149"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
path: "third_party/screen_retriever_macos"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.2.0"
|
||||
screen_retriever_platform_interface:
|
||||
dependency: transitive
|
||||
|
|
@ -1050,6 +1065,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.2.0"
|
||||
wakelock_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: wakelock_plus
|
||||
sha256: ddf3db70eaa10c37558ff817519b85d527dbd21034fd5d8e1c2e85f31588f1c1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.2"
|
||||
wakelock_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: wakelock_plus_platform_interface
|
||||
sha256: b13f99e992e7ae6a152e16c5559d3c07ff445b13330192662494e614ca3e7d7b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ dependencies:
|
|||
flutter_highlight: ^0.7.0
|
||||
flutter_math_fork: ^0.7.4
|
||||
highlight: ^0.7.0
|
||||
wakelock_plus: ^1.5.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
@ -38,7 +39,13 @@ dev_dependencies:
|
|||
flutter_lints: ^6.0.0
|
||||
xml: ^6.6.1
|
||||
|
||||
dependency_overrides:
|
||||
screen_retriever_macos:
|
||||
path: third_party/screen_retriever_macos
|
||||
|
||||
flutter:
|
||||
config:
|
||||
enable-swift-package-manager: false
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- assets/images/de-winter-wittegeheel.png
|
||||
|
|
|
|||
|
|
@ -83,6 +83,8 @@ void main() {
|
|||
footerText: 'Vertrouwelijk · {page}/{total}',
|
||||
footerShowPageNumbers: true,
|
||||
footerPosition: 'center',
|
||||
closingSlideEnabled: true,
|
||||
closingSlideMarkdown: '# Einde\n\nDank voor jullie aandacht.',
|
||||
);
|
||||
|
||||
final markdown = service.generateDeck(
|
||||
|
|
@ -106,6 +108,11 @@ void main() {
|
|||
expect(deck.themeProfile.footerText, 'Vertrouwelijk · {page}/{total}');
|
||||
expect(deck.themeProfile.footerShowPageNumbers, isTrue);
|
||||
expect(deck.themeProfile.footerPosition, 'center');
|
||||
expect(deck.themeProfile.closingSlideEnabled, isTrue);
|
||||
expect(
|
||||
deck.themeProfile.closingSlideMarkdown,
|
||||
'# Einde\n\nDank voor jullie aandacht.',
|
||||
);
|
||||
});
|
||||
|
||||
test('adds logo-safe class when deck profile has logo', () {
|
||||
|
|
|
|||
3
third_party/screen_retriever_macos/CHANGELOG.md
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
## 0.2.0
|
||||
|
||||
* First release.
|
||||
21
third_party/screen_retriever_macos/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022-2024 LiJianying <lijy91@foxmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
12
third_party/screen_retriever_macos/README.md
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# screen_retriever_macos
|
||||
|
||||
[![pub version][pub-image]][pub-url]
|
||||
|
||||
[pub-image]: https://img.shields.io/pub/v/screen_retriever_macos.svg
|
||||
[pub-url]: https://pub.dev/packages/screen_retriever_macos
|
||||
|
||||
The macos implementation of [screen_retriever](https://pub.dev/packages/screen_retriever).
|
||||
|
||||
## License
|
||||
|
||||
[MIT](./LICENSE)
|
||||
151
third_party/screen_retriever_macos/macos/Classes/ScreenRetrieverMacosPlugin.swift
vendored
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
extension NSScreen {
|
||||
var displayID: CGDirectDisplayID {
|
||||
return deviceDescription[NSDeviceDescriptionKey(rawValue: "NSScreenNumber")] as? CGDirectDisplayID ?? 0
|
||||
}
|
||||
|
||||
public func toDictionary() -> NSDictionary {
|
||||
var name: String = "";
|
||||
if #available(macOS 10.15, *) {
|
||||
name = self.localizedName
|
||||
}
|
||||
let size: NSDictionary = [
|
||||
"width": self.frame.width,
|
||||
"height": self.frame.height,
|
||||
]
|
||||
let visiblePosition: NSDictionary = [
|
||||
"dx": self.visibleFrame.topLeft.x,
|
||||
"dy": self.visibleFrame.topLeft.y,
|
||||
]
|
||||
let visibleSize: NSDictionary = [
|
||||
"width": self.visibleFrame.width,
|
||||
"height": self.visibleFrame.height,
|
||||
]
|
||||
let dict: NSDictionary = [
|
||||
"id": self.displayID.description,
|
||||
"name": name,
|
||||
"size": size,
|
||||
"visiblePosition": visiblePosition,
|
||||
"visibleSize": visibleSize,
|
||||
]
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
|
||||
extension NSRect {
|
||||
var topLeft: CGPoint {
|
||||
set {
|
||||
let screenFrameRect = NSScreen.screens[0].frame
|
||||
origin.x = newValue.x
|
||||
origin.y = screenFrameRect.height - newValue.y - size.height
|
||||
}
|
||||
get {
|
||||
let screenFrameRect = NSScreen.screens[0].frame
|
||||
return CGPoint(x: origin.x, y: screenFrameRect.height - origin.y - size.height)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ScreenRetrieverMacosPlugin: NSObject, FlutterPlugin,FlutterStreamHandler {
|
||||
private var _eventSink: FlutterEventSink?
|
||||
|
||||
var externalDisplayCount:Int = 0
|
||||
|
||||
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||
let channel = FlutterMethodChannel(name: "dev.leanflutter.plugins/screen_retriever", binaryMessenger: registrar.messenger)
|
||||
let instance = ScreenRetrieverMacosPlugin()
|
||||
registrar.addMethodCallDelegate(instance, channel: channel)
|
||||
let eventChannel = FlutterEventChannel(name: "dev.leanflutter.plugins/screen_retriever_event", binaryMessenger: registrar.messenger)
|
||||
eventChannel.setStreamHandler(instance)
|
||||
|
||||
instance.externalDisplayCount = NSScreen.screens.count
|
||||
instance.setupNotificationCenter()
|
||||
}
|
||||
|
||||
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
|
||||
self._eventSink = events
|
||||
return nil;
|
||||
}
|
||||
|
||||
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
|
||||
self._eventSink = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupNotificationCenter() {
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(handleDisplayConnection),
|
||||
name: NSApplication.didChangeScreenParametersNotification,
|
||||
object: nil)
|
||||
}
|
||||
|
||||
|
||||
@objc func handleDisplayConnection(notification: Notification) {
|
||||
if externalDisplayCount < NSScreen.screens.count {
|
||||
_emitEvent("display-added")
|
||||
externalDisplayCount = NSScreen.screens.count
|
||||
} else if externalDisplayCount > NSScreen.screens.count {
|
||||
_emitEvent("display-removed")
|
||||
externalDisplayCount = NSScreen.screens.count
|
||||
}
|
||||
}
|
||||
|
||||
public func _emitEvent(_ eventName: String) {
|
||||
guard let eventSink = self._eventSink else {
|
||||
return
|
||||
}
|
||||
let event: NSDictionary = [
|
||||
"type": eventName,
|
||||
]
|
||||
eventSink(event)
|
||||
}
|
||||
|
||||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
switch call.method {
|
||||
case "getCursorScreenPoint":
|
||||
getCursorScreenPoint(call, result: result)
|
||||
break
|
||||
case "getPrimaryDisplay":
|
||||
getPrimaryDisplay(call, result: result)
|
||||
break
|
||||
case "getAllDisplays":
|
||||
getAllDisplays(call, result: result)
|
||||
break
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
}
|
||||
|
||||
public func getCursorScreenPoint(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
let currentScreen = NSScreen.main!
|
||||
let mouseLocation: NSPoint = NSEvent.mouseLocation;
|
||||
|
||||
var visibleHeight = currentScreen.frame.maxY
|
||||
for screen in NSScreen.screens {
|
||||
if (visibleHeight > screen.frame.maxY) {
|
||||
visibleHeight = screen.frame.maxY
|
||||
}
|
||||
}
|
||||
let data: NSDictionary = [
|
||||
"dx": mouseLocation.x,
|
||||
"dy": visibleHeight - mouseLocation.y,
|
||||
]
|
||||
result(data)
|
||||
}
|
||||
|
||||
public func getPrimaryDisplay(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
result(NSScreen.screens[0].toDictionary())
|
||||
}
|
||||
|
||||
public func getAllDisplays(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
let data: NSDictionary = [
|
||||
"displays": NSScreen.screens.map({ screen in
|
||||
return screen.toDictionary()
|
||||
}),
|
||||
]
|
||||
result(data)
|
||||
}
|
||||
}
|
||||
23
third_party/screen_retriever_macos/macos/screen_retriever_macos.podspec
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#
|
||||
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
|
||||
# Run `pod lib lint screen_retriever_macos.podspec` to validate before publishing.
|
||||
#
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'screen_retriever_macos'
|
||||
s.version = '0.0.1'
|
||||
s.summary = 'A new Flutter plugin project.'
|
||||
s.description = <<-DESC
|
||||
A new Flutter plugin project.
|
||||
DESC
|
||||
s.homepage = 'http://example.com'
|
||||
s.license = { :file => '../LICENSE' }
|
||||
s.author = { 'Your Company' => 'email@example.com' }
|
||||
|
||||
s.source = { :path => '.' }
|
||||
s.source_files = 'Classes/**/*'
|
||||
s.dependency 'FlutterMacOS'
|
||||
|
||||
s.platform = :osx, '10.11'
|
||||
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
|
||||
s.swift_version = '5.0'
|
||||
end
|
||||
20
third_party/screen_retriever_macos/macos/screen_retriever_macos/Package.swift
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// swift-tools-version: 5.9
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "screen_retriever_macos",
|
||||
platforms: [
|
||||
.macOS("10.15")
|
||||
],
|
||||
products: [
|
||||
.library(name: "screen-retriever-macos", targets: ["screen_retriever_macos"])
|
||||
],
|
||||
dependencies: [],
|
||||
targets: [
|
||||
.target(
|
||||
name: "screen_retriever_macos",
|
||||
dependencies: []
|
||||
)
|
||||
]
|
||||
)
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
extension NSScreen {
|
||||
var displayID: CGDirectDisplayID {
|
||||
return deviceDescription[NSDeviceDescriptionKey(rawValue: "NSScreenNumber")] as? CGDirectDisplayID ?? 0
|
||||
}
|
||||
|
||||
public func toDictionary() -> NSDictionary {
|
||||
var name: String = "";
|
||||
if #available(macOS 10.15, *) {
|
||||
name = self.localizedName
|
||||
}
|
||||
let size: NSDictionary = [
|
||||
"width": self.frame.width,
|
||||
"height": self.frame.height,
|
||||
]
|
||||
let visiblePosition: NSDictionary = [
|
||||
"dx": self.visibleFrame.topLeft.x,
|
||||
"dy": self.visibleFrame.topLeft.y,
|
||||
]
|
||||
let visibleSize: NSDictionary = [
|
||||
"width": self.visibleFrame.width,
|
||||
"height": self.visibleFrame.height,
|
||||
]
|
||||
let dict: NSDictionary = [
|
||||
"id": self.displayID.description,
|
||||
"name": name,
|
||||
"size": size,
|
||||
"visiblePosition": visiblePosition,
|
||||
"visibleSize": visibleSize,
|
||||
]
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
|
||||
extension NSRect {
|
||||
var topLeft: CGPoint {
|
||||
set {
|
||||
let screenFrameRect = NSScreen.screens[0].frame
|
||||
origin.x = newValue.x
|
||||
origin.y = screenFrameRect.height - newValue.y - size.height
|
||||
}
|
||||
get {
|
||||
let screenFrameRect = NSScreen.screens[0].frame
|
||||
return CGPoint(x: origin.x, y: screenFrameRect.height - origin.y - size.height)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ScreenRetrieverMacosPlugin: NSObject, FlutterPlugin,FlutterStreamHandler {
|
||||
private var _eventSink: FlutterEventSink?
|
||||
|
||||
var externalDisplayCount:Int = 0
|
||||
|
||||
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||
let channel = FlutterMethodChannel(name: "dev.leanflutter.plugins/screen_retriever", binaryMessenger: registrar.messenger)
|
||||
let instance = ScreenRetrieverMacosPlugin()
|
||||
registrar.addMethodCallDelegate(instance, channel: channel)
|
||||
let eventChannel = FlutterEventChannel(name: "dev.leanflutter.plugins/screen_retriever_event", binaryMessenger: registrar.messenger)
|
||||
eventChannel.setStreamHandler(instance)
|
||||
|
||||
instance.externalDisplayCount = NSScreen.screens.count
|
||||
instance.setupNotificationCenter()
|
||||
}
|
||||
|
||||
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
|
||||
self._eventSink = events
|
||||
return nil;
|
||||
}
|
||||
|
||||
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
|
||||
self._eventSink = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupNotificationCenter() {
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(handleDisplayConnection),
|
||||
name: NSApplication.didChangeScreenParametersNotification,
|
||||
object: nil)
|
||||
}
|
||||
|
||||
|
||||
@objc func handleDisplayConnection(notification: Notification) {
|
||||
if externalDisplayCount < NSScreen.screens.count {
|
||||
_emitEvent("display-added")
|
||||
externalDisplayCount = NSScreen.screens.count
|
||||
} else if externalDisplayCount > NSScreen.screens.count {
|
||||
_emitEvent("display-removed")
|
||||
externalDisplayCount = NSScreen.screens.count
|
||||
}
|
||||
}
|
||||
|
||||
public func _emitEvent(_ eventName: String) {
|
||||
guard let eventSink = self._eventSink else {
|
||||
return
|
||||
}
|
||||
let event: NSDictionary = [
|
||||
"type": eventName,
|
||||
]
|
||||
eventSink(event)
|
||||
}
|
||||
|
||||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
switch call.method {
|
||||
case "getCursorScreenPoint":
|
||||
getCursorScreenPoint(call, result: result)
|
||||
break
|
||||
case "getPrimaryDisplay":
|
||||
getPrimaryDisplay(call, result: result)
|
||||
break
|
||||
case "getAllDisplays":
|
||||
getAllDisplays(call, result: result)
|
||||
break
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
}
|
||||
|
||||
public func getCursorScreenPoint(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
let currentScreen = NSScreen.main!
|
||||
let mouseLocation: NSPoint = NSEvent.mouseLocation;
|
||||
|
||||
var visibleHeight = currentScreen.frame.maxY
|
||||
for screen in NSScreen.screens {
|
||||
if (visibleHeight > screen.frame.maxY) {
|
||||
visibleHeight = screen.frame.maxY
|
||||
}
|
||||
}
|
||||
let data: NSDictionary = [
|
||||
"dx": mouseLocation.x,
|
||||
"dy": visibleHeight - mouseLocation.y,
|
||||
]
|
||||
result(data)
|
||||
}
|
||||
|
||||
public func getPrimaryDisplay(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
result(NSScreen.screens[0].toDictionary())
|
||||
}
|
||||
|
||||
public func getAllDisplays(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
let data: NSDictionary = [
|
||||
"displays": NSScreen.screens.map({ screen in
|
||||
return screen.toDictionary()
|
||||
}),
|
||||
]
|
||||
result(data)
|
||||
}
|
||||
}
|
||||
25
third_party/screen_retriever_macos/pubspec.yaml
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
name: screen_retriever_macos
|
||||
description: macOS implementation of the screen_retriever plugin.
|
||||
version: 0.2.0
|
||||
repository: https://github.com/leanflutter/screen_retriever/tree/main/packages/screen_retriever_macos
|
||||
|
||||
environment:
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
flutter: ">=3.3.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
screen_retriever_platform_interface: ^0.2.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
mostly_reasonable_lints: ^0.1.2
|
||||
|
||||
flutter:
|
||||
plugin:
|
||||
implements: screen_retriever
|
||||
platforms:
|
||||
macos:
|
||||
pluginClass: ScreenRetrieverMacosPlugin
|
||||
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 363 KiB |