diff --git a/lib/models/activity_timer_model.dart b/lib/models/activity_timer_model.dart index 7fa82fd..46fbdee 100644 --- a/lib/models/activity_timer_model.dart +++ b/lib/models/activity_timer_model.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:sendtrain/models/activity_model.dart'; class ActivityTimerModel with ChangeNotifier { @@ -11,6 +12,7 @@ class ActivityTimerModel with ChangeNotifier { int _currentSetNum = 0; Timer? _periodicTimer; double _progress = 0; + ItemScrollController? _isc; int get actionCount => _actionCounter; int get currentActionNum => _currentActionNum; @@ -24,11 +26,31 @@ class ActivityTimerModel with ChangeNotifier { double get progress => _progress; void setup(ActivityModel activity) { - _activity = activity; - _sets = activity.actions[0].items(); + if (_activity == null || activity.id != _activity?.id) { + _periodicTimer?.cancel(); + _progress = 0; + _isc = null; + _activity = activity; + _sets = activity.actions[0].items(); + _currentActionNum = 0; + _currentSetNum = 0; + setActionCount(); + } + + moveToIndex(_currentSetNum); + } + + void reset() { + _progress = 0; _currentActionNum = 0; _currentSetNum = 0; + _periodicTimer!.cancel(); setActionCount(); + moveToIndex(0); + } + + void setScrollController(ItemScrollController isc) { + _isc = isc; } bool isCurrentItem(int setNum, int actionNum) { @@ -79,13 +101,16 @@ class ActivityTimerModel with ChangeNotifier { } void updateProgress() { - _progress = (currentAction['actionID'] + (1.0 - _actionCounter / currentAction['amount'])) / totalActions(); + _progress = (currentAction['actionID'] + + (1.0 - _actionCounter / currentAction['amount'])) / + totalActions(); notifyListeners(); } void setAction(int setNum, int actionNum, String type) { _currentActionNum = actionNum; _currentSetNum = setNum; + moveToIndex(_currentSetNum); notifyListeners(); } @@ -99,8 +124,16 @@ class ActivityTimerModel with ChangeNotifier { } else { // if we're done all the sets // cancel timer and reset activity - _periodicTimer!.cancel(); - setup(_activity!); + reset(); + } + } + + void moveToIndex(int index) { + if (_isc != null && _isc!.isAttached) { + _isc?.scrollTo( + index: index, + duration: Duration(milliseconds: 500), + curve: Curves.easeInOutCubic); } } diff --git a/lib/widgets/activity_action_view.dart b/lib/widgets/activity_action_view.dart index 3fef92b..2d88665 100644 --- a/lib/widgets/activity_action_view.dart +++ b/lib/widgets/activity_action_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:sendtrain/classes/activity_action.dart'; import 'package:sendtrain/models/activity_timer_model.dart'; @@ -12,22 +13,36 @@ class ActivityActionView extends StatefulWidget { } class ActivityActionViewState extends State { + final ItemScrollController itemScrollController = ItemScrollController(); + final ScrollOffsetController scrollOffsetController = + ScrollOffsetController(); + final ItemPositionsListener itemPositionsListener = + ItemPositionsListener.create(); + final ScrollOffsetListener scrollOffsetListener = + ScrollOffsetListener.create(); + @override Widget build(BuildContext context) { ActivityTimerModel atm = Provider.of(context, listen: true); List>> sets = atm.activity!.actions[0].items(); + // we need to set the scroll controller + // so we can update the selected item position + atm.setScrollController(itemScrollController); + return Expanded( - child: ListView.builder( + child: ScrollablePositionedList.builder( padding: const EdgeInsets.fromLTRB(10, 0, 10, 20), itemCount: widget.action.activityActionSet.total, + itemScrollController: itemScrollController, + scrollOffsetController: scrollOffsetController, + itemPositionsListener: itemPositionsListener, + scrollOffsetListener: scrollOffsetListener, itemBuilder: (BuildContext context, int setNum) { List content = []; - List> set = sets[setNum]; - // log('${sets.length}'); - // log('${set.length}'); + for (int actionNum = 0; actionNum < set.length; actionNum++) { Map setItem = set[actionNum]; @@ -35,6 +50,11 @@ class ActivityActionViewState extends State { onTap: () { atm.setAction(setNum, actionNum, 'manual'); atm.setActionCount(); + + itemScrollController.scrollTo( + index: setNum, + duration: Duration(milliseconds: 500), + curve: Curves.easeInOutCubic); }, child: Row(children: [ Ink( @@ -43,8 +63,9 @@ class ActivityActionViewState extends State { color: atm.isCurrentItem(setNum, actionNum) ? Theme.of(context).colorScheme.primaryContainer : Theme.of(context).colorScheme.onPrimary, - child: - Text(textAlign: TextAlign.center, '${setNum + 1}.${actionNum + 1} ')), + child: Text( + textAlign: TextAlign.center, + '${setNum + 1}.${actionNum + 1} ')), Expanded( child: Ink( padding: const EdgeInsets.all(15), diff --git a/lib/widgets/activity_card.dart b/lib/widgets/activity_card.dart index 52783fc..af38eba 100644 --- a/lib/widgets/activity_card.dart +++ b/lib/widgets/activity_card.dart @@ -64,9 +64,9 @@ class ActivityCard extends StatelessWidget { } ImageProvider findMediaByType(List? media, String type) { - var found = media!.where((m) => m.type == 'image'); + var found = media?.where((m) => m.type == 'image'); - if (found.isNotEmpty) { + if (found != null) { return NetworkImage(found.first.reference); } else { // Element is not found diff --git a/lib/widgets/activity_view.dart b/lib/widgets/activity_view.dart index 40680ca..72820b1 100644 --- a/lib/widgets/activity_view.dart +++ b/lib/widgets/activity_view.dart @@ -26,8 +26,6 @@ class _ActivityViewState extends State { return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ AppBar( - // surfaceTintColor: ThemeData.dark(useMaterial3: true).colorScheme.primary, - // backgroundColor: ThemeData.dark(useMaterial3: true).colorScheme.primaryContainer, centerTitle: true, title: const Text('Activity', style: TextStyle(fontSize: 15)), ), @@ -71,7 +69,6 @@ class _ActivityViewState extends State { builder: (context, atm, child) { return IconButton( alignment: AlignmentDirectional.center, - iconSize: 30, icon: atm.isActive ? const Icon(Icons.pause_rounded) : const Icon(Icons.play_arrow_rounded), @@ -99,7 +96,7 @@ class _ActivityViewState extends State { child: Consumer( builder: (context, atm, child) { return Text( - style: const TextStyle(fontSize: 15), + style: const TextStyle(fontSize: 12), textAlign: TextAlign.right, '${atm.currentAction['actionID'] + 1} of ${atm.totalActions()}'); })), diff --git a/lib/widgets/session_card.dart b/lib/widgets/session_card.dart index 162b20d..fd030dd 100644 --- a/lib/widgets/session_card.dart +++ b/lib/widgets/session_card.dart @@ -72,7 +72,7 @@ class SessionCard extends StatelessWidget { type: 'fundamental', categories: ['technique', 'conditioning'], description: - "Session focussed on attempting a climb at or beyond your perceived limit.", + "Session focused on attempting a climb at or beyond your perceived limit.", actions: [ ActivityAction( id: 1, @@ -109,7 +109,7 @@ class SessionCard extends StatelessWidget { type: 'Hypertrophy', categories: ['Strength', 'Power'], description: - "Block pull on a edge of a specific size and time to induce a hypotrophic effect on the formarms.", + "Block pull on a edge of a specific size and time to induce a hypertrophic effect on the formarms.", actions: [ ActivityAction( id: 1, diff --git a/pubspec.yaml b/pubspec.yaml index fe7c8e9..f1206c7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -40,6 +40,7 @@ dependencies: youtube_player_flutter: ^9.1.1 json_annotation: ^4.9.0 provider: ^6.1.2 + scrollable_positioned_list: ^0.3.8 flutter_launcher_name: name: "SendTrain"