diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index be52c18..794e21e 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -32,5 +32,16 @@ + + + + + + + + + + diff --git a/assets/images/DALL·E 2024-11-17 16.18.59 - A humorous cartoon-style image featuring a penguin and an alpaca together. The penguin is holding a sign that says 'Noch kein Bild geladen' with a pla.png b/assets/images/DALL·E 2024-11-17 16.18.59 - A humorous cartoon-style image featuring a penguin and an alpaca together. The penguin is holding a sign that says 'Noch kein Bild geladen' with a pla.png new file mode 100644 index 0000000..3ac5c5e Binary files /dev/null and b/assets/images/DALL·E 2024-11-17 16.18.59 - A humorous cartoon-style image featuring a penguin and an alpaca together. The penguin is holding a sign that says 'Noch kein Bild geladen' with a pla.png differ diff --git a/assets/images/Event_Images/photo-1429962714451-bb934ecdc4ec.avif b/assets/images/Event_Images/photo-1429962714451-bb934ecdc4ec.avif new file mode 100644 index 0000000..cd85386 Binary files /dev/null and b/assets/images/Event_Images/photo-1429962714451-bb934ecdc4ec.avif differ diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index dd6877c..ff28507 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -14,6 +14,10 @@ 6.0 CFBundleName Montomo + NSPhotoLibraryUsageDescription +Photo Library Access Warning +NSCameraUsageDescription +This app requires access to the camera. CFBundlePackageType APPL CFBundleShortVersionString diff --git a/lib/main.dart b/lib/main.dart index 873a0c0..d76b862 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,7 +9,6 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; //import 'package:learn_project/screens/login_page.dart'; //import 'package:learn_project/screens/splash_page.dart'; -import 'utils/constants.dart'; import 'package:flutter/services.dart'; Future main() async { @@ -20,19 +19,18 @@ Future main() async { throw Exception('Error loading .env file: $e'); // Print error if any } await Supabase.initialize( - url : dotenv.env['NEXT_PUBLIC_SUPABASE_URL'] ?? 'default_url', - anonKey : dotenv.env['NEXT_PUBLIC_SUPABASE_ANON_KEY'] ?? 'default_key', - + url: dotenv.env['NEXT_PUBLIC_SUPABASE_URL'] ?? 'default_url', + anonKey: dotenv.env['NEXT_PUBLIC_SUPABASE_ANON_KEY'] ?? 'default_key', ); await autoLogin(); - var singleCategories = Categories.instance; + var singleCategories = Categories.instance; singleCategories.updateCategoryAmount('Ingosltadt'); runApp( ChangeNotifierProvider( create: (context) => Categories.instance, child: const MyApp(), ), - ); + ); } /// Supabase client @@ -52,16 +50,17 @@ Future autoLogin() async { print('AppUser erfolgreich eingeloggt'); } } + class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { //set initial orentation to langscape - SystemChrome.setPreferredOrientations([ - DeviceOrientation.portraitDown, - DeviceOrientation.portraitUp, - ]); + SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitDown, + DeviceOrientation.portraitUp, + ]); return MaterialApp( debugShowCheckedModeBanner: false, //title: 'Recipes', diff --git a/lib/screens/detail_widget.dart b/lib/screens/detail_widget.dart index 94e004b..7163d66 100644 --- a/lib/screens/detail_widget.dart +++ b/lib/screens/detail_widget.dart @@ -6,16 +6,22 @@ import 'package:intl/intl.dart'; import 'package:Emma_home/utils/data.dart'; //import 'package:supabase_flutter/supabase_flutter.dart'; -class Frame28 extends StatelessWidget { +class Event_Card_small extends StatelessWidget { final List> events; + final List> event_grouped; final Color color; final MediaQueryData screen_size; final Category category; - const Frame28({super.key, required this.events, required this.color, required this.screen_size, required this.category}); - - void toggleExpanded() { - } + const Event_Card_small( + {super.key, + required this.events, + required this.event_grouped, + required this.color, + required this.screen_size, + required this.category}); + + void toggleExpanded() {} /* void initState() { super.initState(); _loadData(); @@ -99,100 +105,96 @@ class Frame28 extends StatelessWidget { child: Container( alignment: Alignment.center, child: Column(children: [ - //Header Container - //HeaderSection(), Container(height: 10), Container( height: 35, padding: const EdgeInsets.all(0), - child: TimeButtonSection(onToggle: toggleExpanded), // Dein Button-Bereich + child: TimeButtonSection( + onToggle: toggleExpanded), // Dein Button-Bereich ), Container( - height: 10, + height: 10, ), SizedBox( height: 35, - child: MusikHeader(name:category.title , icon: category.iconUrl), + child: + MusikHeader(name: category.title, icon: category.iconUrl), ), //Container(), - Column( - children: [ - SizedBox( - width: 380, - //height: 259, - - child: const Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - - children: [ - // TODO: muss noch angepasst werden jedes mal wenn ein neuer Tag ist wieder diesen Text anzeigen - SizedBox(height: 10), - Padding( - padding: EdgeInsets.only(left: 20.0), // Abstand von 16 Pixeln zum linken Rand - child: Text( - 'Heute', - style: TextStyle( - color: Color(0xFF171717), - fontSize: 12, - fontFamily: 'Inter', - fontWeight: FontWeight.w600, - height: 0, - ), - ), - ), - - - - //Kurze Zwischenbereich zum trennen - SizedBox(height: 4), - //EventCard2Section(), - /* const SizedBox(height: 20), - EventCard2Section(), - const SizedBox(height: 20), - Text('Morgen', style: TextStyle(color: Color(0xFF171717), fontSize: 12, fontFamily: 'Inter', fontWeight: FontWeight.w600, height: 0,),), - //Kurze Zwischenbereich zum trennen - const SizedBox(height: 4), - EventCard2Section(), - const SizedBox(height: 20), */ - ], - ), - ), - ], - ), ])), ), ), - SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) { - return EventCard2Section( - title: events[index]['name'], - description: events[index]['description'], - location: events[index]['location']['name'], - start_time: - extractTime24h(events[index]['time'][0]['start_date']), - - color: events[index]['main_category_id'] != null - ? extractColor(events[index]['main_category_id']) - : color, - //extractColor(events[index]['main_category_id']), - // oder ein beliebiger Standardwert - screen_size: screen_size, - ); - }, - childCount: events.length, - ), + + // Gruppierte Events mit SliverMainAxisGroup + ...event_grouped.map((group) { + final String startDate = group['start_date']; + final List> events = group['events']; + + return SliverMainAxisGroup( + slivers: [ + // Gruppierungs-Header + SliverToBoxAdapter( + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // TODO: muss noch angepasst werden jedes mal wenn ein neuer Tag ist wieder diesen Text anzeigen + const SizedBox(height: 10), + Padding( + padding: const EdgeInsets.only( + left: 20.0), // Abstand von 16 Pixeln zum linken Rand + child: Text( + DateFormat('dd.MM.yyyy').format(DateTime.parse(startDate)) + as String, + style: const TextStyle( + color: Color(0xFF171717), + fontSize: 12, + fontFamily: 'Inter', + fontWeight: FontWeight.w600, + height: 0, + ), + ), + ), + + //Kurze Zwischenbereich zum trennen + const SizedBox(height: 4), + ], + ), + ), + + // Liste der Events + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + final event = events[index]; + return EventCard2Section( + title: event['name'], + description: event['description'], + location: event['location_name'] ?? '', + start_time: extractTime24h(event['start']), + color: event['main_category_id'] != null + ? extractColor(event['main_category_id']) + : color, + //extractColor(events[index]['main_category_id']), + // oder ein beliebiger Standardwert + screen_size: screen_size, + ); + }, + childCount: events.length, + ), + ), + ], + ); + }).toList(), + + SliverToBoxAdapter( + child: Column(children: [ + LostEvents(), + Container(height: 20), + Footer(), + ]), ), - SliverToBoxAdapter( - child: Column( - children:[ - LostEvents(), - Container( - height: 20), - Footer(),]), - ), ]); return Scaffold( backgroundColor: Colors.white, @@ -215,16 +217,16 @@ String extractTime24h(String timestamp) { } //TODO: Color muss anhand der Categorie entschieden werden - // Also herauslesen welche Category das Event hat - // Dann den richtigen Eintrag in Data identifizieren udn die Farbe auswählen -Color extractColor(String categoryID) -{ - var singleCategories = Categories.instance; - final result = singleCategories.categories.where((recipe) => recipe.category == categoryID).firstOrNull; - Color categorieColor= const Color.fromARGB(255, 255, 255, 255); - if (result!=null) - { - categorieColor=result.color; - } +// Also herauslesen welche Category das Event hat +// Dann den richtigen Eintrag in Data identifizieren udn die Farbe auswählen +Color extractColor(String categoryID) { + var singleCategories = Categories.instance; + final result = singleCategories.categories + .where((recipe) => recipe.category == categoryID) + .firstOrNull; + Color categorieColor = const Color.fromARGB(255, 255, 255, 255); + if (result != null) { + categorieColor = result.color; + } return categorieColor; -} \ No newline at end of file +} diff --git a/lib/screens/home.dart b/lib/screens/home.dart index e82eafc..9618dc5 100644 --- a/lib/screens/home.dart +++ b/lib/screens/home.dart @@ -40,9 +40,27 @@ class _HomePage extends State { // Überprüfe, ob der Insert erfolgreich war } +//fetchGroupedEvents + Future>> fetchGroupedEvents(String category) async { + try { + final response = await supabase + .rpc('get_events_grouped_by_date', params: {'cat_id': category}); + // Cast auf die gewünschte Struktur + return (response as List) + .map((entry) => { + 'start_date': entry['start_date'], // Das Gruppierungsdatum + 'events': (entry['events'] as List) + .cast>() // Event-Details + }) + .toList(); + } catch (e) { + throw Exception('Failed to fetch data: $e'); + } + } + Future>> fetchEvents(String category) async { try { - final today = DateTime.now().toUtc().toString().split(' ')[0]; + //final today = DateTime.now().toUtc().toString().split(' ')[0]; final response = await supabase .from("events") //.innerJoin('event_category', 'events.id = event_category.event_id') @@ -247,11 +265,14 @@ class _HomePage extends State { await _loadData(); */ List> events2 = await fetchEvents( singleCategories.categories[index].category); + final events = await fetchGroupedEvents( + singleCategories.categories[index].category); Navigator.push( context, MaterialPageRoute( - builder: (context) => Frame28( + builder: (context) => Event_Card_small( events: events2, + event_grouped: events, color: singleCategories .categories[index].color, screen_size: queryData, diff --git a/lib/screens/missing_event.dart b/lib/screens/missing_event.dart index 363a16b..0d0f3e5 100644 --- a/lib/screens/missing_event.dart +++ b/lib/screens/missing_event.dart @@ -102,6 +102,7 @@ class _MissingEvent extends State { ); } print('Image path: $imagePath'); + dialogSave(context); } else { print('No image selected.'); } @@ -132,6 +133,7 @@ class _MissingEvent extends State { ); } print('Image path: $imagePath'); + dialogSave(context); } else { print('No image captured.'); } @@ -357,7 +359,7 @@ class _MissingEvent extends State { const SizedBox(height: 10), Row( children: [ - Container( + /* Container( width: 160, height: 44, padding: const EdgeInsets.all(10), @@ -372,7 +374,7 @@ class _MissingEvent extends State { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text( + Text( 'Abrechen', style: TextStyle( color: Colors.black, @@ -412,10 +414,10 @@ class _MissingEvent extends State { fontWeight: FontWeight.w500, height: 0.07, ), - ), + ), ], ), - ), + ),*/ ], ), /* const SizedBox(height: 10), @@ -472,3 +474,29 @@ class _MissingEvent extends State { ); } } + +Future dialogSave(BuildContext context) { + return showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Bild wurde gespeichert'), + content: const Text( + 'Vielen Dank!\n' + 'Das Bild wurde erfolgreich hochgeladen!\n', + ), + actions: [ + TextButton( + style: TextButton.styleFrom( + textStyle: Theme.of(context).textTheme.labelLarge, + ), + child: const Text('OK'), + onPressed: () { + Navigator.popUntil(context, (route) => route.isFirst); + }, + ), + ], + ); + }, + ); +} diff --git a/lib/utils/constants.dart b/lib/utils/constants.dart deleted file mode 100644 index 8e26a98..0000000 --- a/lib/utils/constants.dart +++ /dev/null @@ -1,14 +0,0 @@ -/// Environment variables and shared app constants. -abstract class Constants { - static const String supabaseUrl = String.fromEnvironment( - //'http://192.168.179.86:8000/', //local - 'https://vvtefqdqahcpzgolvupv.supabase.co', - //defaultValue: '', - ); - - static const String supabaseAnnonKey = String.fromEnvironment( - //'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InZ2dGVmcWRxYWhjcHpnb2x2dXB2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTYwMjE2NTIsImV4cCI6MjAzMTU5NzY1Mn0.1bp5V61Oguo5zLUhCFJmCabUY1sujeISr_CR2XUKvh4', // local - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InZ2dGVmcWRxYWhjcHpnb2x2dXB2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTYwMjE3ODksImV4cCI6MjAzMTU5Nzc4OX0.6g5EtS9HrgxC6cpCYgOT0HLpA4lEnASQbKs9mfnUM7k', - //defaultValue: '', - ); -} \ No newline at end of file diff --git a/lib/utils/data.dart b/lib/utils/data.dart index f763a3c..a7528e7 100644 --- a/lib/utils/data.dart +++ b/lib/utils/data.dart @@ -6,10 +6,11 @@ import 'package:Emma_home/utils/class.dart'; import 'package:Emma_home/main.dart'; import 'package:intl/intl.dart' as intl; import 'package:supabase_flutter/supabase_flutter.dart'; + const Duration fakeAPIDuration = Duration(milliseconds: 50); const Duration debounceDuration = Duration(milliseconds: 50); -class Categories extends ChangeNotifier{ +class Categories extends ChangeNotifier { // Private constructor Categories._privateConstructor(); @@ -22,87 +23,88 @@ class Categories extends ChangeNotifier{ // Your singleton methods and properties static final List _categories = [ Category( - id: '1', - title: 'Musik', - imageUrl: 'assets/images/Musik.png', - iconUrl:'assets/images/Icons/Icon_Musik.png', - color: const Color.fromARGB(255, 249, 171, 21), //FD4949 //Musik - category: '34725c49-740b-45c5-8a51-7c742f92a621', - textheight: 86, - amount:0, - ), + id: '1', + title: 'Musik', + imageUrl: 'assets/images/Musik.png', + iconUrl: 'assets/images/Icons/Icon_Musik.png', + color: const Color.fromARGB(255, 249, 171, 21), //FD4949 //Musik + category: '34725c49-740b-45c5-8a51-7c742f92a621', + textheight: 86, + amount: 0, + ), Category( - id: '2', - title: 'Nachtleben', - imageUrl: 'assets/images/Nachtleben.png', - iconUrl:'assets/images/Icons/Icon_Nachtleben.png', - color: const Color.fromARGB(255, 253, 73, 73), //FD4949 //Nachtleben - category: '2d8c714e-3290-4d8c-8a95-e3b100ad8599', - textheight: 86, - amount:0, - ), + id: '2', + title: 'Nachtleben', + imageUrl: 'assets/images/Nachtleben.png', + iconUrl: 'assets/images/Icons/Icon_Nachtleben.png', + color: const Color.fromARGB(255, 253, 73, 73), //FD4949 //Nachtleben + category: '2d8c714e-3290-4d8c-8a95-e3b100ad8599', + textheight: 86, + amount: 0, + ), Category( - id: '3', - title: 'Kunst & Kultur', - imageUrl: 'assets/images/Kultur.png', - iconUrl:'assets/images/Icons/Icon_Kultur.png', - color: const Color.fromARGB(255, 80, 168, 250), //50A8FA//Kunst - category: '09d1f862-2b56-47af-8ee6-3c50056e3fa2', - textheight: 58, - amount:0, - ), + id: '3', + title: 'Kunst & Kultur', + imageUrl: 'assets/images/Kultur.png', + iconUrl: 'assets/images/Icons/Icon_Kultur.png', + color: const Color.fromARGB(255, 80, 168, 250), //50A8FA//Kunst + category: '09d1f862-2b56-47af-8ee6-3c50056e3fa2', + textheight: 58, + amount: 0, + ), Category( - id: '4', - title: 'Sport & Verein', - imageUrl: 'assets/images/Sport.png', - iconUrl:'assets/images/Icons/Icon_Sport.png', - color: const Color.fromARGB(255, 80, 240, 250), //50F0FA //Sport - category: '15119876-9ff0-41e5-8fe5-c3675af5e054', - textheight: 58, - amount:0, - ), + id: '4', + title: 'Sport & Verein', + imageUrl: 'assets/images/Sport.png', + iconUrl: 'assets/images/Icons/Icon_Sport.png', + color: const Color.fromARGB(255, 80, 240, 250), //50F0FA //Sport + category: '15119876-9ff0-41e5-8fe5-c3675af5e054', + textheight: 58, + amount: 0, + ), Category( - id: '5', - title: 'Gesundheit', - imageUrl: 'assets/images/Gesundheit.png', - iconUrl:'assets/images/Icons/Icon_Gesundheit.png', - color: const Color.fromARGB(255,130, 73, 253), //8249FD //Gesundheit - category: '4ad3ac2d-04fc-4aa1-afae-17ec7bfc1714', - textheight: 86, - amount:0, - ), + id: '5', + title: 'Gesundheit', + imageUrl: 'assets/images/Gesundheit.png', + iconUrl: 'assets/images/Icons/Icon_Gesundheit.png', + color: const Color.fromARGB(255, 130, 73, 253), //8249FD //Gesundheit + category: '4ad3ac2d-04fc-4aa1-afae-17ec7bfc1714', + textheight: 86, + amount: 0, + ), Category( - id: '6', - title: 'Essen & Trinken', - imageUrl: 'assets/images/Essen.png', - iconUrl:'assets/images/Icons/Icon_Essen.png', - color: const Color.fromARGB(255,253, 73,73), //FD4949//Essen - category: 'ef83c4d3-228a-44e1-8a5d-8b47d2dfc351', - textheight: 58, - amount:0, - ), + id: '6', + title: 'Essen & Trinken', + imageUrl: 'assets/images/Essen.png', + iconUrl: 'assets/images/Icons/Icon_Essen.png', + color: const Color.fromARGB(255, 253, 73, 73), //FD4949//Essen + category: 'ef83c4d3-228a-44e1-8a5d-8b47d2dfc351', + textheight: 58, + amount: 0, + ), Category( - id: '7', - title: 'Soziales', - imageUrl: 'assets/images/Soziales.png', - iconUrl:'assets/images/Icons/Icon_Soziales.png', - color: const Color.fromARGB(255,255,0,199), //FF00C7//Essen - category: '53a1782b-e07b-491d-b778-d99ef9cf57e2', - textheight: 86, - amount:0, - ), + id: '7', + title: 'Soziales', + imageUrl: 'assets/images/Soziales.png', + iconUrl: 'assets/images/Icons/Icon_Soziales.png', + color: const Color.fromARGB(255, 255, 0, 199), //FF00C7//Essen + category: '53a1782b-e07b-491d-b778-d99ef9cf57e2', + textheight: 86, + amount: 0, + ), Category( - id: '8', - title: 'Familie', - imageUrl: 'assets/images/Familie.png', - iconUrl:'assets/images/Icons/Icon_Familie.png', - color: const Color.fromARGB(255,66,255,0), //42FF00//Essen //HSL 104 100 50 100 - category: '30cb3481-c425-4068-9714-adc86df85d3f', - textheight: 86, - amount:0, - ), + id: '8', + title: 'Familie', + imageUrl: 'assets/images/Familie.png', + iconUrl: 'assets/images/Icons/Icon_Familie.png', + color: const Color.fromARGB( + 255, 66, 255, 0), //42FF00//Essen //HSL 104 100 50 100 + category: '30cb3481-c425-4068-9714-adc86df85d3f', + textheight: 86, + amount: 0, + ), ]; - + void addCategory(Category category) { category.add(category); notifyListeners(); @@ -119,27 +121,31 @@ class Categories extends ChangeNotifier{ if (index >= 0 && index < _categories.length) { _categories.removeAt(index); notifyListeners(); - }} - - Future updateCategoryAmount (String cityName) async{ - try { - for (var index = 0; index < _categories.length; index++) { - final response = await supabase - .from("events") - .select( - 'id,name,main_category_id,location(name),event_category!inner(event_id)') - .eq('event_category.category_id', _categories[index].category); - var temp =(response.length); - _categories[index].amount= temp; - notifyListeners(); - - }return; } catch (e) { + } + } + + Future updateCategoryAmount(String cityName) async { + try { + for (var index = 0; index < _categories.length; index++) { + //TODO: man müsste sicherstellen, dass die Main Category bei allen Events existent ist. Und dass in den jeweilige Subcategorien nicht die MainCatgeorie vorkommt. Erst dann kann das möglich sein. + //müsste man auch in der Admin Page ermöglichen. + final response = await supabase + .from("events") + .select( + 'id,name,main_category_id,location(name),event_category!inner(event_id)') + .eq('event_category.category_id', _categories[index].category); + var temp = (response.length); + _categories[index].amount = temp; + notifyListeners(); + } + return; + } catch (e) { throw Exception('Failed to fetch data: $e'); } - return; -} + return; + } - // Method to get all items + // Method to get all items List get categories => List.unmodifiable(_categories); // Method to clear all items @@ -151,18 +157,12 @@ class Categories extends ChangeNotifier{ int get itemCount => _categories.length; } - - - class SupabaseDataService { - - - Future>> fetchEvents(String category) async { try { final today = DateTime.now().toUtc().toString().split(' ')[0]; final response = await supabase - .from("events") + .from("events") .select( 'id,name,description,detail,subheader,main_category_id,location(name),time!inner(*),event_category!inner(event_id)') .eq('event_category.category_id', category); @@ -177,14 +177,11 @@ class SupabaseDataService { } } - Future> fetchSingleRecord(String tableName, String id) async { + Future> fetchSingleRecord( + String tableName, String id) async { try { - final response = await supabase - .from(tableName) - .select() - .eq('id', id) - .single(); - + final response = + await supabase.from(tableName).select().eq('id', id).single(); return response; } catch (e) { @@ -356,4 +353,4 @@ class _NetworkException implements Exception { }, ), ); - } */ \ No newline at end of file + } */