Separation of the events integrated. And a grouped Event from the server.

This commit is contained in:
Niklas 2024-11-24 20:17:48 +01:00
parent f4728b0e78
commit 3b71c9d7ea
10 changed files with 281 additions and 233 deletions

View File

@ -32,5 +32,16 @@
</application>
<!-- Required to fetch data from the internet. -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- Devices running Android 12L (API level 32) or lower -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<!-- Devices running Android 13 (API level 33) or higher -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<!-- To handle the reselection within the app on devices running Android 14
or higher if your app targets Android 14 (API level 34) or higher. -->
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
<!-- ... -->
</manifest>

View File

@ -14,6 +14,10 @@
<string>6.0</string>
<key>CFBundleName</key>
<string>Montomo</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Photo Library Access Warning</string>
<key>NSCameraUsageDescription</key>
<string>This app requires access to the camera.</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>

View File

@ -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<void> main() async {
@ -20,19 +19,18 @@ Future<void> 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<void> 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',

View File

@ -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<Map<String, dynamic>> events;
final List<Map<String, dynamic>> 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: <Widget>[
//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<Map<String, dynamic>> 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;
}
}

View File

@ -40,9 +40,27 @@ class _HomePage extends State<HomePage> {
// Überprüfe, ob der Insert erfolgreich war
}
//fetchGroupedEvents
Future<List<Map<String, dynamic>>> 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<Map<String, dynamic>>() // Event-Details
})
.toList();
} catch (e) {
throw Exception('Failed to fetch data: $e');
}
}
Future<List<Map<String, dynamic>>> 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<HomePage> {
await _loadData(); */
List<Map<String, dynamic>> 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,

View File

@ -102,6 +102,7 @@ class _MissingEvent extends State<MissingEvent> {
);
}
print('Image path: $imagePath');
dialogSave(context);
} else {
print('No image selected.');
}
@ -132,6 +133,7 @@ class _MissingEvent extends State<MissingEvent> {
);
}
print('Image path: $imagePath');
dialogSave(context);
} else {
print('No image captured.');
}
@ -357,7 +359,7 @@ class _MissingEvent extends State<MissingEvent> {
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<MissingEvent> {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
Text(
'Abrechen',
style: TextStyle(
color: Colors.black,
@ -412,10 +414,10 @@ class _MissingEvent extends State<MissingEvent> {
fontWeight: FontWeight.w500,
height: 0.07,
),
),
),
],
),
),
),*/
],
),
/* const SizedBox(height: 10),
@ -472,3 +474,29 @@ class _MissingEvent extends State<MissingEvent> {
);
}
}
Future<void> dialogSave(BuildContext context) {
return showDialog<void>(
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: <Widget>[
TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
),
child: const Text('OK'),
onPressed: () {
Navigator.popUntil(context, (route) => route.isFirst);
},
),
],
);
},
);
}

View File

@ -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: '',
);
}

View File

@ -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<Category> _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<void> 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<void> 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<Category> 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<List<Map<String, dynamic>>> 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<Map<String, dynamic>> fetchSingleRecord(String tableName, String id) async {
Future<Map<String, dynamic>> 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 {
},
),
);
} */
} */