Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Controller 'animateTo' doesn't work after event update #67

Closed
abel-123 opened this issue Apr 28, 2021 · 3 comments
Closed

Controller 'animateTo' doesn't work after event update #67

abel-123 opened this issue Apr 28, 2021 · 3 comments
Labels
T: Fix Type: :bug: Bug Fixes

Comments

@abel-123
Copy link

In my web app the timetable controller looses the ability to 'animateTo' whenever there is a change in the eventlist. The timetable keeps working correctly otherwise (I can swipe through the weeks manually, updated events show correctly, onTap keeps working, etc.). I can even use the timetableController to get information about the currentstate (e.g. currently displayed week). The only thing that seems to stop working is 'AnimateTo'. If I reload the widget through "popAndPushNamed", it works again, until there is a change in a event. Not a 100% sure this is a bug or an issue with my code, but since everything else seems to work correctly I'm posting it here..

Screenshot:
image

Environment:

  • OS version:
  • Package version:

Code that displays the calendar:

import 'package:flutter/material.dart';
import 'package:flutter_timetable_backend_app/models/event.dart';
import 'package:flutter_timetable_backend_app/screens/home/calendar/src/edit_session.dart';
import 'package:flutter_timetable_backend_app/screens/home/calendar/src/week_list.dart';
import 'package:flutter_timetable_backend_app/screens/home/recurring_sessions/src/location_selection.dart';
import 'package:flutter_timetable_backend_app/services/providers.dart';
import 'package:flutter_timetable_backend_app/shared/loading.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:timetable/timetable.dart';
import 'package:time_machine/time_machine.dart';

class Calendar extends StatefulWidget {
  @override
  _CalendarState createState() => _CalendarState();
}

class _CalendarState extends State<Calendar> {
  static TimetableController<BasicEvent> calController;
  List<BasicEvent> myList = [];
  EventProvider<BasicEvent> myEventProvider;

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

  @override
  void dispose() {
    calController.dispose();
    super.dispose();
  }


  @override
  Widget build(BuildContext context) {
    // create list of sessions that can be processed by Timetable EventProvider

    return Consumer(builder: (context, watch, child) {
      final boxData = watch(boxDataProvider).data == null ? null : watch(boxDataProvider).data.value;
      final userData = watch(userDataProvider).data == null ? null : watch(userDataProvider).data.value;
      final sessionEvents = watch(sessionEventsProvider).data == null ? null : watch(sessionEventsProvider).data.value;
      if (boxData == null || sessionEvents == null || userData == null) return Container(alignment: Alignment.topCenter, child: Loading());

      final DateTime monday = DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().add(Duration(days: 1-DateTime.now().weekday)).day, 0, 0);
      SessionEvent sessionEvent;

      myList = [];
      List<int> weekNumCount = [0,0,0,0];

      if (sessionEvents != null) {
        sessionEvents.forEach((session) {
          session.start.compareTo(monday.add(Duration(days: 7))) < 0 ? weekNumCount[0]++ :
            session.start.compareTo(monday.add(Duration(days: 14))) < 0 ? weekNumCount[1]++ :
              session.start.compareTo(monday.add(Duration(days: 21))) < 0 ? weekNumCount[2]++ : weekNumCount[3]++;

          myList.add(
              BasicEvent(
                id: session.id,
                title: '${session.spotsTaken}/${session.spots}',
                color: Color(session.color),
                start: LocalDate.dateTime(session.start).at(LocalTime(session.start.hour, session.start.minute, 0)),//LocalDateTime.dateTime(session.start),
                end: LocalDate.dateTime(session.end).at(LocalTime(session.end.hour, session.end.minute, 0)),//LocalDateTime.dateTime(session.end),
              )
            );
        });
      }
      myEventProvider = EventProvider.list(myList);

      calController = TimetableController(
        eventProvider: myEventProvider,
        // Optional parameters with their default values:
        initialTimeRange: InitialTimeRange.range(
          startTime: LocalTime(6, 0, 0),
          endTime: LocalTime(23, 0, 0),
        ),
        initialDate: LocalDate.today(),
        visibleRange: VisibleRange.week(),
        firstDayOfWeek: DayOfWeek.monday,
      );

      void animateToWeek(int weekNum) {
          calController.animateTo(LocalDate.today().addWeeks(weekNum));
      }

      return Container(
        height: MediaQuery.of(context).size.height-150,
        margin: EdgeInsets.only(left: 40.0, top: 40.0, bottom: 40.0, right: 40.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Calendar',
              style: TextStyle(
                fontWeight: FontWeight.bold,
                fontSize: 40.0,
              ),
            ),
            Expanded(
              child: Row(
                children: [
                  Expanded(
                    flex: 1,
                    child: Padding(
                      padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 40.0),
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.start,
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          SizedBox(height: 40.0,),
                          Text('Select location',
                            style: TextStyle(
                              fontSize: 20.0,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                          SizedBox(height: 15.0,),
                          LocationList(),
                          SizedBox(height: 40.0,),
                          Text('Select week',
                            style: TextStyle(
                              fontSize: 20.0,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                          SizedBox(height: 20.0,),
                          WeekList(weekNumCount: weekNumCount, animateToWeek: animateToWeek),
                          SizedBox(height: 30,),
                          Row(
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: [
                            ],
                          ),
                        ],
                      ),
                    ),
                  ),
                  Container(
                    width: 500,
                    child: Padding(
                      padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 40.0),
                      child: Timetable<BasicEvent>(
                        controller: calController,
                        theme: TimetableThemeData(
                          totalDateIndicatorHeight: 74,
                          primaryColor: boxData.highlight,
                        ),
                        eventBuilder: (event) =>
                            BasicEventWidget(event,
                              onTap: () async {
                                sessionEvent = sessionEvents.firstWhere((item) => item.id == event.id);
                                EditSession.editSessionKey.currentState.showEventDetails(sessionEvent);
                              },
                            ),
                        allDayEventBuilder: (context, event, info) =>
                            BasicAllDayEventWidget(event, info: info),
                      ),
                    ),
                  ),
                  Expanded(
                    flex: 1,
                    child: Padding(
                      padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 40.0),
                      child: Container(
                        alignment: Alignment.topCenter,
                        child: EditSession(boxData: boxData, userData: userData, key: EditSession.editSessionKey),
                      ),
                    ),
                  )
                ],
              ),
            ),
          ],
        ),
      );
    });
  }
}

Child widget with the list of weeks:

import 'package:flutter_timetable_backend_app/models/event.dart';
import 'package:flutter_timetable_backend_app/services/database.dart';
import 'package:flutter/material.dart';
import 'package:flutter_timetable_backend_app/shared/loading.dart';
import 'package:flutter_timetable_backend_app/services/providers.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';

class WeekList extends StatefulWidget {

  final List<int> weekNumCount;
  final Function animateToWeek;

  WeekList({this.weekNumCount, this.animateToWeek});

  @override
  _WeekListState createState() => _WeekListState();
}

class _WeekListState extends State<WeekList> {

  int weekDisplayed;
  List<SessionEvent> sessionsToDelete;

  final int weekNum = weekNumber(DateTime.now());
  final DateTime monday = DateTime.now().add(Duration(days: 1-DateTime.now().weekday));

  @override
  void initState() {
    super.initState();
    weekDisplayed = weekNum;
  }


  @override
  Widget build(BuildContext context) {
    return Consumer(
      builder: (context, watch, child) {
        final userData = watch(userDataProvider).data == null ? null : watch(userDataProvider).data.value;
        final recurringSessions = watch(recurringSessionsProvider).data == null ? null : watch(recurringSessionsProvider).data.value;
        final sessionEvents = watch(sessionEventsProvider).data == null ? null : watch(sessionEventsProvider).data.value;

        if (userData == null || recurringSessions == null || sessionEvents == null) {return Loading();
        } else {
          return ListView.builder(
            itemCount: 4,
            scrollDirection: Axis.vertical,
            shrinkWrap: true,
            itemBuilder: (context, index) {
              return Card(
                child: ListTile(
                  leading: Text(
                      (weekNum+index).toString(),
                    style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
                  ),
                  title: Text('Mon ${monday.add(Duration(days: 7*index)).day} - Sun ${monday.add(Duration(days: (7*index)+6)).day}'),
                  subtitle: Text('Scheduled sessions: ${widget.weekNumCount[index]}'),
                  selected: index + weekNum == weekDisplayed,
                  onTap: () {
                    setState(() {
                      weekDisplayed = weekNum + index;
                      widget.animateToWeek(index);
                    });
                  },
                  trailing: PopupMenuButton(
                    onSelected: (selectedValue) async {
                      switch (selectedValue) {
                        case 99:
                          await DatabaseService().scheduleFullWeek(userData, recurringSessions, monday.add(Duration(days: 7 * index)));
                          break;
                        case 100:
                          print('total ${sessionEvents.length}');
                          sessionsToDelete = sessionEvents.where((session) => session.start.day >= monday.add(Duration(days: 7 * index)).day).toList();
                          print('after first filter ${sessionsToDelete.length}');
                          sessionsToDelete = sessionsToDelete.where((session) => session.start.day < monday.add(Duration(days: 7 * index + 7)).day).toList();
                          print('after second filter ${sessionsToDelete.length}');
                          await DatabaseService().deleteSessionEvents(userData, sessionsToDelete);
                          break;
                        default:
                          sessionsToDelete = sessionEvents.where((session) => session.start.day == monday.add(Duration(days: 7 * index + selectedValue)).day).toList();
                          await DatabaseService().deleteSessionEvents(userData, sessionsToDelete);
                          print(sessionsToDelete.length);
                          break;
                      }
                    },
                    itemBuilder: (BuildContext ctx) => [
                      PopupMenuItem(child: Text('Schedule full week'), value: 99),
                      PopupMenuItem(child: Text('Clear all sessions'), value: 100),
                      PopupMenuItem(child: Text('Clear Monday'), value: 0),
                      PopupMenuItem(child: Text('Clear Tuesday'), value: 1),
                      PopupMenuItem(child: Text('Clear Wednesday'), value: 2),
                      PopupMenuItem(child: Text('Clear Thursday'), value: 3),
                      PopupMenuItem(child: Text('Clear Friday'), value: 4),
                      PopupMenuItem(child: Text('Clear Saturday'), value: 5),
                      PopupMenuItem(child: Text('Clear Sunday'), value: 6),
                    ]
                  ),
                ),
              );
            });
          }
        }
      );
    }
}

// Calculates number of weeks for a given year as per https://en.wikipedia.org/wiki/ISO_week_date#Weeks_per_year
int numOfWeeks(int year) {
  DateTime dec28 = DateTime(year, 12, 28);
  int dayOfDec28 = int.parse(DateFormat("D").format(dec28));
  return ((dayOfDec28 - dec28.weekday + 10) / 7).floor();
}

// Calculates week number from a date as per https://en.wikipedia.org/wiki/ISO_week_date#Calculation
int weekNumber(DateTime date) {
  int dayOfYear = int.parse(DateFormat("D").format(date));
  int woy =  ((dayOfYear - date.weekday + 10) / 7).floor();
  if (woy < 1) {
    woy = numOfWeeks(date.year - 1);
  } else if (woy > numOfWeeks(date.year)) {
    woy = 1;
  }
  return woy;
}
@abel-123 abel-123 added the T: Fix Type: :bug: Bug Fixes label Apr 28, 2021
@abel-123 abel-123 changed the title Controller looses connection with Controller 'animateTo' doesn't work after event update Apr 28, 2021
@JonasWanke
Copy link
Owner

I'm not sure that this is causing the issue, but the TimetableController is supposed to be instantiated only once (usually in initState) and then get reused across invocations of build. You can then use EventProvider.simpleStream(Stream<List<E>> eventStream) with a StreamController to change the displayed events after initState has been executed. Though I'm a bit confused that you can still read information from the controller, so the cause may be something else…

@abel-123
Copy link
Author

@JonasWanke, thank you Jonas. I'll check if that resolves the issue, will let you know.

@abel-123
Copy link
Author

abel-123 commented May 1, 2021

@JonasWanke, your suggestion resolved the issue. So, not a bug after all. Thanks for the help!

@abel-123 abel-123 closed this as completed May 1, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T: Fix Type: :bug: Bug Fixes
Projects
None yet
Development

No branches or pull requests

2 participants