diff --git a/ios/Podfile.lock b/ios/Podfile.lock new file mode 100644 index 0000000..64cf43c --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,16 @@ +PODS: + - Flutter (1.0.0) + +DEPENDENCIES: + - Flutter (from `Flutter`) + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + +SPEC CHECKSUMS: + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + +PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e + +COCOAPODS: 1.16.2 diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 29ee166..961c012 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -6,10 +6,8 @@ class AppConfig { static const logUrl = 'https://baka-net.de'; static const authUrl = 'https://baka-net.de/auth'; - // Auth läuft über Bearer-Token aus dem Cloud-Login — KEIN hartcodierter Key mehr. - // (Alter Key melo-cloud-2026-secret-key wurde entfernt: steckte in jeder APK.) - static const ytProxyApiKey = String.fromEnvironment('MELO_API_KEY', - defaultValue: ''); + // API-Key (MUSS via --dart-define MELO_API_KEY=xxx beim Build gesetzt werden) + static const ytProxyApiKey = String.fromEnvironment('MELO_API_KEY'); // Feature-Toggles static bool sendeDiagnosedaten = true; diff --git a/lib/main.dart b/lib/main.dart index 1d6952d..aab04b2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,16 +2,18 @@ import 'package:flutter/material.dart'; import 'package:audio_service/audio_service.dart'; import 'database/db_helper.dart'; import 'services/favoriten_service.dart'; +import 'services/auth_service.dart'; import 'services/melo_logger.dart'; import 'services/audio_handler.dart'; import 'utils/farb_theme.dart'; import 'screens/home_screen.dart'; +import 'screens/login_screen.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); // Logger startet sofort – zeichnet ALLES auf - MeloLogger().init('2.38'); + MeloLogger().init('2.31'); try { await DbHelper().db; @@ -30,6 +32,9 @@ void main() async { MeloLogger().fehler('App-Start', e, stack); } + // Auth initialisieren (Token aus SharedPreferences laden) + await AuthService().initialisieren(); + runApp(const MeloApp()); } @@ -38,17 +43,15 @@ class MeloApp extends StatelessWidget { @override Widget build(BuildContext context) { - // Reagiert live auf Akzent-Änderungen (Login-Effekt pro Person) - return ValueListenableBuilder( - valueListenable: MeloTheme.akzentNotifier, - builder: (context, akzent, _) => MaterialApp( - title: 'Melo', - debugShowCheckedModeBanner: false, - theme: MeloTheme.theme, - home: const Scaffold(body: MeloHome()), - ), + final auth = AuthService(); + + return MaterialApp( + title: 'Melo', + debugShowCheckedModeBanner: false, + theme: MeloTheme.theme, + home: auth.istEingeloggt + ? const Scaffold(body: MeloHome()) + : const LoginScreen(), ); } } - - diff --git a/lib/screens/cloud_screen.dart b/lib/screens/cloud_screen.dart index 422c0c4..8d4c208 100644 --- a/lib/screens/cloud_screen.dart +++ b/lib/screens/cloud_screen.dart @@ -1,21 +1,16 @@ import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; -import 'package:just_audio/just_audio.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../utils/farb_theme.dart'; import '../services/cloud_service.dart'; +import '../services/auth_service.dart'; import '../services/melo_logger.dart'; -import '../models/song.dart'; -import '../database/db_helper.dart'; -import '../services/id3_reader.dart'; class CloudScreen extends StatefulWidget { final CloudService cloud; - final VoidCallback onSongsChanged; - const CloudScreen({super.key, required this.cloud, required this.onSongsChanged}); + const CloudScreen({super.key, required this.cloud}); @override State createState() => _CloudScreenState(); @@ -25,193 +20,144 @@ class _CloudScreenState extends State { int _serverCount = 0; bool _ladt = false; String? _status; + bool _statusOk = false; bool _autoSync = true; int _syncIntervall = 6; - String _aktuellerDownload = ''; - int _downloadFortschritt = 0; - int _downloadGesamt = 0; + Timer? _syncTimer; + String _letzterSync = 'Nie'; @override void initState() { super.initState(); + _verbindeCloud(); _ladeStatus(); _ladeSettings(); } - Future _ladeStatus() async { - final p = await SharedPreferences.getInstance(); - final nutzer = p.getString('melo_nutzer') ?? ''; - await widget.cloud.restoreLogin(); - if (nutzer.isEmpty || !widget.cloud.istAngemeldet) { - if (mounted) setState(() { _serverCount = 0; _status = 'Nicht angemeldet'; }); - return; + @override + void dispose() { + _syncTimer?.cancel(); + super.dispose(); + } + + Future _verbindeCloud() async { + final user = AuthService().benutzer; + if (user.isNotEmpty) { + await widget.cloud.login(user); } - final st = await widget.cloud.status(); - if (!mounted) return; - setState(() { - _serverCount = st?['total'] ?? 0; - _status = st != null ? 'Verbunden ($nutzer)' : 'Keine Verbindung'; - }); } Future _ladeSettings() async { final p = await SharedPreferences.getInstance(); - if (mounted) setState(() { - _autoSync = p.getBool('cloud_auto') ?? true; - _syncIntervall = p.getInt('cloud_interval') ?? 6; + final letzter = p.getString('cloud_last_sync'); + if (mounted) { + setState(() { + _autoSync = p.getBool('cloud_auto') ?? true; + _syncIntervall = p.getInt('cloud_interval') ?? 6; + _letzterSync = letzter ?? 'Nie'; + }); + } + _starteAutoSync(); + } + + void _starteAutoSync() { + _syncTimer?.cancel(); + if (!_autoSync || _syncIntervall == 0) return; + _syncTimer = Timer.periodic( + Duration(hours: _syncIntervall), + (_) => _autoSyncDurchfuehren(), + ); + } + + Future _autoSyncDurchfuehren() async { + await _download(); + final p = await SharedPreferences.getInstance(); + final now = DateTime.now(); + final zeit = '${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}'; + await p.setString('cloud_last_sync', zeit); + if (mounted) setState(() => _letzterSync = zeit); + } + + Future _ladeStatus() async { + final st = await widget.cloud.status(); + if (!mounted) return; + setState(() { + _serverCount = st?['total'] ?? 0; + _status = st != null ? 'Verbunden' : 'Keine Verbindung'; + _statusOk = st != null; }); } Future _upload() async { setState(() => _ladt = true); + _setzeStatus('Suche lokale Songs...'); final dir = Directory('${(await getApplicationDocumentsDirectory()).path}/music'); if (!await dir.exists()) { - setState(() { _ladt = false; _status = 'Keine lokalen Songs'; }); + setState(() { _ladt = false; _setzeStatus('Keine lokalen Songs', ok: false); }); return; } final files = dir.listSync().whereType().where((f) => f.path.endsWith('.mp3') || f.path.endsWith('.m4a')); int count = 0; for (final f in files) { + _setzeStatus('Upload: ${f.path.split('/').last}...'); final sid = await widget.cloud.upload(f.path, f.path.split('/').last); if (sid != null) count++; } await _ladeStatus(); if (mounted) { - setState(() { _ladt = false; _status = '$count Songs hochgeladen'; }); + setState(() { _ladt = false; }); + _setzeStatus('$count Songs hochgeladen', ok: count > 0); MeloLogger().aktion('cloud_upload', {'count': count}); } } Future _download() async { - setState(() { - _ladt = true; - _status = null; - _aktuellerDownload = 'Lade Liste...'; - _downloadFortschritt = 0; - _downloadGesamt = 0; - }); - - // Auf Android: Music/Melo (getExternalStorageDirectory) - // Auf iOS: Documents/music (Sandbox, sichtbar in Dateien-App) - String basePath; - if (Platform.isIOS) { - basePath = '${(await getApplicationDocumentsDirectory()).path}/music'; - } else { - // Wenn voller Speicherzugriff: Downloads/Melo (sichtbar) - final p = await SharedPreferences.getInstance(); - if (p.getBool('manage_storage') == true) { - final d = await getDownloadsDirectory(); - basePath = d != null ? '${d.path}/Melo' : '${(await getApplicationDocumentsDirectory()).path}/music'; - } else { - final ext = await getExternalStorageDirectory(); - basePath = ext != null ? '${ext.path}/Melo' : '${(await getApplicationDocumentsDirectory()).path}/music'; - } - } - final dir = Directory(basePath); + setState(() => _ladt = true); + _setzeStatus('Vergleiche mit Server...'); + final dir = Directory('${(await getApplicationDocumentsDirectory()).path}/music'); if (!await dir.exists()) await dir.create(recursive: true); - final localFiles = dir.listSync().whereType() .map((f) => f.path.split('/').last).toSet(); final serverSongs = await widget.cloud.listSongs(); - - // Filter: nur neue Songs - final neue = serverSongs.where((s) { - final title = s['title'].toString(); - return !localFiles.contains(title); - }).toList(); - - if (neue.isEmpty) { - if (mounted) setState(() { _ladt = false; _status = 'Alle Songs bereits lokal'; }); - return; - } - - setState(() => _downloadGesamt = neue.length); - final db = DbHelper(); - - for (int i = 0; i < neue.length; i++) { - final song = neue[i]; + int downloaded = 0; + for (final song in serverSongs) { final title = song['title'].toString(); + if (localFiles.contains(title)) continue; + _setzeStatus('Download: $title...'); final sid = song['id'].toString(); - - // Dateinamen säubern (p.basename verhindert Pfad-Traversal via Titel) - final safeTitle = p.basename(title).replaceAll(RegExp(r'[^\w\s\.-]'), '_').trim(); - final dateiName = safeTitle.endsWith('.mp3') || safeTitle.endsWith('.m4a') - ? safeTitle : '$safeTitle.mp3'; - final dest = '${dir.path}/$dateiName'; - - if (mounted) setState(() { - _aktuellerDownload = title; - _downloadFortschritt = i + 1; - }); - - final ok = await widget.cloud.download(sid, dest); - if (ok) { - // Dauer auslesen - int dauer = 0; - try { - final ap = AudioPlayer(); - await ap.setFilePath(dest).timeout(const Duration(milliseconds: 2000)); - dauer = ap.duration?.inSeconds ?? 0; - await ap.dispose(); - } catch (_) {} - - // ID3-Metadaten & Cover aus der heruntergeladenen Datei extrahieren - final id3 = Id3Reader.lesen(dest); - String? coverPfad; - if (id3['cover'] != null && id3['cover'] is List) { - try { - final coversDir = Directory('${(await getApplicationDocumentsDirectory()).path}/covers'); - if (!await coversDir.exists()) await coversDir.create(recursive: true); - final coverFile = File('${coversDir.path}/cloud_$sid.jpg'); - await coverFile.writeAsBytes(id3['cover'] as List); - coverPfad = coverFile.path; - } catch (_) {} - } - - try { - await db.songEinfuegen(Song( - titel: (id3['titel'] as String).isNotEmpty - ? id3['titel'] - : title.replaceAll('.mp3', '').replaceAll('.m4a', ''), - kuenstler: (id3['kuenstler'] as String).isNotEmpty - ? id3['kuenstler'] - : 'Melo Cloud', - album: id3['album'] ?? '', - dauerSekunden: dauer, - dateiPfad: dest, - coverPfad: coverPfad, - downloadQuelle: 'cloud', - istHeruntergeladen: true, - )); - } catch (_) {} - } + final dest = '${dir.path}/$title'; + if (await widget.cloud.download(sid, dest)) downloaded++; } - await _ladeStatus(); - widget.onSongsChanged(); // Musik-Tab aktualisieren - if (mounted) { - setState(() { - _ladt = false; - _aktuellerDownload = ''; - _status = '${_downloadGesamt} Songs heruntergeladen'; - }); - MeloLogger().aktion('cloud_download', {'count': _downloadGesamt}); + setState(() { _ladt = false; }); + _setzeStatus('$downloaded Songs heruntergeladen', ok: true); + MeloLogger().aktion('cloud_download', {'count': downloaded}); } } + void _setzeStatus(String msg, {bool ok = false}) { + if (mounted) setState(() { _status = msg; _statusOk = ok; }); + } + @override Widget build(BuildContext context) { return Scaffold( backgroundColor: MeloTheme.schwarz, appBar: AppBar( backgroundColor: MeloTheme.dunkel1, - title: const Row(children: [ - Text('☁️', style: TextStyle(fontSize: 20)), - SizedBox(width: 8), - Text('Cloud Sync', style: TextStyle(color: Colors.white, fontSize: 18)), - ]), + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white, size: 22), + onPressed: () => Navigator.pop(context), + ), + title: const Row( + children: [ + Text('☁️', style: TextStyle(fontSize: 20)), + SizedBox(width: 8), + Text('Cloud Sync', style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.w600)), + ], + ), actions: [ IconButton( icon: const Icon(Icons.refresh, color: Colors.grey, size: 20), @@ -219,14 +165,118 @@ class _CloudScreenState extends State { ), ], ), - body: Padding( + body: SingleChildScrollView( padding: const EdgeInsets.all(20), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Status-Karte + // ─── Status-Karte ─── + _statusKarte(), + const SizedBox(height: 20), + + // ─── Upload / Download Buttons ─── + Row( + children: [ + Expanded(child: _aktionsButton( + icon: Icons.upload_rounded, + label: 'Upload', + beschreibung: 'Lokale Songs → Server', + onTap: _upload, + )), + const SizedBox(width: 12), + Expanded(child: _aktionsButton( + icon: Icons.download_rounded, + label: 'Download', + beschreibung: 'Server → Lokal', + onTap: _download, + )), + ], + ), + + // Lade-Indikator + if (_ladt) + const Padding( + padding: EdgeInsets.only(top: 16), + child: Center(child: CircularProgressIndicator(color: MeloTheme.rot)), + ), + + // Status-Text + if (_status != null && !_ladt) + Padding( + padding: const EdgeInsets.only(top: 12), + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), + decoration: BoxDecoration( + color: _statusOk ? const Color(0xFF0D3B1E) : const Color(0xFF3B0D0D), + borderRadius: BorderRadius.circular(10), + ), + child: Row( + children: [ + Icon( + _statusOk ? Icons.check_circle : Icons.info_outline, + size: 16, + color: _statusOk ? const Color(0xFF4CAF50) : const Color(0xFFEF5350), + ), + const SizedBox(width: 8), + Expanded( + child: Text( + _status!, + style: TextStyle( + color: _statusOk ? const Color(0xFFA5D6A7) : const Color(0xFFEF9A9A), + fontSize: 13, + ), + ), + ), + ], + ), + ), + ), + + const SizedBox(height: 24), + + // ─── Sync-Einstellungen ─── + _sektionsHeader('⚙ Sync-Einstellungen'), + const SizedBox(height: 8), + + // Auto-Sync Toggle + Container( + decoration: BoxDecoration( + color: MeloTheme.dunkel1, + borderRadius: BorderRadius.circular(14), + border: Border.all(color: MeloTheme.dunkel2), + ), + child: SwitchListTile( + title: const Text('Auto-Sync', style: TextStyle(color: Colors.white, fontSize: 14)), + subtitle: Text( + _autoSync ? 'Automatisch synchronisieren' : 'Nur manuell', + style: const TextStyle(color: MeloTheme.textSekundaer, fontSize: 12), + ), + value: _autoSync, + activeColor: MeloTheme.rot, + secondary: const Icon(Icons.sync, color: MeloTheme.rot, size: 22), + onChanged: (v) async { + setState(() => _autoSync = v); + (await SharedPreferences.getInstance()).setBool('cloud_auto', v); + _starteAutoSync(); + }, + ), + ), + const SizedBox(height: 12), + + // Sync-Intervall – schöne Segmented Buttons + const Text( + 'Intervall', + style: TextStyle(color: MeloTheme.textSekundaer, fontSize: 12, fontWeight: FontWeight.w500), + ), + const SizedBox(height: 8), + _intervallAuswahl(), + const SizedBox(height: 12), + + // Letzter Sync Container( width: double.infinity, - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: MeloTheme.dunkel1, borderRadius: BorderRadius.circular(12), @@ -234,102 +284,197 @@ class _CloudScreenState extends State { ), child: Row( children: [ - const Icon(Icons.storage, color: MeloTheme.rot, size: 28), - const SizedBox(width: 12), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('$_serverCount Songs auf Server', - style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600)), - Text(_status ?? 'Lädt...', - style: const TextStyle(color: Colors.grey, fontSize: 12)), - ], + const Icon(Icons.history, color: MeloTheme.textSekundaer, size: 18), + const SizedBox(width: 10), + const Text('Letzter Sync: ', style: TextStyle(color: MeloTheme.textSekundaer, fontSize: 13)), + Text( + _letzterSync, + style: const TextStyle(color: Colors.white, fontSize: 13, fontWeight: FontWeight.w500), ), ], ), ), - const SizedBox(height: 16), - // Upload / Download - Row( - children: [ - Expanded(child: _btn(Icons.upload, 'Upload', _upload)), - const SizedBox(width: 12), - Expanded(child: _btn(Icons.download, 'Download', _download)), - ], - ), - // Download-Fortschritt - if (_ladt && _aktuellerDownload.isNotEmpty) ...[ - const SizedBox(height: 12), - LinearProgressIndicator( - value: _downloadGesamt > 0 ? _downloadFortschritt / _downloadGesamt : null, - backgroundColor: MeloTheme.dunkel2, - valueColor: const AlwaysStoppedAnimation(MeloTheme.rot), - ), - const SizedBox(height: 6), - Text( - '$_downloadFortschritt/$_downloadGesamt: $_aktuellerDownload', - style: const TextStyle(color: Colors.grey, fontSize: 12), - maxLines: 2, overflow: TextOverflow.ellipsis, - ), - ], - if (_status != null && !_ladt) - Padding( - padding: const EdgeInsets.only(top: 8), - child: Text(_status!, style: const TextStyle(color: Colors.grey, fontSize: 13)), - ), - const SizedBox(height: 12), - // Sync-Einstellungen - const Divider(color: MeloTheme.dunkel2), - const Align( - alignment: Alignment.centerLeft, - child: Text('⚙ Sync-Einstellungen', style: TextStyle(color: Colors.grey, fontSize: 11)), - ), - SwitchListTile( - dense: true, - title: const Text('Auto-Sync', style: TextStyle(color: Colors.white, fontSize: 13)), - value: _autoSync, - activeColor: MeloTheme.rot, - onChanged: (v) async { - setState(() => _autoSync = v); - (await SharedPreferences.getInstance()).setBool('cloud_auto', v); - }, - ), - Row( - children: ['Manuell', 'Alle 3h', 'Alle 6h', 'Alle 12h'].asMap().entries.map((e) { - final vals = [0, 3, 6, 12]; - return Expanded( - child: ChoiceChip( - label: Text(e.value, style: TextStyle(fontSize: 10, color: _syncIntervall == vals[e.key] ? Colors.white : Colors.grey)), - selected: _syncIntervall == vals[e.key], - selectedColor: MeloTheme.rot, - backgroundColor: MeloTheme.dunkel2, - onSelected: (v) async { - setState(() => _syncIntervall = vals[e.key]); - (await SharedPreferences.getInstance()).setInt('cloud_interval', vals[e.key]); - }, - ), - ); - }).toList(), - ), + const SizedBox(height: 32), ], ), ), ); } - Widget _btn(IconData icon, String label, VoidCallback onTap) { - return ElevatedButton.icon( - style: ElevatedButton.styleFrom( - backgroundColor: MeloTheme.dunkel1, - padding: const EdgeInsets.symmetric(vertical: 14), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - side: const BorderSide(color: MeloTheme.dunkel2), + Widget _statusKarte() { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [Color(0xFF1A0000), Color(0xFF0D0D0D)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(18), + border: Border.all(color: MeloTheme.rot.withValues(alpha: 0.3)), + ), + child: Row( + children: [ + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: MeloTheme.rot.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(14), + ), + child: const Icon(Icons.storage_rounded, color: MeloTheme.rot, size: 24), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '$_serverCount', + style: const TextStyle( + color: Colors.white, + fontSize: 28, + fontWeight: FontWeight.w700, + ), + ), + const Text( + 'Songs auf dem Server', + style: TextStyle(color: MeloTheme.textSekundaer, fontSize: 13), + ), + ], + ), + ), + // Verbindungsstatus-Indikator + Container( + width: 12, + height: 12, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _statusOk ? const Color(0xFF4CAF50) : const Color(0xFFEF5350), + boxShadow: [ + BoxShadow( + color: (_statusOk ? const Color(0xFF4CAF50) : const Color(0xFFEF5350)).withValues(alpha: 0.5), + blurRadius: 8, + ), + ], + ), + ), + ], + ), + ); + } + + Widget _aktionsButton({ + required IconData icon, + required String label, + required String beschreibung, + required VoidCallback onTap, + }) { + return Material( + color: MeloTheme.dunkel1, + borderRadius: BorderRadius.circular(16), + child: InkWell( + onTap: _ladt ? null : onTap, + borderRadius: BorderRadius.circular(16), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + border: Border.all(color: MeloTheme.dunkel2), + ), + child: Column( + children: [ + Icon(icon, color: MeloTheme.rot, size: 28), + const SizedBox(height: 8), + Text(label, style: const TextStyle(color: Colors.white, fontSize: 15, fontWeight: FontWeight.w600)), + const SizedBox(height: 4), + Text(beschreibung, + textAlign: TextAlign.center, + style: const TextStyle(color: MeloTheme.textSekundaer, fontSize: 11)), + ], + ), ), ), - onPressed: _ladt ? null : onTap, - icon: Icon(icon, size: 18, color: MeloTheme.rot), - label: Text(label, style: const TextStyle(color: Colors.white, fontSize: 14)), + ); + } + + Widget _intervallAuswahl() { + final optionen = [ + _IntervallOption('Manuell', 0, Icons.block), + _IntervallOption('3 Std', 3, Icons.timer), + _IntervallOption('6 Std', 6, Icons.timer), + _IntervallOption('12 Std', 12, Icons.timer), + ]; + + return Row( + children: optionen.map((opt) { + final istAktiv = _syncIntervall == opt.wert; + return Expanded( + child: Padding( + padding: EdgeInsets.only( + right: opt != optionen.last ? 8 : 0, + ), + child: GestureDetector( + onTap: () async { + setState(() => _syncIntervall = opt.wert); + (await SharedPreferences.getInstance()).setInt('cloud_interval', opt.wert); + _starteAutoSync(); + }, + child: AnimatedContainer( + duration: const Duration(milliseconds: 250), + padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 6), + decoration: BoxDecoration( + color: istAktiv ? MeloTheme.rot : MeloTheme.dunkel1, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: istAktiv ? MeloTheme.rot : MeloTheme.dunkel2, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + opt.icon, + size: 16, + color: istAktiv ? Colors.white : MeloTheme.textSekundaer, + ), + const SizedBox(height: 6), + Text( + opt.label, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w600, + color: istAktiv ? Colors.white : MeloTheme.textSekundaer, + ), + ), + ], + ), + ), + ), + ), + ); + }).toList(), + ); + } + + Widget _sektionsHeader(String titel) { + return Text( + titel, + style: const TextStyle( + color: MeloTheme.textSekundaer, + fontSize: 11, + fontWeight: FontWeight.w600, + letterSpacing: 1.2, + ), ); } } + +class _IntervallOption { + final String label; + final int wert; + final IconData icon; + const _IntervallOption(this.label, this.wert, this.icon); +} diff --git a/lib/screens/download_screen.dart b/lib/screens/download_screen.dart index cf323e4..57d6b72 100644 --- a/lib/screens/download_screen.dart +++ b/lib/screens/download_screen.dart @@ -2,9 +2,15 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:just_audio/just_audio.dart'; import '../services/download_service.dart'; +import '../services/cloud_service.dart'; +import '../models/song.dart'; +import '../database/db_helper.dart'; import '../utils/farb_theme.dart'; import '../services/melo_logger.dart'; +import '../config/app_config.dart'; +import '../widgets/melo_loader.dart'; class DownloadScreen extends StatefulWidget { final DownloadService downloader; @@ -20,58 +26,114 @@ class DownloadScreen extends StatefulWidget { State createState() => _DownloadScreenState(); } -class _DownloadScreenState extends State { +class _DownloadScreenState extends State with WidgetsBindingObserver { final _urlController = TextEditingController(); + final _cloud = CloudService(); + final _previewPlayer = AudioPlayer(); bool _ladt = false; + List> _globalSongs = []; + String? _previewSid; String? _fehler; String? _erfolg; - String _speicherOrt = 'Intern (Documents/music)'; + String _speicherOrt = 'App-intern (Music/)'; + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + _previewPlayer.dispose(); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.paused || state == AppLifecycleState.inactive) { + _stopPreview(); + } + } + + Future _ladeGlobalListe() async { + final songs = await _cloud.globalList(); + if (mounted) setState(() => _globalSongs = songs.cast>()); + } + + Future _addFromRegistry(String sid, String title) async { + setState(() => _ladt = true); + try { + final dir = Directory('${(await getApplicationDocumentsDirectory()).path}/music'); + if (!await dir.exists()) await dir.create(recursive: true); + final dest = '${dir.path}/cloud_$sid.mp3'; + final ok = await _cloud.download(sid, dest); + if (ok && mounted) { + final song = Song( + titel: title.isNotEmpty ? title : 'Cloud-Song $sid', + kuenstler: 'Melo Registry', + dauerSekunden: 0, + dateiPfad: dest, + downloadQuelle: 'cloud', + istHeruntergeladen: true, + ); + await DbHelper().songEinfuegen(song); + setState(() => _erfolg = 'Song "$title" hinzugefügt!'); + widget.onSongsChanged(); + await _ladeGlobalListe(); + } + } catch (e) { + setState(() => _fehler = 'Fehler beim Hinzufügen'); + MeloLogger().fehler('add_from_registry', e); + } + if (mounted) setState(() => _ladt = false); + } + + Future _startPreview(String sid) async { + if (_previewSid == sid && _previewPlayer.playing) { + await _stopPreview(); + return; + } + _previewSid = sid; + try { + final url = '${AppConfig.cloudUrl}/api/cloud/stream/$sid'; + await _previewPlayer.setUrl(url); + await _previewPlayer.seek(const Duration(seconds: 11)); + await _previewPlayer.play(); + Future.delayed(const Duration(seconds: 10), () { + if (_previewSid == sid) _stopPreview(); + }); + } catch (e) { + MeloLogger().fehler('preview', e); + } + } + + Future _stopPreview() async { + _previewSid = null; + await _previewPlayer.stop(); + } + bool _speichertInDownloads = false; @override void initState() { super.initState(); + WidgetsBinding.instance.addObserver(this); _ladeSpeicherPfad(); - } - - @override - void dispose() { - _urlController.dispose(); - super.dispose(); + _ladeGlobalListe(); } Future _ladeSpeicherPfad() async { final prefs = await SharedPreferences.getInstance(); - final pfad = prefs.getString('speicher_pfad'); - if (pfad != null && pfad.isNotEmpty) { - widget.downloader.setzeSpeicherPfad(pfad); - setState(() => _speicherOrt = pfad.split('/').last); - } else { - final pf = await _standardPfad(); - widget.downloader.setzeSpeicherPfad(pf); + final inDownloads = prefs.getBool('download_in_downloads') ?? false; + if (inDownloads) { + final dir = await getDownloadsDirectory(); + if (dir != null) { + final pfad = '${dir.path}/Melo'; + widget.downloader.setzeSpeicherPfad(pfad); + setState(() { + _speichertInDownloads = true; + _speicherOrt = '⬇ Downloads/Melo'; + }); + } } } - Future _standardPfad() async { - if (Platform.isIOS) return '${(await getApplicationDocumentsDirectory()).path}/music'; - final p = await SharedPreferences.getInstance(); - if (p.getBool('manage_storage') == true) { - final d = await getDownloadsDirectory(); - if (d != null) return '${d.path}/Melo'; - } - final ext = await getExternalStorageDirectory(); - return ext != null ? '${ext.path}/Melo' : '${(await getApplicationDocumentsDirectory()).path}/music'; - } - Future _ordnerDialog() async { - if (Platform.isIOS) { - final appDir = await getApplicationDocumentsDirectory(); - final pfad = '${appDir.path}/music'; - widget.downloader.setzeSpeicherPfad(pfad); - setState(() => _speicherOrt = '📁 App-intern'); - return; - } - - // Android: Ordner wählen via Text-Eingabe oder vordefinierte Optionen final auswahl = await showDialog( context: context, builder: (ctx) => AlertDialog( @@ -80,88 +142,96 @@ class _DownloadScreenState extends State { content: Column( mainAxisSize: MainAxisSize.min, children: [ - _optionTile(ctx, '📁 Intern (App-Ordner)', 'intern', icon: Icons.phone_android), - const Divider(color: MeloTheme.dunkel2), - _optionTile(ctx, '⬇ Downloads/Melo', 'downloads', icon: Icons.download), - const Divider(color: MeloTheme.dunkel2), - _optionTile(ctx, '📂 Eigener Pfad...', 'custom', icon: Icons.folder_open), + _optionTile(ctx, '📁 App-intern (Music/)', 'intern', + icon: Icons.phone_android), + if (!Platform.isIOS) ...[ + const Divider(color: MeloTheme.dunkel2), + _optionTile(ctx, '⬇ Downloads/Melo', 'downloads', + icon: Icons.download), + const Divider(color: MeloTheme.dunkel2), + _optionTile(ctx, '💾 SD-Karte / Extern', 'extern', + icon: Icons.sd_storage), + ], ], ), ), ); - if (auswahl == null || !mounted) return; - String pfad; - if (auswahl == 'custom') { - final ctrl = TextEditingController(); - final p = await showDialog( - context: context, - builder: (ctx) => AlertDialog( - backgroundColor: MeloTheme.dunkel1, - title: const Text('Pfad eingeben', style: TextStyle(color: Colors.white, fontSize: 15)), - content: TextField( - controller: ctrl, autofocus: true, - style: const TextStyle(color: Colors.white), - decoration: InputDecoration( - hintText: '/storage/emulated/0/Music/Melo', - hintStyle: const TextStyle(color: Colors.grey), - border: const OutlineInputBorder(), - prefixIcon: const Icon(Icons.folder, color: Colors.grey), - ), - ), - actions: [ - TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('Abbrechen')), - TextButton(onPressed: () => Navigator.pop(ctx, ctrl.text.trim()), child: const Text('OK', style: TextStyle(color: MeloTheme.rot))), - ], - ), - ); - if (p == null || p.isEmpty) return; - pfad = p; - } else if (auswahl == 'intern') { - pfad = await _standardPfad(); - } else { - final d = await getDownloadsDirectory(); - pfad = d != null ? '${d.path}/Melo' : await _standardPfad(); - } - - await Directory(pfad).create(recursive: true); - widget.downloader.setzeSpeicherPfad(pfad); + if (auswahl == null) return; final prefs = await SharedPreferences.getInstance(); - await prefs.setString('speicher_pfad', pfad); - - setState(() => _speicherOrt = pfad.split('/').last); + if (auswahl == 'downloads') { + final dir = await getDownloadsDirectory(); + if (dir != null) { + final pfad = '${dir.path}/Melo'; + await prefs.setBool('download_in_downloads', true); + widget.downloader.setzeSpeicherPfad(pfad); + setState(() { + _speichertInDownloads = true; + _speicherOrt = '⬇ Downloads/Melo'; + }); + } + } else if (auswahl == 'extern') { + final dirs = await getExternalStorageDirectories(); + if (dirs != null && dirs.isNotEmpty) { + final pfad = '${dirs.first.path}/Melo'; + await prefs.setBool('download_in_downloads', false); + widget.downloader.setzeSpeicherPfad(pfad); + setState(() { + _speichertInDownloads = false; + _speicherOrt = '💾 ${dirs.first.path.split('/').last}/Melo'; + }); + } else { + if (mounted) setState(() => _fehler = 'Kein externer Speicher gefunden'); + } + } else { + await prefs.setBool('download_in_downloads', false); + widget.downloader.setzeSpeicherPfad(''); + setState(() { + _speichertInDownloads = false; + _speicherOrt = '📁 App-intern (Music/)'; + }); + } } - Widget _optionTile(BuildContext ctx, String label, String wert, {IconData? icon}) { + Widget _optionTile(BuildContext ctx, String label, String wert, + {required IconData icon}) { return ListTile( - leading: Icon(icon ?? Icons.folder, color: MeloTheme.rot, size: 20), - title: Text(label, style: const TextStyle(color: Colors.white, fontSize: 13)), + leading: Icon(icon, color: MeloTheme.rot, size: 20), + title: Text(label, + style: const TextStyle(color: Colors.white, fontSize: 13)), onTap: () => Navigator.pop(ctx, wert), ); } - Future _startDownload() async { - final url = _urlController.text.trim(); - if (url.isEmpty) return; + void _starteDownload() async { + final input = _urlController.text.trim(); + if (input.isEmpty) { + setState(() => _fehler = 'Bitte eine YouTube-URL einfügen'); + return; + } setState(() { _ladt = true; _fehler = null; _erfolg = null; }); + MeloLogger().aktion('download_start', {'url': input.substring(0, 40)}); - try { - final song = await widget.downloader.downloadVonUrl(url); - if (!mounted) return; - if (song != null) { - setState(() { _ladt = false; _erfolg = '✅ "${song.titel}" heruntergeladen!'; }); - widget.onSongsChanged(); - } else { - setState(() { _ladt = false; _fehler = widget.downloader.fehler ?? '❌ Download fehlgeschlagen'; }); - } - } catch (e) { - MeloLogger().fehler('download', e); - if (mounted) setState(() { _ladt = false; _fehler = 'Fehler: $e'; }); + final anzahl = await widget.downloader.downloadBatch(input); + if (mounted) { + setState(() { + _ladt = false; + if (anzahl > 0) { + _erfolg = '✅ $anzahl Song${anzahl > 1 ? 's' : ''} gespeichert'; + } else { + _fehler = widget.downloader.fehler ?? 'Download fehlgeschlagen'; + } + }); + widget.onSongsChanged(); } } + void _abbrechen() { + widget.downloader.abbrechen(); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -169,54 +239,271 @@ class _DownloadScreenState extends State { appBar: AppBar( backgroundColor: MeloTheme.dunkel1, title: const Row(children: [ - Icon(Icons.add_circle, color: MeloTheme.rot, size: 20), + Icon(Icons.download, color: MeloTheme.rot, size: 20), SizedBox(width: 8), Text('Lied +', style: TextStyle(color: Colors.white, fontSize: 18)), ]), + actions: [ + if (_erfolg != null || _fehler != null) + IconButton( + icon: const Icon(Icons.refresh, color: Colors.grey, size: 20), + onPressed: () => setState(() { _fehler = null; _erfolg = null; _urlController.clear(); }), + ), + ], ), body: Padding( padding: const EdgeInsets.all(20), - child: Column(children: [ - GestureDetector( - onTap: _ladt ? null : _ordnerDialog, - child: Container( - width: double.infinity, padding: const EdgeInsets.all(12), - decoration: BoxDecoration(color: MeloTheme.dunkel1, borderRadius: BorderRadius.circular(12), border: Border.all(color: MeloTheme.dunkel2)), - child: Row(children: [ - const Icon(Icons.folder, color: MeloTheme.rot, size: 18), const SizedBox(width: 8), - Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Speicherort', style: TextStyle(color: Colors.grey, fontSize: 11)), - Text(_speicherOrt, style: const TextStyle(color: Colors.white, fontSize: 13)), - ])), - const Icon(Icons.chevron_right, color: Colors.grey, size: 18), + child: Column( + children: [ + // ─── Globale Registry (Lied +) ─── + if (_globalSongs.isNotEmpty) ...[ + Row(children: [ + const Icon(Icons.public, color: MeloTheme.rot, size: 16), + const SizedBox(width: 6), + Text('Globale Songs (${_globalSongs.length})', + style: const TextStyle(color: Colors.white70, fontSize: 13, fontWeight: FontWeight.w600)), + const Spacer(), + GestureDetector( + onTap: _ladeGlobalListe, + child: const Icon(Icons.refresh, color: Colors.grey, size: 16), + ), ]), + const SizedBox(height: 8), + SizedBox( + height: 100, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: _globalSongs.length, + itemBuilder: (_, i) { + final s = _globalSongs[i]; + final sid = s['id']?.toString() ?? ''; + final title = s['title']?.toString() ?? '?'; + final isPreviewing = _previewSid == sid; + return Container( + width: 140, + margin: const EdgeInsets.only(right: 8), + decoration: BoxDecoration( + color: isPreviewing ? const Color(0xFF2A0000) : MeloTheme.dunkel1, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: isPreviewing ? MeloTheme.rot : MeloTheme.dunkel2), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(title, style: TextStyle(fontSize: 11, color: Colors.white, fontWeight: FontWeight.w500), + maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center), + const SizedBox(height: 4), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + onTap: () => _startPreview(sid), + child: Icon(isPreviewing ? Icons.stop : Icons.play_arrow, + color: isPreviewing ? Colors.white : MeloTheme.rot, size: 20), + ), + const SizedBox(width: 10), + GestureDetector( + onTap: () => _addFromRegistry(sid, title), + child: const Icon(Icons.add_circle_outline, color: Colors.grey, size: 18), + ), + ], + ), + ], + ), + ); + }, + ), + ), + const Divider(color: MeloTheme.dunkel2), + ], + // ─── Zielordner ─── + GestureDetector( + onTap: _ladt ? null : _ordnerDialog, + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: MeloTheme.dunkel1, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: MeloTheme.dunkel2), + ), + child: Row(children: [ + const Icon(Icons.folder, color: MeloTheme.rot, size: 18), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Speicherort', style: TextStyle(color: Colors.grey, fontSize: 11)), + Text(_speicherOrt, style: const TextStyle(color: Colors.white, fontSize: 13)), + ], + ), + ), + const Icon(Icons.chevron_right, color: Colors.grey, size: 18), + ]), + ), ), - ), - const SizedBox(height: 16), - TextField( - controller: _urlController, style: const TextStyle(color: Colors.white), - decoration: InputDecoration( - hintText: 'YouTube / SoundCloud URL...', hintStyle: const TextStyle(color: Colors.grey), - filled: true, fillColor: MeloTheme.dunkel1, - border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: MeloTheme.dunkel2)), - prefixIcon: const Icon(Icons.link, color: Colors.grey), + const SizedBox(height: 12), + + // ─── Eingabefeld ─── + TextField( + controller: _urlController, + enabled: !_ladt, + maxLines: 3, + style: const TextStyle(color: Colors.white, fontSize: 14), + decoration: InputDecoration( + hintText: 'YouTube-URL hier einfügen...\n\nMehrere URLs: eine pro Zeile\nPlaylists werden erkannt 🎯', + hintStyle: const TextStyle(color: Colors.grey, fontSize: 13), + border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + filled: true, + fillColor: MeloTheme.dunkel1, + contentPadding: const EdgeInsets.all(16), + ), ), - ), - const SizedBox(height: 12), - SizedBox( - width: double.infinity, - child: ElevatedButton.icon( - style: ElevatedButton.styleFrom(backgroundColor: MeloTheme.rot, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))), - onPressed: _ladt ? null : _startDownload, - icon: _ladt ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white)) : const Icon(Icons.download, color: Colors.white), - label: Text(_ladt ? 'Lädt...' : 'Download', style: const TextStyle(color: Colors.white, fontSize: 15)), + const SizedBox(height: 12), + + // ─── Animierte Ladeanzeige (während Download) ─── + if (_ladt) ...[ + MeloLoader( + titel: widget.downloader.aktuellerTitel ?? 'Lade herunter...', + ), + const SizedBox(height: 16), + ], + + // ─── Download-Button ─── + SizedBox( + width: double.infinity, + height: 48, + child: ElevatedButton.icon( + onPressed: _ladt ? null : _starteDownload, + icon: _ladt + ? const SizedBox(width: 20, height: 20, + child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white)) + : const Icon(Icons.download, size: 20), + label: Text(_ladt ? 'Lädt...' : '⬇ Download'), + style: ElevatedButton.styleFrom( + backgroundColor: MeloTheme.rot, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + ), + ), ), - ), - const SizedBox(height: 12), - if (_fehler != null) Padding(padding: const EdgeInsets.only(top: 8), child: Text(_fehler!, style: const TextStyle(color: Colors.red, fontSize: 13))), - if (_erfolg != null) Padding(padding: const EdgeInsets.only(top: 8), child: Text(_erfolg!, style: const TextStyle(color: Colors.green, fontSize: 13))), - ]), + if (_ladt) ...[ + const SizedBox(height: 8), + SizedBox( + width: double.infinity, + height: 40, + child: ElevatedButton.icon( + onPressed: _abbrechen, + icon: const Icon(Icons.cancel, size: 18), + label: const Text('Abbrechen'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red.shade800, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + ), + ), + ), + ], + const SizedBox(height: 16), + + // ─── Fortschritt ─── + if (_ladt) + ListenableBuilder( + listenable: widget.downloader, + builder: (context, _) { + final fortschritt = widget.downloader.fortschritt; + + if (fortschritt <= 0) return const SizedBox.shrink(); + return Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: MeloTheme.dunkel1, + borderRadius: BorderRadius.circular(12), + ), + child: Column(children: [ + LinearProgressIndicator( + value: fortschritt, + color: MeloTheme.rot, + backgroundColor: MeloTheme.dunkel2), + const SizedBox(height: 4), + Text('${(fortschritt * 100).toStringAsFixed(0)}%', + style: const TextStyle(color: Colors.grey, fontSize: 11)), + ]), + ); + }, + ), + + // ─── Erfolg ─── + if (_erfolg != null) + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: MeloTheme.dunkel1, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.green.withValues(alpha: 0.3)), + ), + child: Row(children: [ + const Icon(Icons.check_circle, color: Colors.green, size: 24), + const SizedBox(width: 12), + Expanded(child: Text(_erfolg!, style: const TextStyle(color: Colors.green, fontSize: 13))), + ]), + ), + + // ─── Fehler ─── + if (_fehler != null) + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: MeloTheme.dunkel1, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.red.withValues(alpha: 0.3)), + ), + child: Row(children: [ + const Icon(Icons.error_outline, color: Colors.red, size: 24), + const SizedBox(width: 12), + Expanded(child: Text(_fehler!, style: const TextStyle(color: Colors.red, fontSize: 13))), + ]), + ), + + const Spacer(), + + // ─── Tipps ─── + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: MeloTheme.dunkel1, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('💡 Tipps', style: TextStyle(color: Colors.grey, fontSize: 12, fontWeight: FontWeight.w600)), + const SizedBox(height: 6), + _tipp('Einzel-URL: youtube.com/watch?v=...'), + _tipp('Playlist: youtube.com/playlist?list=...'), + _tipp('Mehrere: eine URL pro Zeile'), + _tipp('Cooldown: 5s zwischen Downloads ⏱'), + ], + ), + ), + const SizedBox(height: 20), + ], + ), ), ); } + + Widget _tipp(String text) { + return Padding( + padding: const EdgeInsets.only(bottom: 4), + child: Row(children: [ + const Text('• ', style: TextStyle(color: MeloTheme.rot, fontSize: 12)), + Expanded(child: Text(text, style: const TextStyle(color: Colors.grey, fontSize: 11))), + ]), + ); + } } diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 0977606..d03970d 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -1,28 +1,21 @@ -import 'dart:async'; -import 'dart:io'; import 'package:flutter/material.dart'; -import '../database/db_helper.dart'; +import 'dart:async'; import '../viewmodels/melo_home_viewmodel.dart'; import '../models/song.dart'; import '../models/playlist.dart'; import '../utils/farb_theme.dart'; -import '../utils/user_effekte.dart'; -import '../services/cloud_service.dart'; -import '../services/favoriten_service.dart'; import '../widgets/mini_player.dart'; import '../widgets/melo_header.dart'; import '../widgets/statistik_card.dart'; -import '../widgets/recent_widget.dart'; -import '../widgets/tag_stats_widget.dart'; import '../widgets/tag_leiste.dart'; import '../widgets/song_tile.dart'; import '../widgets/navidrome_browser.dart'; import '../widgets/playlist_sheet.dart'; import 'download_screen.dart'; import 'cloud_screen.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:permission_handler/permission_handler.dart'; -import '../widgets/cloud_einstellungen.dart'; +import 'settings_screen.dart'; +import '../services/cloud_service.dart'; +import '../services/auth_service.dart'; import '../config/app_config.dart'; class MeloHome extends StatefulWidget { @@ -37,221 +30,12 @@ class _MeloHomeState extends State { final CloudService _cloud = CloudService(); int _aktiverTab = 0; - String _nutzer = 'Baka'; // Aktueller Nutzer - final Future _favoritenZahl = FavoritenService().anzahlFavoriten(); - @override void initState() { super.initState(); - _ladeNutzer(); - _vm.addListener(() => setState(() {})); _vm.ladeSongs(); } - Future _ladeNutzer() async { - final p = await SharedPreferences.getInstance(); - final n = p.getString('melo_nutzer') ?? ''; - if (n.isNotEmpty && mounted) { - setState(() => _nutzer = n); - _vm.setzeNutzer(n); - } - // Erster Start? → einmalig Modus wählen (Cloud oder nur lokal) – Login ist freiwillig - if (!p.containsKey('melo_modus')) { - await _zeigeModusWahl(); - return; - } - // Login-Effekt beim App-Start: nur wenn ein Token vorhanden ist (eingeloggt) - final ok = await CloudService().restoreLogin(); - if (ok && mounted) { - UserEffekt.anwenden(_nutzer); - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(UserEffekt.fuer(_nutzer).begruessung), - duration: const Duration(seconds: 3), - )); - } - } - - /// Einmalige Auswahl beim ersten App-Start: Cloud-Sync oder nur lokal. - Future _zeigeModusWahl() async { - if (!mounted) return; - final cloud = await showDialog( - context: context, - barrierDismissible: false, - builder: (ctx) => AlertDialog( - backgroundColor: MeloTheme.dunkel1, - title: const Text('👋 Willkommen bei Melo!', - style: TextStyle(color: Colors.white, fontSize: 16)), - content: const Text( - 'Wie möchtest du Melo nutzen?', - style: TextStyle(color: Colors.white, fontSize: 14), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx, false), - child: const Text('📱 Nur lokal', - style: TextStyle(color: Colors.white)), - ), - TextButton( - onPressed: () => Navigator.pop(ctx, true), - child: const Text('☁️ Cloud (empfohlen)', - style: TextStyle(color: MeloTheme.rot, fontWeight: FontWeight.bold)), - ), - ], - ), - ); - final p = await SharedPreferences.getInstance(); - await p.setString('melo_modus', cloud == true ? 'cloud' : 'lokal'); - // Cloud gewählt → direkt zum Login-Dialog (Name + Passwort) - if (cloud == true && mounted) { - _nutzerWechseln(); - } - } - - Future _zeigeProfil() async { - final p = await SharedPreferences.getInstance(); - final modus = p.getString('melo_modus') ?? 'cloud'; - // Cloud-Count VOR dem Dialog auflösen (sonst stünde "Instance of Future" da) - final cloudCount = await _cloud.status(); - if (!mounted) return; - showDialog( - context: context, - builder: (ctx) => AlertDialog( - backgroundColor: MeloTheme.dunkel1, - title: Row(children: [ - const Icon(Icons.person, color: MeloTheme.rot, size: 22), - const SizedBox(width: 8), - Text(_nutzer, style: const TextStyle(color: Colors.white, fontSize: 17)), - ]), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - _profilZeile(Icons.music_note, 'Lieder auf Gerät', '${_vm.songs.length}'), - _profilZeile(Icons.cloud, 'Cloud-Server', '${cloudCount?['total'] ?? 0}'), - const Divider(color: MeloTheme.dunkel2), - // Später von "nur lokal" auf Cloud wechseln – jederzeit möglich - if (modus == 'lokal') ...[ - ListTile( - leading: const Icon(Icons.cloud_upload, color: Colors.blueAccent, size: 18), - title: const Text('☁️ Cloud aktivieren', - style: TextStyle(color: Colors.white, fontSize: 13)), - subtitle: const Text('Musik sichern & geräteübergreifend nutzen', - style: TextStyle(color: Colors.grey, fontSize: 11)), - onTap: () async { - final prefs = await SharedPreferences.getInstance(); - await prefs.setString('melo_modus', 'cloud'); - if (ctx.mounted) Navigator.pop(ctx); - _nutzerWechseln(); - }, - ), - const Divider(color: MeloTheme.dunkel2), - ], - ListTile( - leading: const Icon(Icons.swap_horiz, color: Colors.grey, size: 18), - title: const Text('Nutzer wechseln', style: TextStyle(color: Colors.white, fontSize: 13)), - onTap: () { - Navigator.pop(ctx); - _nutzerWechseln(); - }, - ), - ListTile( - leading: const Icon(Icons.logout, color: Colors.red, size: 18), - title: const Text('Abmelden', style: TextStyle(color: Colors.red, fontSize: 13)), - onTap: () async { - // Token wirklich löschen, sonst wäre man gar nicht abgemeldet - await CloudService().logout(); - final prefs = await SharedPreferences.getInstance(); - await prefs.remove('melo_nutzer'); - if (ctx.mounted) Navigator.pop(ctx); - if (mounted) setState(() => _nutzer = ''); - }, - ), - ], - ), - ), - ); - } - - Widget _profilZeile(IconData icon, String label, String wert) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 6), - child: Row(children: [ - Icon(icon, color: MeloTheme.rot, size: 16), - const SizedBox(width: 8), - Expanded(child: Text(label, style: const TextStyle(color: Colors.grey, fontSize: 12))), - Text(wert, style: const TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w600)), - ]), - ); - } - - void _nutzerWechseln() { - final ctrl = TextEditingController(text: _nutzer); - final pwCtrl = TextEditingController(); - showDialog( - context: context, - builder: (ctx) => AlertDialog( - backgroundColor: MeloTheme.dunkel1, - title: const Text('👤 Anmelden', style: TextStyle(color: Colors.white, fontSize: 15)), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - TextField( - controller: ctrl, - autofocus: true, - style: const TextStyle(color: Colors.white), - decoration: const InputDecoration( - hintText: 'Nutzername...', - hintStyle: TextStyle(color: Colors.grey), - border: OutlineInputBorder(), - ), - ), - const SizedBox(height: 8), - TextField( - controller: pwCtrl, - obscureText: true, - style: const TextStyle(color: Colors.white), - decoration: const InputDecoration( - hintText: 'Passwort...', - hintStyle: TextStyle(color: Colors.grey), - border: OutlineInputBorder(), - ), - ), - ], - ), - actions: [ - TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('Abbrechen')), - TextButton( - onPressed: () async { - final name = ctrl.text.trim(); - final pass = pwCtrl.text; - if (name.isEmpty || pass.isEmpty) return; - final ok = await _vm.cloud.login(name, pass); - if (!ctx.mounted) return; - Navigator.pop(ctx); - if (ok) { - final p = await SharedPreferences.getInstance(); - await p.setString('melo_nutzer', name); - if (mounted) setState(() => _nutzer = name); - _vm.setzeNutzer(name); - // Login-Effekt: Akzentfarbe + Sound + Begrüßung pro Person - UserEffekt.anwenden(name); - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(UserEffekt.fuer(name).begruessung), - duration: const Duration(seconds: 3))); - } - } else { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text('❌ Login fehlgeschlagen – Name oder Passwort falsch'))); - } - } - }, - child: const Text('OK', style: TextStyle(color: MeloTheme.rot)), - ), - ], - ), - ); - } @override void dispose() { _vm.dispose(); @@ -448,29 +232,6 @@ class _MeloHomeState extends State { ); } - void _songLoeschen(Song song) async { - if (song.id != null) { - final db = DbHelper(); - await db.loeschSong(song.id!); - if (song.dateiPfad.isNotEmpty) { - try { await File(song.dateiPfad).delete(); } catch (_) {} - } - _vm.ladeSongs(); - } - } - - void _zeigePlaylists() { - showModalBottomSheet( - context: context, - backgroundColor: MeloTheme.schwarz, - isScrollControlled: true, - builder: (_) => SizedBox( - height: MediaQuery.of(context).size.height * 0.7, - child: PlaylistSheet(vm: _vm), - ), - ); - } - void _zeigeAddToPlaylist(Song song) async { final playlists = await _vm.playlists.allePlaylists(); if (!mounted || playlists.isEmpty) { @@ -529,8 +290,8 @@ class _MeloHomeState extends State { ); } - final gesamtMB = _vm.songs.isEmpty ? '0.0' - : (_vm.songs.fold(0, (int s, Song song) => s + song.groesseBytes) / 1048576).toStringAsFixed(1); + final gesamtMB = _vm.songs.isEmpty ? '0' + : (_vm.songs.fold(0, (int s, Song song) => s + song.groesseBytes) / 1048576).toStringAsFixed(0); final gesamtMin = _vm.songs.isEmpty ? 0 : (_vm.songs.fold(0, (int s, Song song) => s + song.dauerSekunden) / 60).round(); @@ -542,23 +303,31 @@ class _MeloHomeState extends State { downloader: _vm.downloader, onSongsChanged: _vm.ladeSongs, ) - : _aktiverTab == 3 - ? CloudScreen(cloud: _cloud, onSongsChanged: _vm.ladeSongs) + : _aktiverTab == 4 + ? CloudScreen(cloud: _cloud) : Column( children: [ - MeloHeader(onDownload: _zeigeDownloadDialog, onSearch: _zeigeSuche, onServer: _zeigeProfil, onSettings: _zeigeEinstellungen), - if (_vm.zeigeBotschaft) _botschaftBanner(), - _tagBereich(), - FutureBuilder( - future: _favoritenZahl, - builder: (context, snap) => StatistikCard( - anzahlSongs: _vm.songs.length, - gesamtMB: gesamtMB, - gesamtMin: gesamtMin, - anzahlFavoriten: snap.data ?? 0, - ), + MeloHeader(onDownload: _zeigeDownloadDialog, onSearch: _zeigeSuche, onServer: _zeigeServerBrowser), + // ─── EIN/AUS: RecentWidget (Zuletzt gehört) ─── + // Entferne die Kommentarzeichen um RecentWidget zu aktivieren: + // RecentWidget(songs: _vm.letzteSongs, onPlay: _vm.spieleSong), + StatistikCard( + anzahlSongs: _vm.songs.length, + gesamtMB: gesamtMB, + gesamtMin: gesamtMin, + anzahlFavoriten: _vm.favoritenIds.length, ), - RecentWidget(songs: _vm.letzteSongs, onPlay: _vm.spieleSong), + // ─── EIN/AUS: Hidden Message "Seit 2008" ─── + // Entferne die Kommentarzeichen um die Botschaft zu aktivieren: + if (_vm.zeigeBotschaft) _botschaftBanner(), + TagLeiste( + tags: _vm.tags, + aktiveTags: _vm.aktiveTags, + onTagToggled: _vm.toggleTag, + ), + // ─── EIN/AUS: TagStatsWidget (Tag-Counts) ─── + // Entferne die Kommentarzeichen um TagStatsWidget zu aktivieren: + // TagStatsWidget(tagCounts: _vm.tagCounts), Expanded(child: _songListe()), const MiniPlayer(), const SizedBox(height: 8), @@ -607,9 +376,7 @@ class _MeloHomeState extends State { ), ), Expanded( - child: songs.isEmpty - ? _emptyStateWidget() - : ListView.builder( + child: ListView.builder( padding: const EdgeInsets.fromLTRB(20, 4, 20, 4), itemCount: songs.length, itemBuilder: (_, i) => SongTile( @@ -619,7 +386,6 @@ class _MeloHomeState extends State { onPlay: _vm.spieleSong, onMetadataChanged: _vm.ladeSongs, onAddToPlaylist: _zeigeAddToPlaylist, - onDelete: _songLoeschen, ), ), ), @@ -627,85 +393,26 @@ class _MeloHomeState extends State { ); } - void _zeigeEinstellungen() { - showDialog( - context: context, - builder: (ctx) => AlertDialog( - backgroundColor: MeloTheme.dunkel1, - title: const Text('⚙ Einstellungen', style: TextStyle(color: Colors.white, fontSize: 16)), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // Stats - Container( - padding: const EdgeInsets.all(12), - margin: const EdgeInsets.only(bottom: 12), - decoration: BoxDecoration(color: MeloTheme.dunkel2, borderRadius: BorderRadius.circular(10)), - child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - _statWert('${_vm.songs.length}', 'Songs'), - _statWert('${(_vm.songs.fold(0, (int s, Song song) => s + song.groesseBytes) / 1048576).toStringAsFixed(1)} MB', 'Größe'), - _statWert('${(_vm.songs.fold(0, (int s, Song song) => s + song.dauerSekunden) / 60).round()} Min', 'Dauer'), - _statWert('${_vm.favoritenIds.length}', '❤️'), - ]), - ), - ListTile( - leading: const Icon(Icons.favorite, color: MeloTheme.rot), - title: const Text('Favoriten', style: TextStyle(color: Colors.white)), - subtitle: const Text('Deine Lieblingssongs', style: TextStyle(color: Colors.grey, fontSize: 12)), - onTap: () { - Navigator.pop(ctx); - _vm.aktiveTags = {'★ Favoriten'}; - }, - ), - ListTile( - leading: const Icon(Icons.cloud, color: MeloTheme.rot), - title: const Text('Cloud Sync', style: TextStyle(color: Colors.white)), - subtitle: const Text('Auto-Sync & Einstellungen', style: TextStyle(color: Colors.grey, fontSize: 12)), - onTap: () { - Navigator.pop(ctx); - showDialog( - context: context, - builder: (_) => const CloudEinstellungen(), - ); - }, - ), - ListTile( - leading: const Icon(Icons.dns, color: MeloTheme.rot), - title: const Text('Server verbinden', style: TextStyle(color: Colors.white)), - subtitle: const Text('Navidrome / Musik-Server', style: TextStyle(color: Colors.grey, fontSize: 12)), - onTap: () { Navigator.pop(ctx); _zeigeServerBrowser(); }, - ), - ListTile( - leading: const Icon(Icons.person, color: MeloTheme.rot), - title: const Text('Profil', style: TextStyle(color: Colors.white)), - subtitle: Text(_nutzer.isEmpty ? 'Nicht angemeldet' : _nutzer, style: const TextStyle(color: Colors.grey, fontSize: 12)), - onTap: () { Navigator.pop(ctx); _zeigeProfil(); }, - ), - SwitchListTile( - title: const Text('Diagnosedaten senden', style: TextStyle(color: Colors.white, fontSize: 13)), - subtitle: const Text('Absturz-Logs & Nutzungsdaten', style: TextStyle(color: Colors.grey, fontSize: 11)), - value: AppConfig.sendeDiagnosedaten, - activeColor: MeloTheme.rot, - onChanged: (v) { - AppConfig.sendeDiagnosedaten = v; - }, - ), - if (Platform.isAndroid) - ListTile( - leading: const Icon(Icons.folder_open, color: MeloTheme.rot), - title: const Text('Voller Speicherzugriff', style: TextStyle(color: Colors.white)), - subtitle: const Text('Zum Speichern in Downloads/Music', style: TextStyle(color: Colors.grey, fontSize: 12)), - onTap: () async { - final status = await Permission.manageExternalStorage.request(); - if (status.isGranted) { - await SharedPreferences.getInstance().then((p) => p.setBool('manage_storage', true)); - } - }, - ), - ], - ), - ), + Future _oeffneEinstellungen() async { + final result = await Navigator.push( + context, + MaterialPageRoute(builder: (_) => const SettingsScreen()), ); + if (!mounted || result == null) return; + + switch (result) { + case 'server': + _zeigeServerBrowser(); + break; + case 'logout': + await AuthService().logout(); + if (mounted) { + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (_) => const MeloHome()), + ); + } + break; + } } Widget _bottomNav() { @@ -718,21 +425,28 @@ class _MeloHomeState extends State { backgroundColor: MeloTheme.schwarz, selectedItemColor: MeloTheme.rot, unselectedItemColor: MeloTheme.textSekundaer, - currentIndex: _aktiverTab.clamp(0, 3), + currentIndex: _aktiverTab, onTap: (i) { + if (i == 5) { + // Einstellungen als Screen öffnen + _oeffneEinstellungen(); + return; + } setState(() => _aktiverTab = i); + // Zurück zu Musik → Filter zurücksetzen wenn Favoriten aktiv if (i == 0 && _vm.aktiveTags.contains('★ Favoriten')) { _vm.aktiveTags.clear(); - } else if (i == 2) { - // Playlisten öffnen - _zeigePlaylists(); + } else if (i == 3) { + _vm.aktiveTags = {'★ Favoriten'}; } }, items: const [ BottomNavigationBarItem(icon: Icon(Icons.music_note, size: 22), label: 'Musik'), BottomNavigationBarItem(icon: Icon(Icons.add_circle, size: 22), label: 'Lied +'), - BottomNavigationBarItem(icon: Icon(Icons.queue_music, size: 22), label: 'Playlisten'), + BottomNavigationBarItem(icon: Icon(Icons.label, size: 22), label: 'Tags'), + BottomNavigationBarItem(icon: Icon(Icons.favorite, size: 22), label: 'Favoriten'), BottomNavigationBarItem(icon: Icon(Icons.cloud, size: 22), label: 'Cloud'), + BottomNavigationBarItem(icon: Icon(Icons.settings, size: 22), label: 'Einstellungen'), ], ), ); @@ -769,8 +483,15 @@ class _MeloHomeState extends State { const Text('💌', style: TextStyle(fontSize: 20)), const SizedBox(width: 10), Expanded( - child: Text(_vm.nutzerBotschaft ?? '🎵 Danke fürs Zuhören!', - style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: Colors.white)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + Text('Seit 2008', + style: TextStyle(fontSize: 14, fontWeight: FontWeight.w700, color: Colors.white)), + Text('Danke, dass du immer da bist ♥', + style: TextStyle(fontSize: 11, color: MeloTheme.textSekundaer)), + ], + ), ), GestureDetector( onTap: _vm.botschaftAusblenden, @@ -781,89 +502,4 @@ class _MeloHomeState extends State { ), ); } - - /// Leerer Zustand – wenn keine Songs in der Bibliothek sind - Widget _emptyStateWidget() { - return Center( - child: Padding( - padding: const EdgeInsets.all(32), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.library_music_outlined, size: 56, color: MeloTheme.rot.withValues(alpha: 0.5)), - const SizedBox(height: 16), - const Text('Noch keine Songs in Melo', - style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600)), - const SizedBox(height: 8), - const Text('Füge Songs über "Lied +" hinzu, verbinde deinen Server oder starte einen lokalen Scan.', - textAlign: TextAlign.center, - style: TextStyle(color: MeloTheme.textSekundaer, fontSize: 12, height: 1.4)), - const SizedBox(height: 20), - ElevatedButton.icon( - onPressed: _scanMusik, - icon: const Icon(Icons.search, size: 16), - label: const Text('Jetzt Musik scannen'), - style: ElevatedButton.styleFrom( - backgroundColor: MeloTheme.dunkel1, - foregroundColor: Colors.white, - side: const BorderSide(color: MeloTheme.dunkel2), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), - ), - ), - ], - ), - ), - ); - } - - Widget _tagBereich() { - return AnimatedSize( - duration: const Duration(milliseconds: 200), - child: Column( - children: [ - // Toggle-Kopf: Tag-Leiste ein-/ausklappen (war vorher unerreichbar!) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Row( - children: [ - Icon(Icons.label_outline, - size: 14, color: MeloTheme.textSekundaer), - const SizedBox(width: 6), - Text('Tags & Filter', - style: const TextStyle( - color: MeloTheme.textSekundaer, fontSize: 12)), - const Spacer(), - IconButton( - visualDensity: VisualDensity.compact, - icon: Icon( - _tagsOffen ? Icons.expand_less : Icons.expand_more, - size: 18, - color: MeloTheme.textSekundaer), - onPressed: () => setState(() => _tagsOffen = !_tagsOffen), - ), - ], - ), - ), - if (_tagsOffen) ...[ - TagLeiste( - tags: _vm.tags, - aktiveTags: _vm.aktiveTags, - onTagToggled: _vm.toggleTag, - ), - const SizedBox(height: 8), - TagStatsWidget(tagCounts: _vm.tagCounts), - ], - ], - ), - ); - } - - Widget _statWert(String wert, String label) { - return Column(children: [ - Text(wert, style: const TextStyle(color: Colors.white, fontSize: 15, fontWeight: FontWeight.w700)), - Text(label, style: const TextStyle(color: Colors.grey, fontSize: 10)), - ]); - } - - bool _tagsOffen = false; } diff --git a/lib/screens/login_screen.dart b/lib/screens/login_screen.dart new file mode 100644 index 0000000..72c4653 --- /dev/null +++ b/lib/screens/login_screen.dart @@ -0,0 +1,640 @@ +import 'package:flutter/material.dart'; +import '../services/auth_service.dart'; +import '../services/navidrome_service.dart'; +import '../utils/farb_theme.dart'; +import 'home_screen.dart'; + +/// Melo Login-Screen – Schwarz+Rot Design +/// Baka-Auth + Navidrome-Credentials +class LoginScreen extends StatefulWidget { + const LoginScreen({super.key}); + + @override + State createState() => _LoginScreenState(); +} + +class _LoginScreenState extends State + with SingleTickerProviderStateMixin { + final AuthService _auth = AuthService(); + final NavidromeService _navidrome = NavidromeService(); + + final _userCtrl = TextEditingController(); + final _passCtrl = TextEditingController(); + final _urlCtrl = TextEditingController(); + final _navUserCtrl = TextEditingController(); + final _navPassCtrl = TextEditingController(); + + bool _ladt = false; + String? _status; + bool _statusOk = false; + bool _zeigeNavidrome = false; + bool _zeigeRegistrierung = false; + bool _passSichtbar = false; + bool _navPassSichtbar = false; + bool _regPassSichtbar = false; + + // Registrierungs-Felder + final _regEmailCtrl = TextEditingController(); + final _regUserCtrl = TextEditingController(); + final _regPassCtrl = TextEditingController(); + + late final AnimationController _animCtrl; + late final Animation _fadeAnim; + + @override + void initState() { + super.initState(); + _animCtrl = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 800), + ); + _fadeAnim = CurvedAnimation(parent: _animCtrl, curve: Curves.easeOut); + _animCtrl.forward(); + _ladeGespeicherteDaten(); + } + + Future _ladeGespeicherteDaten() async { + await _navidrome.ladeGespeicherteZugangsdaten(); + // Navidrome-Daten für Anzeige vorbereiten + if (_navidrome.istVerbunden) { + setState(() { + _zeigeNavidrome = true; + }); + } + } + + @override + void dispose() { + _userCtrl.dispose(); + _passCtrl.dispose(); + _urlCtrl.dispose(); + _navUserCtrl.dispose(); + _navPassCtrl.dispose(); + _regEmailCtrl.dispose(); + _regUserCtrl.dispose(); + _regPassCtrl.dispose(); + _animCtrl.dispose(); + super.dispose(); + } + + void _setzeStatus(String msg, {bool ok = false}) { + setState(() { + _status = msg; + _statusOk = ok; + }); + } + + Future _login() async { + final user = _userCtrl.text.trim(); + final pass = _passCtrl.text.trim(); + + if (user.isEmpty || pass.isEmpty) { + _setzeStatus('Bitte Benutzername und Passwort eingeben'); + return; + } + + setState(() => _ladt = true); + _setzeStatus('Verbinde mit Baka-Auth...'); + + final result = await _auth.login(user, pass); + + if (!mounted) return; + + if (result.erfolg) { + _setzeStatus('✅ Login erfolgreich!', ok: true); + // Navidrome-Credentials speichern falls eingegeben + if (_zeigeNavidrome) { + final url = _urlCtrl.text.trim(); + final nUser = _navUserCtrl.text.trim(); + final nPass = _navPassCtrl.text.trim(); + if (url.isNotEmpty && nUser.isNotEmpty && nPass.isNotEmpty) { + await _navidrome.speichereZugangsdaten(url, nUser, nPass); + } + } + // Kurz warten, dann navigieren + await Future.delayed(const Duration(milliseconds: 600)); + if (mounted) { + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (_) => const MeloHome()), + ); + } + } else { + _setzeStatus('❌ ${result.fehler ?? "Login fehlgeschlagen"}'); + setState(() => _ladt = false); + } + } + + Future _registrieren() async { + final user = _regUserCtrl.text.trim(); + final pass = _regPassCtrl.text.trim(); + final email = _regEmailCtrl.text.trim(); + + if (user.isEmpty || pass.isEmpty) { + _setzeStatus('Bitte alle Felder ausfüllen'); + return; + } + + setState(() => _ladt = true); + _setzeStatus('Registriere...'); + + final result = await _auth.registrieren(user, pass, email); + + if (!mounted) return; + + if (result.erfolg) { + _setzeStatus('✅ Registrierung erfolgreich!', ok: true); + await Future.delayed(const Duration(milliseconds: 600)); + if (mounted) { + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (_) => const MeloHome()), + ); + } + } else { + _setzeStatus('❌ ${result.fehler ?? "Registrierung fehlgeschlagen"}'); + setState(() => _ladt = false); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: MeloTheme.schwarz, + body: SafeArea( + child: Center( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 24), + child: FadeTransition( + opacity: _fadeAnim, + child: _zeigeRegistrierung ? _buildRegister() : _buildLogin(), + ), + ), + ), + ), + ); + } + + Widget _buildLogin() { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + // ─── Logo ─── + _logoBereich(), + const SizedBox(height: 36), + + // ─── Login-Karte ─── + Container( + width: double.infinity, + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: MeloTheme.dunkel1, + borderRadius: BorderRadius.circular(20), + border: Border.all(color: MeloTheme.dunkel2), + boxShadow: [ + BoxShadow( + color: MeloTheme.rot.withValues(alpha: 0.15), + blurRadius: 30, + offset: const Offset(0, 8), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Anmelden', + style: TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 4), + const Text( + 'Mit deinem Baka-Account', + style: TextStyle(color: MeloTheme.textSekundaer, fontSize: 13), + ), + const SizedBox(height: 24), + + // Benutzername + _eingabeFeld( + controller: _userCtrl, + label: 'Benutzername', + icon: Icons.person_outline, + onSubmitted: (_) => _login(), + ), + const SizedBox(height: 16), + + // Passwort + _eingabeFeld( + controller: _passCtrl, + label: 'Passwort', + icon: Icons.lock_outline, + istPasswort: true, + passSichtbar: _passSichtbar, + onPasswortToggle: () => setState(() => _passSichtbar = !_passSichtbar), + onSubmitted: (_) => _login(), + ), + const SizedBox(height: 24), + + // Login Button + SizedBox( + width: double.infinity, + height: 50, + child: ElevatedButton( + onPressed: _ladt ? null : _login, + style: ElevatedButton.styleFrom( + backgroundColor: MeloTheme.rot, + disabledBackgroundColor: MeloTheme.dunkel2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), + ), + elevation: 0, + ), + child: _ladt + ? const SizedBox( + width: 22, + height: 22, + child: CircularProgressIndicator( + strokeWidth: 2.5, + color: Colors.white, + ), + ) + : const Text( + 'Anmelden', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + + // Status + if (_status != null) ...[ + const SizedBox(height: 16), + _statusWidget(), + ], + ], + ), + ), + const SizedBox(height: 20), + + // ─── Navidrome (optional) ─── + _navidromeBereich(), + const SizedBox(height: 20), + + // ─── Registrieren Link ─── + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Noch keinen Account? ', + style: TextStyle(color: MeloTheme.textSekundaer, fontSize: 13), + ), + GestureDetector( + onTap: () => setState(() => _zeigeRegistrierung = true), + child: const Text( + 'Registrieren', + style: TextStyle( + color: MeloTheme.rot, + fontSize: 13, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + + // ─── Offline-Modus ─── + const SizedBox(height: 16), + GestureDetector( + onTap: () { + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (_) => const MeloHome()), + ); + }, + child: const Text( + 'Ohne Login fortfahren', + style: TextStyle(color: MeloTheme.textSekundaer, fontSize: 12), + ), + ), + const SizedBox(height: 24), + ], + ); + } + + Widget _buildRegister() { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + _logoBereich(klein: true), + const SizedBox(height: 28), + + Container( + width: double.infinity, + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: MeloTheme.dunkel1, + borderRadius: BorderRadius.circular(20), + border: Border.all(color: MeloTheme.dunkel2), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Registrieren', + style: TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.w700), + ), + const SizedBox(height: 20), + + _eingabeFeld( + controller: _regUserCtrl, + label: 'Benutzername', + icon: Icons.person_outline, + ), + const SizedBox(height: 14), + _eingabeFeld( + controller: _regEmailCtrl, + label: 'E-Mail (optional)', + icon: Icons.email_outlined, + keyboardType: TextInputType.emailAddress, + ), + const SizedBox(height: 14), + _eingabeFeld( + controller: _regPassCtrl, + label: 'Passwort', + icon: Icons.lock_outline, + istPasswort: true, + passSichtbar: _regPassSichtbar, + onPasswortToggle: () => setState(() => _regPassSichtbar = !_regPassSichtbar), + ), + const SizedBox(height: 24), + + SizedBox( + width: double.infinity, + height: 50, + child: ElevatedButton( + onPressed: _ladt ? null : _registrieren, + style: ElevatedButton.styleFrom( + backgroundColor: MeloTheme.rot, + disabledBackgroundColor: MeloTheme.dunkel2, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), + elevation: 0, + ), + child: _ladt + ? const SizedBox( + width: 22, height: 22, + child: CircularProgressIndicator(strokeWidth: 2.5, color: Colors.white), + ) + : const Text('Registrieren', + style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600)), + ), + ), + + if (_status != null) ...[ + const SizedBox(height: 16), + _statusWidget(), + ], + ], + ), + ), + const SizedBox(height: 16), + GestureDetector( + onTap: () { + setState(() { + _zeigeRegistrierung = false; + _status = null; + }); + }, + child: const Text( + '← Zurück zum Login', + style: TextStyle(color: MeloTheme.rot, fontSize: 13, fontWeight: FontWeight.w600), + ), + ), + ], + ); + } + + Widget _logoBereich({bool klein = false}) { + return Column( + children: [ + // Icon + Container( + width: klein ? 64 : 80, + height: klein ? 64 : 80, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: const LinearGradient( + colors: [Color(0xFFCC0000), Color(0xFF660000)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + boxShadow: [ + BoxShadow( + color: MeloTheme.rot.withValues(alpha: 0.4), + blurRadius: 24, + offset: const Offset(0, 6), + ), + ], + ), + child: const Center( + child: Text( + '♪', + style: TextStyle(fontSize: 36, color: Colors.white, fontWeight: FontWeight.w300), + ), + ), + ), + const SizedBox(height: 16), + const Text( + 'MELO', + style: TextStyle( + color: Colors.white, + fontSize: 32, + fontWeight: FontWeight.w800, + letterSpacing: 6, + ), + ), + const SizedBox(height: 4), + Text( + 'Musik. Für immer.', + style: TextStyle( + color: MeloTheme.rot.withValues(alpha: 0.8), + fontSize: 13, + letterSpacing: 2, + fontWeight: FontWeight.w400, + ), + ), + ], + ); + } + + Widget _navidromeBereich() { + return Column( + children: [ + // Toggle + GestureDetector( + onTap: () => setState(() => _zeigeNavidrome = !_zeigeNavidrome), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + decoration: BoxDecoration( + color: MeloTheme.dunkel1, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: MeloTheme.dunkel2), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + _zeigeNavidrome ? Icons.dns : Icons.dns_outlined, + color: _zeigeNavidrome ? MeloTheme.rot : MeloTheme.textSekundaer, + size: 18, + ), + const SizedBox(width: 8), + Text( + '🌐 Navidrome Server', + style: TextStyle( + color: _zeigeNavidrome ? Colors.white : MeloTheme.textSekundaer, + fontSize: 13, + ), + ), + const SizedBox(width: 6), + Icon( + _zeigeNavidrome ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down, + color: MeloTheme.textSekundaer, + size: 16, + ), + ], + ), + ), + ), + + // Erweiterte Navidrome-Felder + if (_zeigeNavidrome) ...[ + const SizedBox(height: 12), + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: MeloTheme.dunkel1, + borderRadius: BorderRadius.circular(14), + border: Border.all(color: MeloTheme.dunkel2), + ), + child: Column( + children: [ + _eingabeFeld( + controller: _urlCtrl, + label: 'Server-URL', + icon: Icons.link, + hint: 'https://musik.baka-net.de', + ), + const SizedBox(height: 12), + _eingabeFeld( + controller: _navUserCtrl, + label: 'Navidrome Benutzer', + icon: Icons.person_outline, + ), + const SizedBox(height: 12), + _eingabeFeld( + controller: _navPassCtrl, + label: 'Navidrome Passwort', + icon: Icons.lock_outline, + istPasswort: true, + passSichtbar: _navPassSichtbar, + onPasswortToggle: () => setState(() => _navPassSichtbar = !_navPassSichtbar), + ), + ], + ), + ), + ], + ], + ); + } + + Widget _statusWidget() { + return Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), + decoration: BoxDecoration( + color: _statusOk + ? const Color(0xFF0D3B1E) + : const Color(0xFF3B0D0D), + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: _statusOk ? const Color(0xFF2E7D32) : const Color(0xFF7D2E2E), + ), + ), + child: Row( + children: [ + Icon( + _statusOk ? Icons.check_circle : Icons.error_outline, + size: 16, + color: _statusOk ? const Color(0xFF4CAF50) : const Color(0xFFEF5350), + ), + const SizedBox(width: 8), + Expanded( + child: Text( + _status!, + style: TextStyle( + color: _statusOk ? const Color(0xFFA5D6A7) : const Color(0xFFEF9A9A), + fontSize: 12, + ), + ), + ), + ], + ), + ); + } + + Widget _eingabeFeld({ + required TextEditingController controller, + required String label, + required IconData icon, + bool istPasswort = false, + bool passSichtbar = false, + VoidCallback? onPasswortToggle, + String? hint, + TextInputType keyboardType = TextInputType.text, + ValueChanged? onSubmitted, + }) { + return TextField( + controller: controller, + obscureText: istPasswort && !passSichtbar, + keyboardType: keyboardType, + style: const TextStyle(color: Colors.white, fontSize: 15), + onSubmitted: onSubmitted, + decoration: InputDecoration( + labelText: label, + hintText: hint, + hintStyle: const TextStyle(color: Color(0xFF444444), fontSize: 13), + labelStyle: const TextStyle(color: MeloTheme.textSekundaer, fontSize: 13), + prefixIcon: Icon(icon, color: MeloTheme.textSekundaer, size: 20), + filled: true, + fillColor: MeloTheme.dunkel2, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: MeloTheme.rot, width: 1.5), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: MeloTheme.dunkel2), + ), + contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + suffixIcon: istPasswort + ? IconButton( + icon: Icon( + passSichtbar ? Icons.visibility : Icons.visibility_off, + size: 18, + color: MeloTheme.textSekundaer, + ), + onPressed: onPasswortToggle, + ) + : null, + ), + ); + } +} diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart new file mode 100644 index 0000000..86c94ed --- /dev/null +++ b/lib/screens/settings_screen.dart @@ -0,0 +1,231 @@ +import 'package:flutter/material.dart'; +import '../config/app_config.dart'; +import '../utils/farb_theme.dart'; +import 'cloud_screen.dart'; +import '../services/cloud_service.dart'; + +/// Vollwertiger Einstellungen-Screen – kein Popup mehr +class SettingsScreen extends StatefulWidget { + const SettingsScreen({super.key}); + + @override + State createState() => _SettingsScreenState(); +} + +class _SettingsScreenState extends State { + bool _diagnose = AppConfig.sendeDiagnosedaten; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: MeloTheme.schwarz, + appBar: AppBar( + backgroundColor: MeloTheme.dunkel1, + leading: IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white, size: 22), + onPressed: () => Navigator.pop(context), + ), + title: const Row( + children: [ + Icon(Icons.settings, color: MeloTheme.rot, size: 20), + SizedBox(width: 10), + Text( + 'Einstellungen', + style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.w600), + ), + ], + ), + ), + body: ListView( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + children: [ + // ─── Sektion: Verbindung ─── + _sektionHeader('Verbindung'), + _einstellungsKachel( + icon: Icons.cloud_outlined, + titel: 'Cloud Sync', + untertitel: 'Auto-Sync, Upload & Download', + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => CloudScreen(cloud: CloudService()), + ), + ); + }, + ), + _einstellungsKachel( + icon: Icons.dns_outlined, + titel: 'Server verbinden', + untertitel: 'Navidrome Musik-Server', + onTap: () { + // Signal zum Öffnen des Server-Browsers + Navigator.pop(context, 'server'); + }, + ), + const SizedBox(height: 8), + + // ─── Sektion: Daten ─── + _sektionHeader('Daten & Privatsphäre'), + Container( + decoration: BoxDecoration( + color: MeloTheme.dunkel1, + borderRadius: BorderRadius.circular(14), + border: Border.all(color: MeloTheme.dunkel2), + ), + child: SwitchListTile( + title: const Text( + 'Diagnosedaten senden', + style: TextStyle(color: Colors.white, fontSize: 14), + ), + subtitle: const Text( + 'Absturz-Logs & anonyme Nutzungsdaten', + style: TextStyle(color: MeloTheme.textSekundaer, fontSize: 12), + ), + value: _diagnose, + activeColor: MeloTheme.rot, + onChanged: (v) { + setState(() => _diagnose = v); + AppConfig.sendeDiagnosedaten = v; + }, + secondary: const Icon(Icons.bug_report_outlined, color: MeloTheme.rot, size: 22), + ), + ), + const SizedBox(height: 8), + + // ─── Sektion: Info ─── + _sektionHeader('Info'), + Container( + decoration: BoxDecoration( + color: MeloTheme.dunkel1, + borderRadius: BorderRadius.circular(14), + border: Border.all(color: MeloTheme.dunkel2), + ), + child: Column( + children: [ + _infoZeile('Version', '2.31'), + const Divider(color: MeloTheme.dunkel2, height: 1), + _infoZeile('Theme', 'Schwarz + Rot'), + const Divider(color: MeloTheme.dunkel2, height: 1), + _infoZeile('Auth', AppConfig.authUrl), + const Divider(color: MeloTheme.dunkel2, height: 1), + _infoZeile('Cloud', AppConfig.cloudUrl), + ], + ), + ), + const SizedBox(height: 24), + + // ─── Abmelden ─── + SizedBox( + width: double.infinity, + height: 48, + child: OutlinedButton.icon( + onPressed: () { + Navigator.pop(context, 'logout'); + }, + icon: const Icon(Icons.logout, size: 18), + label: const Text('Abmelden', style: TextStyle(fontSize: 14)), + style: OutlinedButton.styleFrom( + foregroundColor: MeloTheme.rot, + side: const BorderSide(color: MeloTheme.rot), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), + ), + ), + ), + const SizedBox(height: 32), + ], + ), + ); + } + + Widget _sektionHeader(String titel) { + return Padding( + padding: const EdgeInsets.fromLTRB(4, 16, 4, 8), + child: Text( + titel, + style: const TextStyle( + color: MeloTheme.textSekundaer, + fontSize: 11, + fontWeight: FontWeight.w600, + letterSpacing: 1.2, + ), + ), + ); + } + + Widget _einstellungsKachel({ + required IconData icon, + required String titel, + required String untertitel, + required VoidCallback onTap, + }) { + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Material( + color: MeloTheme.dunkel1, + borderRadius: BorderRadius.circular(14), + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(14), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14), + border: Border.all(color: MeloTheme.dunkel2), + ), + child: Row( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: MeloTheme.dunkel2, + borderRadius: BorderRadius.circular(10), + ), + child: Icon(icon, color: MeloTheme.rot, size: 20), + ), + const SizedBox(width: 14), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + titel, + style: const TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w500), + ), + const SizedBox(height: 2), + Text( + untertitel, + style: const TextStyle(color: MeloTheme.textSekundaer, fontSize: 12), + ), + ], + ), + ), + const Icon(Icons.chevron_right, color: MeloTheme.textSekundaer, size: 20), + ], + ), + ), + ), + ), + ); + } + + Widget _infoZeile(String label, String wert) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Row( + children: [ + Text( + label, + style: const TextStyle(color: MeloTheme.textSekundaer, fontSize: 13), + ), + const Spacer(), + Text( + wert, + style: const TextStyle(color: Colors.white70, fontSize: 13), + ), + ], + ), + ); + } +} diff --git a/lib/services/auth_service.dart b/lib/services/auth_service.dart new file mode 100644 index 0000000..e4418b5 --- /dev/null +++ b/lib/services/auth_service.dart @@ -0,0 +1,172 @@ +import 'dart:convert'; +import 'package:flutter/foundation.dart'; +import 'package:http/http.dart' as http; +import 'package:shared_preferences/shared_preferences.dart'; +import '../config/app_config.dart'; +import '../services/melo_logger.dart'; + +/// Baka-Auth Service – JWT-basierte Authentifizierung +/// Integriert mit https://baka-net.de/auth +class AuthService { + static final AuthService _instance = AuthService._(); + factory AuthService() => _instance; + AuthService._(); + + String? _token; + String _user = ''; + bool _initialisiert = false; + + bool get istEingeloggt => _token != null && _token!.isNotEmpty; + String get benutzer => _user; + String? get token => _token; + + /// Auth-Header für API-Requests + Map get authHeader => { + 'Authorization': 'Bearer ${_token ?? ''}', + 'Content-Type': 'application/json', + }; + + /// Lädt gespeicherten Token beim App-Start + Future initialisieren() async { + if (_initialisiert) return; + try { + final prefs = await SharedPreferences.getInstance(); + _token = prefs.getString('baka_token'); + _user = prefs.getString('baka_user') ?? ''; + if (_token != null && _token!.isNotEmpty) { + MeloLogger().zustand('auth_restored', {'user': _user}); + } + } catch (e) { + debugPrint('AuthService init Fehler: $e'); + } + _initialisiert = true; + } + + /// Login über Baka-Auth-Server + /// Gibt true zurück bei Erfolg, false bei Fehler + Future login(String user, String password) async { + try { + final response = await http + .post( + Uri.parse('${AppConfig.authUrl}/login'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode({ + 'username': user, + 'password': password, + }), + ) + .timeout(const Duration(seconds: 10)); + + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + _token = data['token'] as String?; + _user = user; + + if (_token != null && _token!.isNotEmpty) { + // Token speichern + final prefs = await SharedPreferences.getInstance(); + await prefs.setString('baka_token', _token!); + await prefs.setString('baka_user', _user); + + MeloLogger().aktion('auth_login_ok', {'user': _user}); + return AuthResult.ok; + } + } + + // Fehler vom Server parsen + String fehler = 'Unbekannter Fehler'; + try { + final data = jsonDecode(response.body); + fehler = data['error'] as String? ?? 'Login fehlgeschlagen (${response.statusCode})'; + } catch (_) { + fehler = 'Server nicht erreichbar (${response.statusCode})'; + } + + MeloLogger().fehler('auth_login_fail', fehler); + return AuthResult.fehlgeschlagen(fehler); + } catch (e) { + final msg = 'Keine Verbindung zum Auth-Server'; + MeloLogger().fehler('auth_login_error', e); + return AuthResult.fehlgeschlagen(msg); + } + } + + /// Registrierung über Baka-Auth-Server + Future registrieren(String user, String password, String email) async { + try { + final response = await http + .post( + Uri.parse('${AppConfig.authUrl}/register'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode({ + 'username': user, + 'password': password, + 'email': email, + }), + ) + .timeout(const Duration(seconds: 10)); + + if (response.statusCode == 200 || response.statusCode == 201) { + final data = jsonDecode(response.body); + _token = data['token'] as String?; + _user = user; + + if (_token != null && _token!.isNotEmpty) { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString('baka_token', _token!); + await prefs.setString('baka_user', _user); + + MeloLogger().aktion('auth_register_ok', {'user': _user}); + return AuthResult.ok; + } + } + + String fehler = 'Registrierung fehlgeschlagen'; + try { + final data = jsonDecode(response.body); + fehler = data['error'] as String? ?? fehler; + } catch (_) {} + + return AuthResult.fehlgeschlagen(fehler); + } catch (e) { + return AuthResult.fehlgeschlagen('Keine Verbindung zum Server'); + } + } + + /// Token beim Server validieren + Future tokenPruefen() async { + if (_token == null) return false; + try { + final response = await http + .get( + Uri.parse('${AppConfig.authUrl}/verify'), + headers: authHeader, + ) + .timeout(const Duration(seconds: 5)); + return response.statusCode == 200; + } catch (_) { + return false; + } + } + + /// Ausloggen – Token löschen + Future logout() async { + _token = null; + _user = ''; + final prefs = await SharedPreferences.getInstance(); + await prefs.remove('baka_token'); + await prefs.remove('baka_user'); + MeloLogger().aktion('auth_logout', {}); + } +} + +/// Ergebnis eines Auth-Versuchs +class AuthResult { + final bool erfolg; + final String? fehler; + + const AuthResult._(this.erfolg, this.fehler); + + static const ok = AuthResult._(true, null); + static AuthResult fehlgeschlagen(String msg) => AuthResult._(false, msg); +} diff --git a/lib/services/cloud_service.dart b/lib/services/cloud_service.dart index 96ea701..b059f39 100644 --- a/lib/services/cloud_service.dart +++ b/lib/services/cloud_service.dart @@ -1,102 +1,45 @@ import 'dart:convert'; import 'dart:io'; import 'package:http/http.dart' as http; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import '../config/app_config.dart'; +import '../services/auth_service.dart'; import '../services/melo_logger.dart'; -/// Cloud-Sync Service für Melo Registry. -/// Auth: Bearer-JWT vom Baka-Auth-Server (Login mit Nutzername + Passwort). -/// Der alte X-API-Key/X-User-Mechanismus wurde entfernt (IDOR-Lücke). +/// Cloud-Sync Service für Melo Registry +/// Authentifizierung via Baka-Auth JWT-Token class CloudService { - static final CloudService _instanz = CloudService._(); - factory CloudService() => _instanz; - CloudService._(); - static String get _base => AppConfig.cloudUrl; - static String get _authBase => AppConfig.authUrl; - String _user = ''; - String _token = ''; - /// Token liegt verschlüsselt im Keychain/Keystore (flutter_secure_storage) — - /// nicht mehr im Klartext in SharedPreferences (Security-Audit CRIT-1). - static const _secure = FlutterSecureStorage(); - - String get user => _user; - String get token => _token; - bool get istAngemeldet => _token.isNotEmpty; - - /// Echter Login gegen den Baka-Auth-Server. - /// Der Token wird gespeichert und bei allen Cloud-Calls als - /// Authorization: Bearer `token` mitgeschickt. - Future login(String user, String pass) async { + /// Login mit Baka-Auth – Token wird aus AuthService bezogen + Future login(String user) async { + _user = user; try { final r = await http - .post(Uri.parse('$_authBase/login'), - headers: {'Content-Type': 'application/json'}, - body: jsonEncode({'username': user, 'password': pass})) - .timeout(const Duration(seconds: 10)); - if (r.statusCode == 200) { - final d = jsonDecode(r.body); - if (d['status'] == 'ok' && d['token'] != null) { - _user = d['username'] as String? ?? user; - _token = d['token'] as String; - await _speichereToken(); - MeloLogger.cloudToken = _token; - return true; - } - } - } catch (_) {} - return false; - } - - /// Stellt gespeicherten Token wieder her (Auto-Login nach App-Start). - /// Migriert einmalig alte SharedPreferences-Einträge in SecureStorage. - Future restoreLogin() async { - final prefs = await SharedPreferences.getInstance(); - var t = await _secure.read(key: 'melo_cloud_token') ?? ''; - var u = await _secure.read(key: 'melo_cloud_user') ?? ''; - // Migration alter Versionen (Token lag früher in SharedPreferences) - if (t.isEmpty) { - final altT = prefs.getString('melo_cloud_token') ?? ''; - final altU = prefs.getString('melo_cloud_user') ?? ''; - if (altT.isNotEmpty) { - t = altT; - u = altU; - await _secure.write(key: 'melo_cloud_token', value: t); - if (u.isNotEmpty) { - await _secure.write(key: 'melo_cloud_user', value: u); - } - await prefs.remove('melo_cloud_token'); - await prefs.remove('melo_cloud_user'); - } + .get(Uri.parse('$_base/api/cloud/status'), + headers: _authHeader) + .timeout(const Duration(seconds: 5)); + return r.statusCode == 200; + } catch (_) { + return false; } - if (t.isEmpty) return false; - _token = t; - _user = u; - MeloLogger.cloudToken = t; - return true; } - Future logout() async { - _token = ''; - _user = ''; - MeloLogger.cloudToken = null; - await _secure.delete(key: 'melo_cloud_token'); - await _secure.delete(key: 'melo_cloud_user'); + Map get _authHeader { + final token = AuthService().token; + final headers = { + 'X-API-Key': AppConfig.ytProxyApiKey, + }; + if (_user.isNotEmpty) { + headers['X-User'] = _user; + } + // Baka-Auth JWT Token mitsenden falls vorhanden + if (token != null && token.isNotEmpty) { + headers['Authorization'] = 'Bearer $token'; + } + return headers; } - Future _speichereToken() async { - await _secure.write(key: 'melo_cloud_token', value: _token); - await _secure.write(key: 'melo_cloud_user', value: _user); - } - - Map get _authHeader => { - if (_token.isNotEmpty) 'Authorization': 'Bearer $_token', - }; - Future status() => _get('/api/cloud/status'); Future> listSongs() async { @@ -126,11 +69,7 @@ class CloudService { headers: _authHeader) .timeout(const Duration(seconds: 120)); if (r.statusCode == 200) { - final file = File(destPath); - if (!await file.parent.exists()) { - await file.parent.create(recursive: true); - } - await file.writeAsBytes(r.bodyBytes); + await File(destPath).writeAsBytes(r.bodyBytes); return true; } return false; @@ -229,26 +168,4 @@ class CloudService { return false; } } - - /// Löscht alle Cloud-Songs des angemeldeten Users (inkl. Server-Dateien). - Future loescheMusik() async { - return _postOhneBody('/api/cloud/delete-music'); - } - - /// Löscht ALLE Cloud-Daten des Users (Musik + Shares + Ordner). - Future loescheAlles() async { - return _postOhneBody('/api/cloud/delete-all'); - } - - Future _postOhneBody(String path) async { - try { - final r = await http - .post(Uri.parse('$_base$path'), - headers: {..._authHeader, 'Content-Type': 'application/json'}) - .timeout(const Duration(seconds: 30)); - return r.statusCode == 200; - } catch (_) { - return false; - } - } } diff --git a/lib/services/download_service.dart b/lib/services/download_service.dart index a3941b3..4eea7fc 100644 --- a/lib/services/download_service.dart +++ b/lib/services/download_service.dart @@ -3,12 +3,11 @@ import 'dart:convert'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; -import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import '../models/song.dart'; import '../database/db_helper.dart'; import 'melo_logger.dart'; -import 'cloud_service.dart'; +import '../config/app_config.dart'; /// Download-Service: Lädt YouTube-Audio über den yt-proxy herunter. /// Nutzt ChangeNotifier für UI-Updates via ListenableBuilder. @@ -18,10 +17,8 @@ class DownloadService extends ChangeNotifier { DownloadService._(); static const String _proxyBasisUrl = 'https://yt.baka-net.de'; - static Map get _authHeader { - final t = CloudService().token; - return {if (t.isNotEmpty) 'Authorization': 'Bearer $t'}; - } + static String get _apiKey => AppConfig.ytProxyApiKey; + static Map get _authHeader => {'X-API-Key': _apiKey}; final DbHelper _db = DbHelper(); @@ -150,14 +147,6 @@ class DownloadService extends ChangeNotifier { String? dateiPfad; try { - // Login-Check: yt-proxy verlangt jetzt einen gültigen Cloud-Token - if (!CloudService().istAngemeldet) { - _fehler = 'Bitte zuerst in der Cloud anmelden (Cloud-Tab)'; - _ladt = false; - notifyListeners(); - return null; - } - // URL-Validierung if (!url.contains('youtube.com') && !url.contains('youtu.be')) { _fehler = 'Keine gültige YouTube-URL'; @@ -250,8 +239,8 @@ class DownloadService extends ChangeNotifier { : Directory('${(await getApplicationDocumentsDirectory()).path}/music'); if (!await dir.exists()) await dir.create(recursive: true); - // Sicheren Dateinamen erstellen (p.basename verhindert Path-Traversal) - final safeName = p.basename(titel).replaceAll(RegExp(r'[^\w\s-]'), '').trim(); + // Sicheren Dateinamen erstellen + final safeName = titel.replaceAll(RegExp(r'[^\w\s-]'), '').trim(); final lokalerName = '${safeName.isEmpty ? "song" : safeName}.mp3'; dateiPfad = '${dir.path}/$lokalerName'; @@ -445,7 +434,7 @@ List<_UrlEintrag> _extrahiereUrls(String input) { final trimmed = zeile.trim(); if (trimmed.isEmpty) continue; - if (trimmed.contains('list=')) { + if (trimmed.contains('playlist') || trimmed.contains('list=')) { result.add(_UrlEintrag(trimmed, '📋 Playlist')); } else if (trimmed.contains('youtube.com') || trimmed.contains('youtu.be')) { result.add(_UrlEintrag(trimmed, '🎵 Song')); diff --git a/lib/services/navidrome_service.dart b/lib/services/navidrome_service.dart index 6b9cc0d..5018ffe 100644 --- a/lib/services/navidrome_service.dart +++ b/lib/services/navidrome_service.dart @@ -1,12 +1,10 @@ import 'dart:convert'; import 'dart:io'; -import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:crypto/crypto.dart'; import 'package:http/http.dart' as http; -import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import '../models/song.dart'; import '../database/db_helper.dart'; @@ -66,10 +64,6 @@ class SubsonicAlbum { class NavidromeService { final DbHelper _db = DbHelper(); - /// Passwort & Zugangsdaten liegen verschlüsselt im Keychain/Keystore - /// (flutter_secure_storage) statt im Klartext in SharedPreferences. - static const _secure = FlutterSecureStorage(); - String _serverUrl = ''; String _user = ''; String _password = ''; @@ -83,17 +77,16 @@ class NavidromeService { _serverUrl = url.endsWith('/') ? url.substring(0, url.length - 1) : url; _user = user; _password = password; - // Kryptographisch sicherer Salt (vorher millisecondsSinceEpoch = vorhersagbar) - final rng = Random.secure(); - _salt = base64Encode(List.generate(16, (_) => rng.nextInt(256))); + _salt = DateTime.now().millisecondsSinceEpoch.toString(); _token = md5.convert(utf8.encode(_password + _salt)).toString(); } Future ladeGespeicherteZugangsdaten() async { try { - final url = await _secure.read(key: 'navidrome_url'); - final user = await _secure.read(key: 'navidrome_user'); - final pass = await _secure.read(key: 'navidrome_pass'); + final prefs = await SharedPreferences.getInstance(); + final url = prefs.getString('navidrome_url'); + final user = prefs.getString('navidrome_user'); + final pass = prefs.getString('navidrome_pass'); if (url != null && user != null && pass != null && url.isNotEmpty) { setCredentials(url, user, pass); } @@ -105,9 +98,10 @@ class NavidromeService { Future speichereZugangsdaten(String url, String user, String password) async { setCredentials(url, user, password); try { - await _secure.write(key: 'navidrome_url', value: url); - await _secure.write(key: 'navidrome_user', value: user); - await _secure.write(key: 'navidrome_pass', value: password); + final prefs = await SharedPreferences.getInstance(); + await prefs.setString('navidrome_url', url); + await prefs.setString('navidrome_user', user); + await prefs.setString('navidrome_pass', password); } catch (e) { debugPrint('Fehler beim Speichern der Navidrome-Zugangsdaten: $e'); } @@ -184,7 +178,7 @@ class NavidromeService { final musikDir = Directory('${dir.path}/music'); if (!await musikDir.exists()) await musikDir.create(recursive: true); - final safeName = p.basename(s.titel).replaceAll(RegExp(r'[^\w\s-]'), '').trim(); + final safeName = s.titel.replaceAll(RegExp(r'[^\w\s-]'), '').trim(); final kurzId = s.id.length > 8 ? s.id.substring(0, 8) : s.id; final dateiName = '${safeName.isEmpty ? "song" : safeName}_$kurzId.mp3'; final dateiPfad = '${musikDir.path}/$dateiName'; diff --git a/lib/viewmodels/melo_home_viewmodel.dart b/lib/viewmodels/melo_home_viewmodel.dart index 6129658..f32453b 100644 --- a/lib/viewmodels/melo_home_viewmodel.dart +++ b/lib/viewmodels/melo_home_viewmodel.dart @@ -1,6 +1,5 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; -import 'package:path/path.dart' as p; import '../database/db_helper.dart'; import '../services/player_service.dart'; import '../services/musik_scanner.dart'; @@ -8,11 +7,6 @@ import '../services/favoriten_service.dart'; import '../services/download_service.dart'; import '../services/navidrome_service.dart'; import '../services/playlist_service.dart'; -import '../services/cloud_service.dart'; -import '../services/melo_logger.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:path_provider/path_provider.dart'; -import 'dart:io'; import '../models/song.dart'; import '../models/tag.dart'; @@ -24,8 +18,6 @@ class MeloHomeViewModel extends ChangeNotifier { final DownloadService downloader = DownloadService(); final NavidromeService navidrome = NavidromeService(); final PlaylistService playlists = PlaylistService(); - final CloudService cloud = CloudService(); - Timer? _autoSyncTimer; List songs = []; Set aktiveTags = {}; @@ -42,19 +34,6 @@ class MeloHomeViewModel extends ChangeNotifier { int _playCount = 0; static const int _botschaftSchwellwert = 10; - String? _nutzerBotschaft; - - /// Per-User Easteregg-Botschaften - static const _botschaften = { - 'Baka': '💌 Seit 2008 – Danke, dass du immer da bist ♥', - 'Tinker': '🎀 Für meine beste Freundin – Melo & Melo 💕', - }; - - String? get nutzerBotschaft => _nutzerBotschaft; - - void setzeNutzer(String name) { - _nutzerBotschaft = _botschaften[name]; - } StreamSubscription? _positionsSub; int _letzteGespeicherteSekunde = -1; @@ -87,10 +66,10 @@ class MeloHomeViewModel extends ChangeNotifier { if (aktiveTags.contains('★ Favoriten')) { return songs.where((s) => s.id != null && favoritenIds.contains(s.id)).toList(); } - // Filter: Songs mit ALLEN aktiven Tags (AND-Logik) + // Filter: Songs mit mindestens einem der aktiven Tags return songs.where((s) { if (s.tagIds == null) return false; - return aktiveTags.every((tagName) { + return aktiveTags.any((tagName) { final tag = _tagsMap[tagName]; return tag != null && s.tagIds!.contains(tag.id); }); @@ -102,22 +81,29 @@ class MeloHomeViewModel extends ChangeNotifier { notifyListeners(); try { - // Auto-Restore der Navidrome-Session beim Start - // Dummies bereinigen - await db.alteDummiesLoeschen(); + var alle = await db.alleSongs(); - await navidrome.ladeGespeicherteZugangsdaten(); - if (navidrome.istVerbunden) { - ladeNavidromeAlben(); + if (alle.isEmpty) { + final beispiele = [ + Song(titel: 'Leichtes Gepäck', kuenstler: 'Silbermond', album: 'Schritte', + dauerSekunden: 232, dateiPfad: '', groesseBytes: 5242880, istHeruntergeladen: true), + Song(titel: 'Auf uns', kuenstler: 'Andreas Bourani', album: 'Hey', + dauerSekunden: 241, dateiPfad: '', groesseBytes: 4819200, istHeruntergeladen: true), + Song(titel: '80 Millionen', kuenstler: 'Max Giesinger', album: 'Der Junge', + dauerSekunden: 225, dateiPfad: '', groesseBytes: 5107200, istHeruntergeladen: true), + Song(titel: 'Tage wie diese', kuenstler: 'Die Toten Hosen', album: 'Ballast', + dauerSekunden: 252, dateiPfad: '', groesseBytes: 4300800, istHeruntergeladen: true), + Song(titel: 'Atlantis', kuenstler: 'Frida Gold', album: 'Liebe', + dauerSekunden: 228, dateiPfad: '', groesseBytes: 3987200, istHeruntergeladen: true), + ]; + await db.songsEinfuegen(beispiele); + alle = await db.alleSongs(); } - // Echte Songs aus SQLite laden (ohne unspielbare Fake-Dummies) - songs = await db.alleSongs(); - + songs = alle; favoritenIds = await favoriten.favoritenIds(); letzteSongs = await db.letzteWiedergaben(); await ladeTags(); - _starteCloudSyncScheduler(); } catch (e, stack) { debugPrint('ladeSongs Fehler: $e\n$stack'); } @@ -282,43 +268,8 @@ class MeloHomeViewModel extends ChangeNotifier { @override void dispose() { - _autoSyncTimer?.cancel(); _positionsSub?.cancel(); player.dispose(); super.dispose(); } - - Future _starteCloudSyncScheduler() async { - _autoSyncTimer?.cancel(); - final prefs = await SharedPreferences.getInstance(); - final autoSync = prefs.getBool('cloud_auto') ?? true; - final intervalStunden = prefs.getInt('cloud_interval') ?? 6; - - if (!autoSync || intervalStunden <= 0) return; - - _autoSyncTimer = Timer.periodic(Duration(hours: intervalStunden), (_) async { - try { - await cloud.restoreLogin(); - if (!cloud.istAngemeldet) return; - final serverSongs = await cloud.listSongs(); - if (serverSongs.isEmpty) return; - - // Nur neue Songs herunterladen - final dir = Directory('${(await getApplicationDocumentsDirectory()).path}/music'); - if (!await dir.exists()) await dir.create(recursive: true); - final localFiles = dir.listSync().whereType() - .map((f) => f.path.split('/').last).toSet(); - - for (final s in serverSongs) { - final title = p.basename(s['title'].toString()); - if (localFiles.contains(title)) continue; // schon lokal - await cloud.download(s['id'].toString(), '${dir.path}/$title'); - } - - await ladeSongs(); // UI aktualisieren - } catch (e) { - MeloLogger().fehler('background_auto_sync', e); - } - }); - } } diff --git a/lib/widgets/song_tile.dart b/lib/widgets/song_tile.dart index 91af942..ab1bd75 100644 --- a/lib/widgets/song_tile.dart +++ b/lib/widgets/song_tile.dart @@ -12,7 +12,6 @@ class SongTile extends StatelessWidget { final ValueChanged onPlay; final VoidCallback onMetadataChanged; final ValueChanged? onAddToPlaylist; - final ValueChanged? onDelete; const SongTile({ super.key, @@ -22,7 +21,6 @@ class SongTile extends StatelessWidget { required this.onPlay, required this.onMetadataChanged, this.onAddToPlaylist, - this.onDelete, }); @override @@ -64,19 +62,14 @@ class SongTile extends StatelessWidget { ), InkWell( borderRadius: BorderRadius.circular(50), - onTap: () { - // Guard gegen null-ID (defensive) - final id = song.id; - if (id == null) return; - showDialog( - context: context, - builder: (_) => TagAuswahlDialog( - songId: id, - songTitel: song.titel, - onChanged: onMetadataChanged, - ), - ); - }, + onTap: () => showDialog( + context: context, + builder: (_) => TagAuswahlDialog( + songId: song.id!, + songTitel: song.titel, + onChanged: onMetadataChanged, + ), + ), child: Padding( padding: const EdgeInsets.all(6), child: const Icon(Icons.label_outline, size: 14, color: MeloTheme.textSekundaer), @@ -106,39 +99,6 @@ class SongTile extends StatelessWidget { ], ), onTap: hatDatei ? () => onPlay(song) : null, - onLongPress: onDelete != null ? () { - showModalBottomSheet( - context: context, - backgroundColor: MeloTheme.dunkel1, - builder: (ctx) => SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - leading: const Icon(Icons.edit, color: Colors.white), - title: const Text('Bearbeiten', style: TextStyle(color: Colors.white)), - onTap: () async { - Navigator.pop(ctx); - final geaendert = await showDialog( - context: context, - builder: (_) => MetadatenDialog(song: song), - ); - if (geaendert == true) onMetadataChanged(); - }, - ), - ListTile( - leading: const Icon(Icons.delete, color: Colors.red), - title: const Text('Löschen', style: TextStyle(color: Colors.red)), - onTap: () { - Navigator.pop(ctx); - onDelete!(song); - }, - ), - ], - ), - ), - ); - } : null, ); } } diff --git a/macos/Podfile.lock b/macos/Podfile.lock new file mode 100644 index 0000000..0a5fc2c --- /dev/null +++ b/macos/Podfile.lock @@ -0,0 +1,16 @@ +PODS: + - FlutterMacOS (1.0.0) + +DEPENDENCIES: + - FlutterMacOS (from `Flutter/ephemeral`) + +EXTERNAL SOURCES: + FlutterMacOS: + :path: Flutter/ephemeral + +SPEC CHECKSUMS: + FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 + +PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009 + +COCOAPODS: 1.16.2 diff --git a/macos_backup2/.gitignore b/macos_backup2/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/macos_backup2/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/macos_backup2/Flutter/Flutter-Debug.xcconfig b/macos_backup2/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/macos_backup2/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos_backup2/Flutter/Flutter-Release.xcconfig b/macos_backup2/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/macos_backup2/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos_backup2/Flutter/GeneratedPluginRegistrant.swift b/macos_backup2/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..f774dc0 --- /dev/null +++ b/macos_backup2/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,20 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import audio_service +import audio_session +import just_audio +import shared_preferences_foundation +import sqflite_darwin + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin")) + AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) + JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) +} diff --git a/macos_backup2/Podfile b/macos_backup2/Podfile new file mode 100644 index 0000000..ff5ddb3 --- /dev/null +++ b/macos_backup2/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.15' + +# 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', 'ephemeral', '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 Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_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_macos_build_settings(target) + end +end diff --git a/macos_backup2/Podfile.lock b/macos_backup2/Podfile.lock new file mode 100644 index 0000000..0a5fc2c --- /dev/null +++ b/macos_backup2/Podfile.lock @@ -0,0 +1,16 @@ +PODS: + - FlutterMacOS (1.0.0) + +DEPENDENCIES: + - FlutterMacOS (from `Flutter/ephemeral`) + +EXTERNAL SOURCES: + FlutterMacOS: + :path: Flutter/ephemeral + +SPEC CHECKSUMS: + FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 + +PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009 + +COCOAPODS: 1.16.2 diff --git a/macos_backup2/Runner.xcodeproj/project.pbxproj b/macos_backup2/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..f04fe7e --- /dev/null +++ b/macos_backup2/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,807 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 1EF72F07E3D3681C335406C6 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D3D7FA51371D4CBFC6AC9FA5 /* Pods_Runner.framework */; }; + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; + AACBA3FB82C466C0D0D0B64E /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C91015B35802EC0E8670BAF /* Pods_RunnerTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* melo_app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = melo_app.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 3C91015B35802EC0E8670BAF /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 55F71C7DFDB20C59BF73EAD7 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 5762EDC4B2221CFE992C3EE3 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 6B469B40C5E42E480AF52E74 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 93F65671892C523CB8CBD7CF /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + C57627DB9855C4DEF21E18BA /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + D24D7755018E213035056554 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + D3D7FA51371D4CBFC6AC9FA5 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + AACBA3FB82C466C0D0D0B64E /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + 1EF72F07E3D3681C335406C6 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + D18E7E1F246ADA1C0D73B904 /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* melo_app.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D18E7E1F246ADA1C0D73B904 /* Pods */ = { + isa = PBXGroup; + children = ( + 55F71C7DFDB20C59BF73EAD7 /* Pods-Runner.debug.xcconfig */, + 5762EDC4B2221CFE992C3EE3 /* Pods-Runner.release.xcconfig */, + 6B469B40C5E42E480AF52E74 /* Pods-Runner.profile.xcconfig */, + 93F65671892C523CB8CBD7CF /* Pods-RunnerTests.debug.xcconfig */, + C57627DB9855C4DEF21E18BA /* Pods-RunnerTests.release.xcconfig */, + D24D7755018E213035056554 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + D3D7FA51371D4CBFC6AC9FA5 /* Pods_Runner.framework */, + 3C91015B35802EC0E8670BAF /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 42029007D2624CC971333D31 /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 3995B6E9A2D4E69AFEF97BBA /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* melo_app.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + 3995B6E9A2D4E69AFEF97BBA /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 42029007D2624CC971333D31 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 93F65671892C523CB8CBD7CF /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.melo.meloApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/melo_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/melo_app"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C57627DB9855C4DEF21E18BA /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.melo.meloApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/melo_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/melo_app"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D24D7755018E213035056554 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.melo.meloApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/melo_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/melo_app"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/macos_backup2/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos_backup2/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos_backup2/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos_backup2/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos_backup2/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..dc3f799 --- /dev/null +++ b/macos_backup2/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos_backup2/Runner.xcworkspace/contents.xcworkspacedata b/macos_backup2/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/macos_backup2/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/macos_backup2/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos_backup2/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos_backup2/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos_backup2/Runner/AppDelegate.swift b/macos_backup2/Runner/AppDelegate.swift new file mode 100644 index 0000000..b3c1761 --- /dev/null +++ b/macos_backup2/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/macos_backup2/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos_backup2/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/macos_backup2/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/macos_backup2/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos_backup2/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/macos_backup2/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/macos_backup2/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos_backup2/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/macos_backup2/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/macos_backup2/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/macos_backup2/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/macos_backup2/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/macos_backup2/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/macos_backup2/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/macos_backup2/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/macos_backup2/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/macos_backup2/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/macos_backup2/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/macos_backup2/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/macos_backup2/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/macos_backup2/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/macos_backup2/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/macos_backup2/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/macos_backup2/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/macos_backup2/Runner/Base.lproj/MainMenu.xib b/macos_backup2/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/macos_backup2/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos_backup2/Runner/Configs/AppInfo.xcconfig b/macos_backup2/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..cebf059 --- /dev/null +++ b/macos_backup2/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = melo_app + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.melo.meloApp + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2026 com.melo. All rights reserved. diff --git a/macos_backup2/Runner/Configs/Debug.xcconfig b/macos_backup2/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/macos_backup2/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos_backup2/Runner/Configs/Release.xcconfig b/macos_backup2/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/macos_backup2/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos_backup2/Runner/Configs/Warnings.xcconfig b/macos_backup2/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/macos_backup2/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/macos_backup2/Runner/DebugProfile.entitlements b/macos_backup2/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/macos_backup2/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/macos_backup2/Runner/Info.plist b/macos_backup2/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/macos_backup2/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/macos_backup2/Runner/MainFlutterWindow.swift b/macos_backup2/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/macos_backup2/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/macos_backup2/Runner/Release.entitlements b/macos_backup2/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/macos_backup2/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/macos_backup2/RunnerTests/RunnerTests.swift b/macos_backup2/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..61f3bd1 --- /dev/null +++ b/macos_backup2/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/macos_old_backup/.gitignore b/macos_old_backup/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/macos_old_backup/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/macos_old_backup/Flutter/Flutter-Debug.xcconfig b/macos_old_backup/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/macos_old_backup/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos_old_backup/Flutter/Flutter-Release.xcconfig b/macos_old_backup/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/macos_old_backup/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos_old_backup/Flutter/GeneratedPluginRegistrant.swift b/macos_old_backup/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..f774dc0 --- /dev/null +++ b/macos_old_backup/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,20 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import audio_service +import audio_session +import just_audio +import shared_preferences_foundation +import sqflite_darwin + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin")) + AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) + JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) +} diff --git a/macos_old_backup/Podfile b/macos_old_backup/Podfile new file mode 100644 index 0000000..ff5ddb3 --- /dev/null +++ b/macos_old_backup/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.15' + +# 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', 'ephemeral', '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 Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_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_macos_build_settings(target) + end +end diff --git a/macos_old_backup/Podfile.lock b/macos_old_backup/Podfile.lock new file mode 100644 index 0000000..0a5fc2c --- /dev/null +++ b/macos_old_backup/Podfile.lock @@ -0,0 +1,16 @@ +PODS: + - FlutterMacOS (1.0.0) + +DEPENDENCIES: + - FlutterMacOS (from `Flutter/ephemeral`) + +EXTERNAL SOURCES: + FlutterMacOS: + :path: Flutter/ephemeral + +SPEC CHECKSUMS: + FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 + +PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009 + +COCOAPODS: 1.16.2 diff --git a/macos_old_backup/Runner.xcodeproj/project.pbxproj b/macos_old_backup/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..f04fe7e --- /dev/null +++ b/macos_old_backup/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,807 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 1EF72F07E3D3681C335406C6 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D3D7FA51371D4CBFC6AC9FA5 /* Pods_Runner.framework */; }; + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; + AACBA3FB82C466C0D0D0B64E /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C91015B35802EC0E8670BAF /* Pods_RunnerTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* melo_app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = melo_app.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 3C91015B35802EC0E8670BAF /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 55F71C7DFDB20C59BF73EAD7 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 5762EDC4B2221CFE992C3EE3 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 6B469B40C5E42E480AF52E74 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 93F65671892C523CB8CBD7CF /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + C57627DB9855C4DEF21E18BA /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + D24D7755018E213035056554 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + D3D7FA51371D4CBFC6AC9FA5 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + AACBA3FB82C466C0D0D0B64E /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + 1EF72F07E3D3681C335406C6 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + D18E7E1F246ADA1C0D73B904 /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* melo_app.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D18E7E1F246ADA1C0D73B904 /* Pods */ = { + isa = PBXGroup; + children = ( + 55F71C7DFDB20C59BF73EAD7 /* Pods-Runner.debug.xcconfig */, + 5762EDC4B2221CFE992C3EE3 /* Pods-Runner.release.xcconfig */, + 6B469B40C5E42E480AF52E74 /* Pods-Runner.profile.xcconfig */, + 93F65671892C523CB8CBD7CF /* Pods-RunnerTests.debug.xcconfig */, + C57627DB9855C4DEF21E18BA /* Pods-RunnerTests.release.xcconfig */, + D24D7755018E213035056554 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + D3D7FA51371D4CBFC6AC9FA5 /* Pods_Runner.framework */, + 3C91015B35802EC0E8670BAF /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 42029007D2624CC971333D31 /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 3995B6E9A2D4E69AFEF97BBA /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* melo_app.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + 3995B6E9A2D4E69AFEF97BBA /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 42029007D2624CC971333D31 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 93F65671892C523CB8CBD7CF /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.melo.meloApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/melo_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/melo_app"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C57627DB9855C4DEF21E18BA /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.melo.meloApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/melo_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/melo_app"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D24D7755018E213035056554 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.melo.meloApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/melo_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/melo_app"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/macos_old_backup/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos_old_backup/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos_old_backup/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos_old_backup/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos_old_backup/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..dc3f799 --- /dev/null +++ b/macos_old_backup/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos_old_backup/Runner.xcworkspace/contents.xcworkspacedata b/macos_old_backup/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/macos_old_backup/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/macos_old_backup/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos_old_backup/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos_old_backup/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos_old_backup/Runner/AppDelegate.swift b/macos_old_backup/Runner/AppDelegate.swift new file mode 100644 index 0000000..b3c1761 --- /dev/null +++ b/macos_old_backup/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/macos_old_backup/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos_old_backup/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/macos_old_backup/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/macos_old_backup/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos_old_backup/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/macos_old_backup/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/macos_old_backup/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos_old_backup/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/macos_old_backup/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/macos_old_backup/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/macos_old_backup/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/macos_old_backup/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/macos_old_backup/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/macos_old_backup/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/macos_old_backup/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/macos_old_backup/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/macos_old_backup/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/macos_old_backup/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/macos_old_backup/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/macos_old_backup/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/macos_old_backup/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/macos_old_backup/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/macos_old_backup/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/macos_old_backup/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/macos_old_backup/Runner/Base.lproj/MainMenu.xib b/macos_old_backup/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/macos_old_backup/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos_old_backup/Runner/Configs/AppInfo.xcconfig b/macos_old_backup/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..cebf059 --- /dev/null +++ b/macos_old_backup/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = melo_app + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.melo.meloApp + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2026 com.melo. All rights reserved. diff --git a/macos_old_backup/Runner/Configs/Debug.xcconfig b/macos_old_backup/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/macos_old_backup/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos_old_backup/Runner/Configs/Release.xcconfig b/macos_old_backup/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/macos_old_backup/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos_old_backup/Runner/Configs/Warnings.xcconfig b/macos_old_backup/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/macos_old_backup/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/macos_old_backup/Runner/DebugProfile.entitlements b/macos_old_backup/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/macos_old_backup/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/macos_old_backup/Runner/Info.plist b/macos_old_backup/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/macos_old_backup/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/macos_old_backup/Runner/MainFlutterWindow.swift b/macos_old_backup/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/macos_old_backup/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/macos_old_backup/Runner/Release.entitlements b/macos_old_backup/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/macos_old_backup/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/macos_old_backup/RunnerTests/RunnerTests.swift b/macos_old_backup/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..61f3bd1 --- /dev/null +++ b/macos_old_backup/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/navidrome.db b/navidrome.db new file mode 100644 index 0000000..2dc2caf Binary files /dev/null and b/navidrome.db differ diff --git a/test/song_test.dart b/test/song_test.dart new file mode 100644 index 0000000..686bf7a --- /dev/null +++ b/test/song_test.dart @@ -0,0 +1,43 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:melo_app/models/song.dart'; + +void main() { + group('Song Model', () { + test('toMap / fromMap roundtrip', () { + final song = Song( + id: 1, + titel: 'Test Song', + kuenstler: 'Test Artist', + album: 'Test Album', + dauerSekunden: 180, + dateiPfad: '/music/test.mp3', + groesseBytes: 5242880, + istHeruntergeladen: true, + downloadQuelle: 'local', + ); + + final map = song.toMap(); + final restored = Song.fromMap(map); + + expect(restored.titel, song.titel); + expect(restored.kuenstler, song.kuenstler); + expect(restored.album, song.album); + expect(restored.dauerSekunden, song.dauerSekunden); + expect(restored.dateiPfad, song.dateiPfad); + expect(restored.groesseBytes, song.groesseBytes); + expect(restored.istHeruntergeladen, song.istHeruntergeladen); + }); + + test('dauerFormatiert formats correctly', () { + final song = Song( + titel: 'Test', kuenstler: 'T', dauerSekunden: 125, dateiPfad: '', + ); + expect(song.dauerFormatiert, '2:05'); + + final short = Song( + titel: 'Test', kuenstler: 'T', dauerSekunden: 7, dateiPfad: '', + ); + expect(short.dauerFormatiert, '0:07'); + }); + }); +}