Skip to content

Commit

Permalink
Schedule Editor Mockup (#72)
Browse files Browse the repository at this point in the history
adds a non-functional "create event" form

Co-authored-by: Nate Wilson <nate.w5687@gmail.com>
  • Loading branch information
Felicia-Mayeyane and nate-thegrate authored Aug 23, 2024
1 parent e428a27 commit 6c93c7e
Show file tree
Hide file tree
Showing 7 changed files with 439 additions and 135 deletions.
4 changes: 2 additions & 2 deletions lib/agora/active_stream.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class _ActiveStreamState extends State<ActiveStream> {
/// If the user was creating the livestream, we go back to the [HomeScreen].
///
/// If the user was watching, we show some post-stream survey questions!
void endStream([_]) async {
void endStream([_, __]) async {
context.read<OverlayBloc>().value = false;
if (streaming) return navigator.pop();

Expand All @@ -63,7 +63,7 @@ class _ActiveStreamState extends State<ActiveStream> {

return PopScope(
canPop: NavBarSelection.streaming(context),
onPopInvoked: endStream,
onPopInvokedWithResult: endStream,
child: Scaffold(
body: const OverlayController(
child: Stack(
Expand Down
2 changes: 1 addition & 1 deletion lib/home/profile/account/account_settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class _AccountSettingsState extends State<AccountSettings> {
);

return PopScope(
onPopInvoked: (_) => context.read<AccountFields>().value = user,
onPopInvokedWithResult: (_, __) => context.read<AccountFields>().value = user,
child: Scaffold(
appBar: AppBar(title: const Text('Account')),
body: ProfileListView(
Expand Down
303 changes: 299 additions & 4 deletions lib/home/schedule/src/schedule_editor.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,309 @@
import 'package:intl/intl.dart';
import 'package:table_calendar/table_calendar.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:thc/home/users/src/all_users.dart';
import 'package:thc/the_good_stuff.dart';
import 'package:thc/utils/widgets/placeholders.dart';

class ScheduleEditor extends StatelessWidget {
class ScheduleEditor extends StatefulWidget {
const ScheduleEditor({super.key});

@override
State<ScheduleEditor> createState() => _ScheduleEditorState();
}

class _ScheduleEditorState extends State<ScheduleEditor> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final TextEditingController _event = TextEditingController();
final TextEditingController _startDate = TextEditingController();
final TextEditingController _endDate = TextEditingController();
final TextEditingController _startTime = TextEditingController();
final TextEditingController _endTime = TextEditingController();

Director? director;

DateTime _selectedDay = DateTime.now();
Map<DateTime, List<dynamic>> _events = {};
CalendarFormat _calendarFormat = CalendarFormat.month;
DateTime _focusedDay = DateTime.now();

final eventsCollection = FirebaseFirestore.instance.collection('scheduled_streams');

@override
void initState() {
super.initState();
fetchEvents();
}

void submit() {
if (_formKey.currentState!.validate()) {
addEvent();
navigator.snackbarMessage('${_event.text} event added!');
}
}

void addEvent() {
final newEvent = {
'title': _event.text,
'startDate': _startDate.text,
'endDate': _endDate.text,
'startTime': _startTime.text,
'endTime': _endTime.text,
'directorId': director?.firestoreId ?? '',
};

eventsCollection.add(newEvent).then((value) {
// Handle success
backendPrint('Event added successfully');
fetchEvents(); // Refresh events after adding
}).catchError((error) {
// Handle error
backendPrint('Failed to add event: $error');
});
}

Future<void> fetchEvents() async {
try {
final snapshot = await eventsCollection.get();
final events = snapshot.docs.map((doc) => doc.data()).toList();

// Update local _events map
setState(() {
_events = {};
for (var event in events) {
final eventDate = DateFormat('yyyy-MM-dd').parse(event['startDate']);
if (_events[eventDate] == null) {
_events[eventDate] = [];
}
_events[eventDate]!.add(event);
}
});
} catch (e) {
backendPrint('Failed to fetch events: $e');
}
}

void updateEvent(String eventId, Map<String, dynamic> updatedEvent) {
eventsCollection.doc(eventId).update(updatedEvent).then((value) {
backendPrint('Event updated successfully');
fetchEvents();
}).catchError((error) {
backendPrint('Failed to update event: $error');
});
}

void deleteEvent(String eventId) {
eventsCollection.doc(eventId).delete().then((value) {
// Handle success
backendPrint('Event deleted successfully');
fetchEvents(); // Refresh events after deleting
}).catchError((error) {
// Handle error
backendPrint('Failed to delete event: $error');
});
}

@override
Widget build(BuildContext context) {
final formContents = <Widget>[
DirectorDropdown(
onSaved: (value) => setState(() => director = value),
validator: (value) => value == null ? '[error message]' : null,
),
TextFormField(
controller: _event,
decoration: const InputDecoration(labelText: 'Event Title'),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter the event title.';
}
return null;
},
),
TextFormField(
controller: _startDate,
decoration: const InputDecoration(
labelText: 'Start Date',
icon: Icon(Icons.calendar_today_rounded),
),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter the event start date.';
}
return null;
},
onTap: () async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2024),
lastDate: DateTime(2035));

if (picked != null) {
setState(() {
_startDate.text = DateFormat('yyyy-MM-dd').format(picked);
});
}
},
),
TextFormField(
controller: _endDate,
decoration: const InputDecoration(
labelText: 'End Date',
icon: Icon(Icons.calendar_today_rounded),
),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter the event end date.';
}
return null;
},
onTap: () async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2024),
lastDate: DateTime(2035),
);

if (picked != null) {
setState(() {
_endDate.text = DateFormat('yyyy-MM-dd').format(picked);
});
}
},
),
TextFormField(
controller: _startTime,
decoration: const InputDecoration(
labelText: 'Start Time',
icon: Icon(Icons.access_time_rounded),
),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter the event start time.';
}
return null;
},
onTap: () async {
final TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);

if (picked != null) {
setState(() {
_startTime.text = picked.format(context);
});
}
},
),
TextFormField(
controller: _endTime,
decoration: const InputDecoration(
labelText: 'End Time',
icon: Icon(Icons.access_time_sharp),
),
validator: (value) {
if (value!.isEmpty) {
return 'Please enter the event end time.';
}
return null;
},
onTap: () async {
final TimeOfDay? picked =
await showTimePicker(context: context, initialTime: TimeOfDay.now());

if (picked != null) {
setState(() {
_endTime.text = picked.format(context);
});
}
},
),
const SizedBox(height: 20.0),
ElevatedButton(
onPressed: submit,
child: const Text('Submit'),
),
];

return Scaffold(
appBar: AppBar(),
body: const FunPlaceholder('change livestream schedule!', color: ThcColors.dullBlue),
appBar: AppBar(title: const Text('Create Event Form')),
body: Padding(
padding: const EdgeInsets.all(32),
child: Column(
children: <Widget>[
Form(
key: _formKey,
child: Column(children: formContents),
),
const SizedBox(height: 20.0),
TableCalendar(
firstDay: DateTime.utc(2023),
lastDay: DateTime.utc(2030, 12, 31),
focusedDay: _focusedDay,
calendarFormat: _calendarFormat,
onFormatChanged: (format) {
setState(() {
_calendarFormat = format;
});
},
selectedDayPredicate: (day) {
return isSameDay(_selectedDay, day);
},
onDaySelected: (selectedDay, focusedDay) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay; // Update focused day for controlled calendar
});
},
eventLoader: (day) {
// Return events for the selected day from _events map
return _events[day] ?? [];
},
),
],
),
),
);
}
}

class DirectorDropdown extends FormField<Director> {
DirectorDropdown({
super.key,
super.autovalidateMode,
super.initialValue,
super.onSaved,
super.validator,
}) : super(
restorationId: 'director dropdown',
builder: (field) {
final options = [
for (final user in ThcUsers.of(field.context))
if (user is Director)
DropdownMenuEntry(
value: user,
label: '${user.name} (${user.firestoreId})',
labelWidget: Text(user.name),
trailingIcon: Text(
user.firestoreId,
style: TextStyle(
size: 12,
color: ThcColors.of(field.context).onSurface.withOpacity(0.5),
),
),
),
];

return DropdownMenu(
label: const Text('Director name'),
dropdownMenuEntries: options,
expandedInsets: EdgeInsets.zero,
enableFilter: true,
onSelected: onSaved,
errorText: field.errorText,
);
},
);
}
7 changes: 3 additions & 4 deletions lib/start/src/bottom_stuff.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'dart:math' as math;

import 'package:flutter/material.dart' as material;
import 'package:thc/start/src/login_progress.dart';
import 'package:thc/the_good_stuff.dart';
import 'package:thc/utils/widgets/clip_height.dart';
Expand Down Expand Up @@ -38,7 +37,7 @@ class BottomStuff extends HookWidget {
};

final bool forwardOrComplete = controller.isForwardOrCompleted;
if (shouldShow != forwardOrComplete) controller.toggle(shouldReverse: !shouldShow);
if (shouldShow != forwardOrComplete) controller.toggle();
if (shouldShow && labels != loginLabels.value) loginLabels.value = labels;

return LayoutBuilder(
Expand All @@ -57,8 +56,8 @@ class BottomStuff extends HookWidget {

final tColumns = (t - 1) * (forwardOrComplete ? 2 : 1) + 1;
final tSeparator = forwardOrComplete
? curve.transform(math.min(t * 2, 1))
: 1 - curve.transform(1 - t);
? curve.transform(math.min(t * 2, 1))
: 1 - curve.transform(1 - t);

const timeOffsetRatio = 7 / 8;
late final tTitle = math.min(tColumns / timeOffsetRatio, 1.0);
Expand Down
15 changes: 0 additions & 15 deletions lib/utils/bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,6 @@ class Loading extends Cubit<bool> {
static bool of(BuildContext context) => context.watch<Loading>().value;
}

extension ToggleController on Animation {
bool get isForwardOrCompleted => switch (status) {
AnimationStatus.forward || AnimationStatus.completed => true,
AnimationStatus.reverse || AnimationStatus.dismissed => false,
};

TickerFuture toggle({bool? shouldReverse, double? from}) {
final animation = this;
if (animation is! AnimationController) throw UnimplementedError();
return (shouldReverse ?? isForwardOrCompleted)
? animation.reverse(from: from)
: animation.forward(from: from);
}
}

extension Powers<T extends num> on T {
T get squared => this * this as T;
T get cubed => this * this * this as T;
Expand Down
Loading

0 comments on commit 6c93c7e

Please sign in to comment.