Files
pyramid/lib/widgets/screen_share_picker.dart
Bernd Steckmeister 133a8acbc8 refactor: print() durch debugPrint() ersetzen (avoid_print Lint)
65 flutter-analyze-Meldungen behoben, indem alle print()-Aufrufe in Core-
Dateien (Push, LiveKit, Matrix-Client, VoIP, Screen-Share) durch das
Flutter-eigene debugPrint() ersetzt wurden. Gleiche Funktion (Logging in
Debug-Sessions), aber zeilenlängen-sicher und ohne Lint-Warnung.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-03 06:29:49 +02:00

233 lines
7.1 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:pyramid/core/theme.dart';
class ScreenSharePicker extends StatefulWidget {
const ScreenSharePicker({super.key});
static Future<DesktopCapturerSource?> show(BuildContext context) async {
return showDialog<DesktopCapturerSource>(
context: context,
builder: (context) => const ScreenSharePicker(),
);
}
@override
State<ScreenSharePicker> createState() => _ScreenSharePickerState();
}
class _ScreenSharePickerState extends State<ScreenSharePicker> with SingleTickerProviderStateMixin {
late TabController _tabController;
List<DesktopCapturerSource> _sources = [];
bool _loading = true;
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
_loadSources();
}
Future<void> _loadSources() async {
try {
// FluffyChat Fix: Get both types at once to avoid native crashes on Windows
final sources = await desktopCapturer.getSources(types: [
SourceType.Screen,
SourceType.Window,
]);
if (mounted) {
setState(() {
_sources = sources;
_loading = false;
});
}
} catch (e) {
debugPrint('[Picker] Error loading sources: $e');
if (mounted) setState(() => _loading = false);
}
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final pt = PyramidTheme.of(context);
final screens = _sources.where((s) => s.type == SourceType.Screen).toList();
final windows = _sources.where((s) => s.type == SourceType.Window).toList();
final screenSize = MediaQuery.sizeOf(context);
return Center(
child: Container(
// Auf kleinen Bildschirmen (Mobile, Quer- wie Hochformat) einpassen.
width: (screenSize.width - 32).clamp(280.0, 600.0),
height: (screenSize.height - 48).clamp(320.0, 520.0),
decoration: BoxDecoration(
color: pt.bg1,
borderRadius: BorderRadius.circular(pt.rXl),
border: Border.all(color: pt.border),
boxShadow: [BoxShadow(color: Colors.black54, blurRadius: 40)],
),
child: Column(
children: [
// Header
Padding(
padding: const EdgeInsets.fromLTRB(24, 20, 16, 8),
child: Row(
children: [
Text('Share your screen',
style: TextStyle(color: pt.fg, fontSize: 20, fontWeight: FontWeight.w700)),
const Spacer(),
IconButton(
icon: Icon(Icons.close_rounded, color: pt.fgDim),
onPressed: () => Navigator.pop(context),
),
],
),
),
// Tabs
TabBar(
controller: _tabController,
indicatorColor: pt.accent,
labelColor: pt.accent,
unselectedLabelColor: pt.fgMuted,
dividerColor: pt.border,
tabs: const [
Tab(text: 'Screens'),
Tab(text: 'Windows'),
],
),
// Content
Expanded(
child: _loading
? Center(child: CircularProgressIndicator(color: pt.accent))
: TabBarView(
controller: _tabController,
children: [
_SourceGrid(sources: screens, pt: pt),
_SourceGrid(sources: windows, pt: pt),
],
),
),
// Footer
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: pt.bg2,
borderRadius: BorderRadius.vertical(bottom: Radius.circular(pt.rXl)),
border: Border(top: BorderSide(color: pt.border)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('Cancel', style: TextStyle(color: pt.fgMuted)),
),
],
),
),
],
),
),
);
}
}
class _SourceGrid extends StatelessWidget {
final List<DesktopCapturerSource> sources;
final PyramidTheme pt;
const _SourceGrid({required this.sources, required this.pt});
@override
Widget build(BuildContext context) {
if (sources.isEmpty) {
return Center(child: Text('No sources found', style: TextStyle(color: pt.fgDim)));
}
return GridView.builder(
padding: const EdgeInsets.all(16),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 12,
mainAxisSpacing: 16,
childAspectRatio: 1.4,
),
itemCount: sources.length,
itemBuilder: (context, i) {
final s = sources[i];
return _SourceTile(source: s, pt: pt);
},
);
}
}
class _SourceTile extends StatefulWidget {
final DesktopCapturerSource source;
final PyramidTheme pt;
const _SourceTile({required this.source, required this.pt});
@override
State<_SourceTile> createState() => _SourceTileState();
}
class _SourceTileState extends State<_SourceTile> {
bool _hovered = false;
@override
Widget build(BuildContext context) {
final pt = widget.pt;
return MouseRegion(
onEnter: (_) => setState(() => _hovered = true),
onExit: (_) => setState(() => _hovered = false),
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () => Navigator.pop(context, widget.source),
child: Column(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
color: pt.bg3,
borderRadius: BorderRadius.circular(pt.rBase),
border: Border.all(
color: _hovered ? pt.accent : pt.border,
width: _hovered ? 2 : 1,
),
),
clipBehavior: Clip.antiAlias,
child: widget.source.thumbnail != null
? Image.memory(
widget.source.thumbnail!,
fit: BoxFit.cover,
width: double.infinity,
)
: Center(child: Icon(Icons.monitor_rounded, color: pt.fgDim, size: 32)),
),
),
const SizedBox(height: 6),
Text(
widget.source.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: _hovered ? pt.fg : pt.fgMuted,
fontSize: 12,
fontWeight: _hovered ? FontWeight.w600 : FontWeight.w400,
),
),
],
),
),
);
}
}