Merge pull request 'activity counter' (#2) from activity-actions into main
Reviewed-on: #2
This commit is contained in:
commit
4094f7edba
1
.gitignore
vendored
1
.gitignore
vendored
@ -32,6 +32,7 @@ migrate_working_dir/
|
|||||||
.pub/
|
.pub/
|
||||||
/build/
|
/build/
|
||||||
pubspec.lock
|
pubspec.lock
|
||||||
|
devtools_options.yaml
|
||||||
|
|
||||||
# Symbolication related
|
# Symbolication related
|
||||||
app.*.symbols
|
app.*.symbols
|
||||||
|
@ -14,6 +14,76 @@ class ActivityAction {
|
|||||||
required this.activityActionSet,
|
required this.activityActionSet,
|
||||||
this.media,
|
this.media,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
List<List<Map<String, dynamic>>> items() {
|
||||||
|
List<List<Map<String, dynamic>>> sets = [];
|
||||||
|
Reps reps = activityActionSet.reps;
|
||||||
|
int totalActions = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < activityActionSet.total; i++) {
|
||||||
|
List<Map<String, dynamic>> actions = [];
|
||||||
|
int? weight = _setWeight(i);
|
||||||
|
|
||||||
|
actions.add({
|
||||||
|
'actionID': totalActions++,
|
||||||
|
'name': title,
|
||||||
|
'type': reps.type,
|
||||||
|
'amount': reps.amounts[i],
|
||||||
|
'weight': weight,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (activityActionSet.type == 'alternating') {
|
||||||
|
if (reps.rest != null) {
|
||||||
|
actions.add({
|
||||||
|
'actionID': totalActions++,
|
||||||
|
'name': 'Rest',
|
||||||
|
'type': 'seconds',
|
||||||
|
'amount': reps.rest! ~/ 1000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
actions.add({
|
||||||
|
'actionID': totalActions++,
|
||||||
|
'name': title,
|
||||||
|
'type': reps.type,
|
||||||
|
'amount': reps.amounts[i],
|
||||||
|
'weights': weight,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
actions.add({
|
||||||
|
'actionID': totalActions++,
|
||||||
|
'name': 'Rest',
|
||||||
|
'type': 'seconds',
|
||||||
|
'amount': activityActionSet.rest ~/ 1000,
|
||||||
|
});
|
||||||
|
|
||||||
|
sets.add(actions);
|
||||||
|
|
||||||
|
// sets.add([{
|
||||||
|
// 'actionID': totalActions++,
|
||||||
|
// 'name': 'Rest',
|
||||||
|
// 'type': 'seconds',
|
||||||
|
// 'amount': activityActionSet.rest ~/ 1000,
|
||||||
|
// }]);
|
||||||
|
|
||||||
|
// for (int j = 0; i < activityActionSet.reps.amounts; j++) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sets;
|
||||||
|
}
|
||||||
|
|
||||||
|
int? _setWeight(setNum) {
|
||||||
|
Reps reps = activityActionSet.reps;
|
||||||
|
|
||||||
|
if (reps.weights.length == activityActionSet.total) {
|
||||||
|
return reps.weights[setNum];
|
||||||
|
} else if (reps.weights.length == 1) {
|
||||||
|
return reps.weights[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Set {
|
class Set {
|
||||||
@ -34,14 +104,14 @@ class Reps {
|
|||||||
String type;
|
String type;
|
||||||
List<int> tempo;
|
List<int> tempo;
|
||||||
List<int> amounts;
|
List<int> amounts;
|
||||||
List<int> weights;
|
List<int> weights = [];
|
||||||
int rest;
|
int? rest;
|
||||||
|
|
||||||
Reps({
|
Reps({
|
||||||
required this.type,
|
required this.type,
|
||||||
required this.tempo,
|
required this.tempo,
|
||||||
required this.amounts,
|
required this.amounts,
|
||||||
required this.weights,
|
required this.weights,
|
||||||
required this.rest,
|
this.rest,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:sendtrain/models/activity_timer_model.dart';
|
||||||
import 'package:sendtrain/screens/activities_screen.dart';
|
import 'package:sendtrain/screens/activities_screen.dart';
|
||||||
import 'package:sendtrain/screens/sessions_screen.dart';
|
import 'package:sendtrain/screens/sessions_screen.dart';
|
||||||
|
|
||||||
@ -88,5 +90,10 @@ class _AppState extends State<App> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(const SendTrain());
|
runApp(
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (context) => ActivityTimerModel(),
|
||||||
|
child: const SendTrain(),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
143
lib/models/activity_timer_model.dart
Normal file
143
lib/models/activity_timer_model.dart
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
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 {
|
||||||
|
int _actionCounter = 0;
|
||||||
|
ActivityModel? _activity;
|
||||||
|
List _sets = [];
|
||||||
|
int _currentActionNum = 0;
|
||||||
|
int _currentSetNum = 0;
|
||||||
|
Timer? _periodicTimer;
|
||||||
|
double _progress = 0;
|
||||||
|
ItemScrollController? _isc;
|
||||||
|
|
||||||
|
int get actionCount => _actionCounter;
|
||||||
|
int get currentActionNum => _currentActionNum;
|
||||||
|
dynamic get currentAction => currentSet[_currentActionNum];
|
||||||
|
int get currentSetNum => _currentSetNum;
|
||||||
|
dynamic get currentSet => _sets[_currentSetNum];
|
||||||
|
ActivityModel? get activity => _activity;
|
||||||
|
List get sets => _sets;
|
||||||
|
Timer? get periodicTimer => _periodicTimer;
|
||||||
|
bool get isActive => _isActive();
|
||||||
|
double get progress => _progress;
|
||||||
|
|
||||||
|
void setup(ActivityModel activity) {
|
||||||
|
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) {
|
||||||
|
if (setNum == _currentSetNum && actionNum == _currentActionNum) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalActions() {
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 0; i < _sets.length; i++) {
|
||||||
|
count = count + _sets[i].length as int;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setActionCount() {
|
||||||
|
_actionCounter = currentAction['amount'];
|
||||||
|
}
|
||||||
|
|
||||||
|
void pause() {
|
||||||
|
_periodicTimer!.cancel();
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void start() {
|
||||||
|
_periodicTimer = Timer.periodic(const Duration(seconds: 1), (Timer timer) {
|
||||||
|
switch (currentAction['type']) {
|
||||||
|
// we don't want to count down
|
||||||
|
// if its repititions
|
||||||
|
case 'repititions':
|
||||||
|
break;
|
||||||
|
case 'seconds':
|
||||||
|
if (_actionCounter > 0) {
|
||||||
|
_actionCounter--;
|
||||||
|
} else {
|
||||||
|
nextAction(_currentActionNum + 1);
|
||||||
|
setActionCount();
|
||||||
|
}
|
||||||
|
updateProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyListeners();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateProgress() {
|
||||||
|
_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();
|
||||||
|
}
|
||||||
|
|
||||||
|
void nextAction(int nextActionIndex) {
|
||||||
|
if (currentSet.length > nextActionIndex) {
|
||||||
|
setAction(_currentSetNum, nextActionIndex, 'automatic');
|
||||||
|
} else if (_sets.length > _currentSetNum + 1) {
|
||||||
|
// if the item isn't in the set
|
||||||
|
// increment the set and reset action index
|
||||||
|
setAction(_currentSetNum + 1, 0, 'automatic');
|
||||||
|
} else {
|
||||||
|
// if we're done all the sets
|
||||||
|
// cancel timer and reset activity
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void moveToIndex(int index) {
|
||||||
|
if (_isc != null && _isc!.isAttached) {
|
||||||
|
_isc?.scrollTo(
|
||||||
|
index: index,
|
||||||
|
duration: Duration(milliseconds: 500),
|
||||||
|
curve: Curves.easeInOutCubic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isActive() {
|
||||||
|
return (_periodicTimer != null && _periodicTimer!.isActive) ? true : false;
|
||||||
|
}
|
||||||
|
}
|
@ -29,13 +29,13 @@ class _ActivitiesScreenState extends State<ActivitiesScreen> {
|
|||||||
activityActionSet: Set(
|
activityActionSet: Set(
|
||||||
type: 'drop_set',
|
type: 'drop_set',
|
||||||
total: 3,
|
total: 3,
|
||||||
rest: 3000,
|
rest: 300000,
|
||||||
reps: Reps(
|
reps: Reps(
|
||||||
type: 'count',
|
type: 'count',
|
||||||
tempo: [2, 3, 5],
|
tempo: [2, 3, 5],
|
||||||
amounts: [5, 3, 2],
|
amounts: [5, 3, 2],
|
||||||
weights: [50, 70, 80],
|
weights: [50, 70, 80],
|
||||||
rest: 200))),
|
rest: 20000))),
|
||||||
));
|
));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -5,10 +5,10 @@ class ActivitiesHeader extends StatefulWidget {
|
|||||||
const ActivitiesHeader({super.key});
|
const ActivitiesHeader({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_ActivitiesHeaderState createState() => _ActivitiesHeaderState();
|
State<ActivitiesHeader> createState() => ActivitiesHeaderState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ActivitiesHeaderState extends State<ActivitiesHeader> {
|
class ActivitiesHeaderState extends State<ActivitiesHeader> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -1,58 +1,107 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
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/classes/activity_action.dart';
|
||||||
import 'package:sendtrain/widgets/action_card.dart';
|
import 'package:sendtrain/models/activity_timer_model.dart';
|
||||||
|
|
||||||
class ActivityActionView extends StatefulWidget {
|
class ActivityActionView extends StatefulWidget {
|
||||||
ActivityActionView({super.key, required this.action});
|
const ActivityActionView({super.key, required this.action});
|
||||||
|
final ActivityAction action;
|
||||||
ActivityAction action;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ActivityActionView> createState() => _ActivityActionViewState();
|
State<ActivityActionView> createState() => ActivityActionViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ActivityActionViewState extends State<ActivityActionView> {
|
class ActivityActionViewState extends State<ActivityActionView> {
|
||||||
|
final ItemScrollController itemScrollController = ItemScrollController();
|
||||||
|
final ScrollOffsetController scrollOffsetController =
|
||||||
|
ScrollOffsetController();
|
||||||
|
final ItemPositionsListener itemPositionsListener =
|
||||||
|
ItemPositionsListener.create();
|
||||||
|
final ScrollOffsetListener scrollOffsetListener =
|
||||||
|
ScrollOffsetListener.create();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
ActivityTimerModel atm =
|
||||||
|
Provider.of<ActivityTimerModel>(context, listen: true);
|
||||||
|
List<List<Map<String, dynamic>>> 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(
|
return Expanded(
|
||||||
child: ListView.builder(
|
child: ScrollablePositionedList.builder(
|
||||||
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
|
padding: const EdgeInsets.fromLTRB(10, 0, 10, 20),
|
||||||
itemCount: widget.action.activityActionSet.total,
|
itemCount: widget.action.activityActionSet.total,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemScrollController: itemScrollController,
|
||||||
String title = widget.action.title;
|
scrollOffsetController: scrollOffsetController,
|
||||||
Set actionSet = widget.action.activityActionSet;
|
itemPositionsListener: itemPositionsListener,
|
||||||
Reps setReps = widget.action.activityActionSet.reps;
|
scrollOffsetListener: scrollOffsetListener,
|
||||||
List<Card> contents = [];
|
itemBuilder: (BuildContext context, int setNum) {
|
||||||
|
List<GestureDetector> content = [];
|
||||||
|
List<Map<String, dynamic>> set = sets[setNum];
|
||||||
|
|
||||||
// contents.add(Card(child: Text('Set ${index + 1}')));
|
for (int actionNum = 0; actionNum < set.length; actionNum++) {
|
||||||
|
Map<String, dynamic> setItem = set[actionNum];
|
||||||
|
|
||||||
for (int repCount = 0; repCount < setReps.amounts[index]; repCount++) {
|
content.add(GestureDetector(
|
||||||
contents.add(Card(
|
onTap: () {
|
||||||
child: Padding(
|
atm.setAction(setNum, actionNum, 'manual');
|
||||||
padding: const EdgeInsets.fromLTRB(15, 15, 15, 15),
|
atm.setActionCount();
|
||||||
child: Column(
|
|
||||||
children: [
|
itemScrollController.scrollTo(
|
||||||
Row(children: [Text('Exercise: ${widget.action.title}')]),
|
index: setNum,
|
||||||
Row(children: [Text('Type: ${actionSet.type}')]),
|
duration: Duration(milliseconds: 500),
|
||||||
Row(children: [
|
curve: Curves.easeInOutCubic);
|
||||||
Text(
|
},
|
||||||
'Set: ${index + 1} / ${widget.action.activityActionSet.total}')
|
child: Row(children: [
|
||||||
]),
|
Ink(
|
||||||
Row(children: [
|
width: 70,
|
||||||
Text('Rep: ${repCount + 1} / ${setReps.amounts[index]}')
|
padding: const EdgeInsets.all(15),
|
||||||
]),
|
color: atm.isCurrentItem(setNum, actionNum)
|
||||||
Row(children: [Text('Tempo: ${setReps.tempo[index]}')]),
|
? Theme.of(context).colorScheme.primaryContainer
|
||||||
Row(children: [
|
: Theme.of(context).colorScheme.onPrimary,
|
||||||
Text('Weight: ${setReps.weights[index]}')
|
child: Text(
|
||||||
]),
|
textAlign: TextAlign.center,
|
||||||
Row(children: [Text('Rest: ${setReps.rest}')])
|
'${setNum + 1}.${actionNum + 1} ')),
|
||||||
],
|
Expanded(
|
||||||
))));
|
child: Ink(
|
||||||
|
padding: const EdgeInsets.all(15),
|
||||||
|
color: atm.isCurrentItem(setNum, actionNum)
|
||||||
|
? Theme.of(context).colorScheme.surfaceBright
|
||||||
|
: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
|
child: Text(
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
'${setItem['name']}: ${setItem['amount']} ${setItem['type']}')))
|
||||||
|
])));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Column(children: contents);
|
if (setNum == 0) {
|
||||||
|
return Card(
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(0),
|
||||||
|
topRight: Radius.circular(0),
|
||||||
|
bottomLeft: Radius.circular(10),
|
||||||
|
bottomRight: Radius.circular(10)),
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
child: Column(children: content));
|
||||||
|
} else {
|
||||||
|
return Card(
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(10),
|
||||||
|
topRight: Radius.circular(10),
|
||||||
|
bottomLeft: Radius.circular(10),
|
||||||
|
bottomRight: Radius.circular(10)),
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
child: Column(children: content));
|
||||||
|
}
|
||||||
|
// return Column(children: contents);
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:sendtrain/classes/media.dart';
|
import 'package:sendtrain/classes/media.dart';
|
||||||
import 'package:sendtrain/models/activity_model.dart';
|
import 'package:sendtrain/models/activity_model.dart';
|
||||||
import 'package:sendtrain/widgets/activity_view.dart';
|
import 'package:sendtrain/widgets/activity_view.dart';
|
||||||
import 'package:sendtrain/widgets/media_card.dart';
|
|
||||||
|
|
||||||
class ActivityCard extends StatelessWidget {
|
class ActivityCard extends StatelessWidget {
|
||||||
const ActivityCard({super.key, required this.activity});
|
const ActivityCard({super.key, required this.activity});
|
||||||
@ -65,9 +64,9 @@ class ActivityCard extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImageProvider findMediaByType(List<Media>? media, String type) {
|
ImageProvider findMediaByType(List<Media>? 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);
|
return NetworkImage(found.first.reference);
|
||||||
} else {
|
} else {
|
||||||
// Element is not found
|
// Element is not found
|
||||||
|
@ -1,44 +1,31 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'package:sendtrain/classes/activity_action.dart';
|
import 'package:sendtrain/classes/activity_action.dart';
|
||||||
import 'package:sendtrain/classes/media.dart';
|
import 'package:sendtrain/classes/media.dart';
|
||||||
import 'package:sendtrain/models/activity_model.dart';
|
import 'package:sendtrain/models/activity_model.dart';
|
||||||
|
import 'package:sendtrain/models/activity_timer_model.dart';
|
||||||
import 'package:sendtrain/widgets/activity_action_view.dart';
|
import 'package:sendtrain/widgets/activity_action_view.dart';
|
||||||
import 'package:sendtrain/widgets/media_card.dart';
|
import 'package:sendtrain/widgets/media_card.dart';
|
||||||
|
|
||||||
class ActivityView extends StatefulWidget {
|
class ActivityView extends StatefulWidget {
|
||||||
ActivityView({super.key, required this.activity});
|
const ActivityView({super.key, required this.activity});
|
||||||
|
final ActivityModel activity;
|
||||||
ActivityModel activity;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ActivityView> createState() => _ActivityViewState();
|
State<ActivityView> createState() => _ActivityViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ActivityViewState extends State<ActivityView> {
|
class _ActivityViewState extends State<ActivityView> {
|
||||||
Timer? _periodicTimer;
|
|
||||||
int _tickCount = 0;
|
|
||||||
int currentSet = 0;
|
|
||||||
int currentRep = 0;
|
|
||||||
|
|
||||||
void _startPeriodicTimer() {
|
|
||||||
const oneSecond = Duration(seconds: 1);
|
|
||||||
_periodicTimer = Timer.periodic(oneSecond, (Timer timer) {
|
|
||||||
setState(() {
|
|
||||||
_tickCount++;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
ActivityModel activity = widget.activity;
|
ActivityModel activity = widget.activity;
|
||||||
|
ActivityTimerModel atm =
|
||||||
|
Provider.of<ActivityTimerModel>(context, listen: false);
|
||||||
|
|
||||||
var content = [
|
atm.setup(activity);
|
||||||
|
|
||||||
|
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
AppBar(
|
AppBar(
|
||||||
// surfaceTintColor: ThemeData.dark(useMaterial3: true).colorScheme.primary,
|
|
||||||
// backgroundColor: ThemeData.dark(useMaterial3: true).colorScheme.primaryContainer,
|
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
title: const Text('Activity', style: TextStyle(fontSize: 15)),
|
title: const Text('Activity', style: TextStyle(fontSize: 15)),
|
||||||
),
|
),
|
||||||
@ -63,48 +50,68 @@ class _ActivityViewState extends State<ActivityView> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||||
'Actions:'))
|
'Actions')),
|
||||||
];
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 10, right: 10),
|
||||||
for (var action in activity.actions) {
|
child: Card(
|
||||||
content.add(ActivityActionView(action: action));
|
clipBehavior: Clip.antiAlias,
|
||||||
}
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
// add bottom bar to manage activity
|
topLeft: Radius.circular(10),
|
||||||
content.add(Container(
|
topRight: Radius.circular(10)),
|
||||||
height: MediaQuery.sizeOf(context).height * .07,
|
),
|
||||||
color: ThemeData.dark(useMaterial3: true).colorScheme.primaryContainer,
|
color: Theme.of(context).colorScheme.onPrimary,
|
||||||
child: Row(children: [
|
child: Row(children: [
|
||||||
Expanded(
|
Ink(
|
||||||
flex: 1,
|
width: 70,
|
||||||
child: Flex(direction: Axis.horizontal, children: [
|
color: Theme.of(context).colorScheme.primaryContainer,
|
||||||
IconButton(
|
child: Consumer<ActivityTimerModel>(
|
||||||
iconSize: 30,
|
builder: (context, atm, child) {
|
||||||
icon: const Icon(Icons.play_arrow_rounded),
|
return IconButton(
|
||||||
onPressed: _startPeriodicTimer),
|
alignment: AlignmentDirectional.center,
|
||||||
IconButton(
|
icon: atm.isActive
|
||||||
iconSize: 30,
|
? const Icon(Icons.pause_rounded)
|
||||||
icon: const Icon(Icons.skip_next_rounded),
|
: const Icon(Icons.play_arrow_rounded),
|
||||||
onPressed: () {})
|
onPressed: () =>
|
||||||
])),
|
{atm.isActive ? atm.pause() : atm.start()});
|
||||||
Expanded(
|
},
|
||||||
flex: 1,
|
)),
|
||||||
child: Text(
|
Expanded(
|
||||||
style: const TextStyle(fontSize: 25),
|
flex: 1,
|
||||||
textAlign: TextAlign.center,
|
child: Stack(alignment: Alignment.center, children: [
|
||||||
'$_tickCount'),
|
Container(
|
||||||
),
|
alignment: Alignment.center,
|
||||||
const Expanded(
|
child: Consumer<ActivityTimerModel>(
|
||||||
flex: 1,
|
builder: (context, atm, child) {
|
||||||
child: Padding(
|
return Text(
|
||||||
padding: EdgeInsets.only(right: 10),
|
style: const TextStyle(fontSize: 20),
|
||||||
child: Text(
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(fontSize: 15),
|
'${atm.actionCount} ${atm.currentAction['type']}');
|
||||||
textAlign: TextAlign.right,
|
},
|
||||||
'Set: 1/3 \nRep: 1/5'))),
|
),
|
||||||
])));
|
),
|
||||||
return Column(
|
Container(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start, children: content);
|
alignment: Alignment.centerRight,
|
||||||
|
padding: EdgeInsets.only(right: 10),
|
||||||
|
child: Consumer<ActivityTimerModel>(
|
||||||
|
builder: (context, atm, child) {
|
||||||
|
return Text(
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
'${atm.currentAction['actionID'] + 1} of ${atm.totalActions()}');
|
||||||
|
})),
|
||||||
|
])),
|
||||||
|
]))),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(left: 14, right: 14),
|
||||||
|
child: Consumer<ActivityTimerModel>(builder: (context, atm, child) {
|
||||||
|
return LinearProgressIndicator(
|
||||||
|
value: atm.progress,
|
||||||
|
semanticsLabel: 'Activity Progress',
|
||||||
|
);
|
||||||
|
})),
|
||||||
|
ActivityActionView(action: activity.actions[0]),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,8 +39,8 @@ class SessionCard extends StatelessWidget {
|
|||||||
actions: [
|
actions: [
|
||||||
ActivityAction(
|
ActivityAction(
|
||||||
id: 1,
|
id: 1,
|
||||||
title: 'test action',
|
title: '1, 3, 5',
|
||||||
description: 'test description',
|
description: 'Move between the first, third, and fifth rungs, alternating hands. Rest and alternate sides, to start',
|
||||||
media: [
|
media: [
|
||||||
Media(
|
Media(
|
||||||
id: 1,
|
id: 1,
|
||||||
@ -57,27 +57,27 @@ class SessionCard extends StatelessWidget {
|
|||||||
activityActionSet: Set(
|
activityActionSet: Set(
|
||||||
type: 'drop_set',
|
type: 'drop_set',
|
||||||
total: 3,
|
total: 3,
|
||||||
rest: 3000,
|
rest: 300000,
|
||||||
reps: Reps(
|
reps: Reps(
|
||||||
type: 'count',
|
type: 'repititions',
|
||||||
tempo: [2, 3, 5],
|
tempo: [0],
|
||||||
amounts: [5, 3, 2],
|
amounts: [1, 1, 1],
|
||||||
weights: [50, 70, 80],
|
weights: [0],
|
||||||
rest: 200))),
|
rest: 20000))),
|
||||||
],
|
],
|
||||||
resources: ['https://www.youtube.com/watch?v=bLz0xp1PEm4']),
|
resources: ['https://www.youtube.com/watch?v=bLz0xp1PEm4']),
|
||||||
ActivityModel(
|
ActivityModel(
|
||||||
id: 1,
|
id: 2,
|
||||||
title: 'Projecting',
|
title: 'Projecting',
|
||||||
type: 'fundamental',
|
type: 'fundamental',
|
||||||
categories: ['technique', 'conditioning'],
|
categories: ['technique', 'conditioning'],
|
||||||
description:
|
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: [
|
actions: [
|
||||||
ActivityAction(
|
ActivityAction(
|
||||||
id: 1,
|
id: 1,
|
||||||
title: 'test action',
|
title: 'Attempt Climb',
|
||||||
description: 'test description',
|
description: 'Attempt your selected climb, if you fall off early in the climb, attempt again. 1 repitition equals roughly doing all the moves, not necessarily in 1 attempt.',
|
||||||
media: [
|
media: [
|
||||||
Media(
|
Media(
|
||||||
id: 1,
|
id: 1,
|
||||||
@ -92,52 +92,52 @@ class SessionCard extends StatelessWidget {
|
|||||||
description: 'How to project climbs')
|
description: 'How to project climbs')
|
||||||
],
|
],
|
||||||
activityActionSet: Set(
|
activityActionSet: Set(
|
||||||
type: 'drop_set',
|
type: 'standard',
|
||||||
total: 3,
|
total: 10,
|
||||||
rest: 3000,
|
rest: 300000,
|
||||||
reps: Reps(
|
reps: Reps(
|
||||||
type: 'count',
|
type: 'repititions',
|
||||||
tempo: [2, 3, 5],
|
tempo: [0],
|
||||||
amounts: [5, 3, 2],
|
amounts: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
weights: [50, 70, 80],
|
weights: [0],
|
||||||
rest: 200))),
|
rest: 0))),
|
||||||
],
|
],
|
||||||
resources: ['https://www.youtube.com/watch?v=dyAvbUvY_PU']),
|
resources: ['https://www.youtube.com/watch?v=dyAvbUvY_PU']),
|
||||||
ActivityModel(
|
ActivityModel(
|
||||||
id: 1,
|
id: 3,
|
||||||
title: 'Weighted Pull Ups',
|
title: 'Long Block Pulls',
|
||||||
type: 'fundamental',
|
type: 'Hypertrophy',
|
||||||
categories: ['Strength', 'Power'],
|
categories: ['Strength', 'Power'],
|
||||||
description:
|
description:
|
||||||
"Weight pullups to increase strength and maximal pulling force.",
|
"Block pull on a edge of a specific size and time to induce a hypertrophic effect on the formarms.",
|
||||||
actions: [
|
actions: [
|
||||||
ActivityAction(
|
ActivityAction(
|
||||||
id: 1,
|
id: 1,
|
||||||
title: 'test action',
|
title: 'Long Pulls',
|
||||||
description: 'test description',
|
description: 'Select your desired weight to pull, add it to the block. You should aim for an effort level of 8-9 when reaching the end of the set time, going to failure will result in significantly extended recovery time.',
|
||||||
media: [
|
media: [
|
||||||
Media(
|
Media(
|
||||||
id: 1,
|
id: 1,
|
||||||
reference:
|
reference:
|
||||||
'https://trainingforclimbing.com/wp-content/uploads/2016/03/hypergravity_pull-up-compress3-966x1024.jpg',
|
'https://trailandcrag.com/sites/default/files/inline-images/05-min_3.jpg',
|
||||||
type: 'image',
|
type: 'image',
|
||||||
description: 'Weighted Pullups'),
|
description: 'Block pull example'),
|
||||||
Media(
|
Media(
|
||||||
id: 1,
|
id: 1,
|
||||||
reference: '7TLG1mHQHgw',
|
reference: 'sZVAEy9UmoY',
|
||||||
type: 'youtube',
|
type: 'youtube',
|
||||||
description: 'How to do weighted pullups')
|
description: 'Principals of Grip gains, and related protocols')
|
||||||
],
|
],
|
||||||
activityActionSet: Set(
|
activityActionSet: Set(
|
||||||
type: 'drop_set',
|
type: 'alternating',
|
||||||
total: 3,
|
total: 5,
|
||||||
rest: 3000,
|
rest: 5000,
|
||||||
reps: Reps(
|
reps: Reps(
|
||||||
type: 'count',
|
type: 'seconds',
|
||||||
tempo: [2, 3, 5],
|
tempo: [0],
|
||||||
amounts: [5, 3, 2],
|
amounts: [5, 5, 5, 5, 5],
|
||||||
weights: [50, 70, 80],
|
weights: [80, 80, 80, 80, 80],
|
||||||
rest: 200))),
|
rest: 5000))),
|
||||||
],
|
],
|
||||||
resources: ['https://www.youtube.com/watch?v=dyAvbUvY_PU']),
|
resources: ['https://www.youtube.com/watch?v=dyAvbUvY_PU']),
|
||||||
],
|
],
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import flutter_inappwebview_macos
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
|
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
|
||||||
}
|
}
|
||||||
|
10
pubspec.yaml
10
pubspec.yaml
@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
version: 0.2.1
|
version: 0.2.1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=2.19.2 <3.0.0'
|
sdk: '>=3.0.0 <4.0.0'
|
||||||
|
|
||||||
# Dependencies specify other packages that your package needs in order to work.
|
# Dependencies specify other packages that your package needs in order to work.
|
||||||
# To automatically upgrade your package dependencies to the latest versions
|
# To automatically upgrade your package dependencies to the latest versions
|
||||||
@ -36,9 +36,11 @@ dependencies:
|
|||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.2
|
cupertino_icons: ^1.0.2
|
||||||
scaler: ^1.1.2+1
|
scaler: ^1.1.2+1
|
||||||
intl: ^0.18.0
|
intl: ^0.20.1
|
||||||
youtube_player_flutter: ^8.1.2
|
youtube_player_flutter: ^9.1.1
|
||||||
json_annotation: ^4.9.0
|
json_annotation: ^4.9.0
|
||||||
|
provider: ^6.1.2
|
||||||
|
scrollable_positioned_list: ^0.3.8
|
||||||
|
|
||||||
flutter_launcher_name:
|
flutter_launcher_name:
|
||||||
name: "SendTrain"
|
name: "SendTrain"
|
||||||
@ -52,7 +54,7 @@ dev_dependencies:
|
|||||||
# activated in the `analysis_options.yaml` file located at the root of your
|
# activated in the `analysis_options.yaml` file located at the root of your
|
||||||
# package. See that file for information about deactivating specific lint
|
# package. See that file for information about deactivating specific lint
|
||||||
# rules and activating additional ones.
|
# rules and activating additional ones.
|
||||||
flutter_lints: ^2.0.0
|
flutter_lints: ^5.0.0
|
||||||
build_runner: ^2.4.13
|
build_runner: ^2.4.13
|
||||||
json_serializable: ^6.9.0
|
json_serializable: ^6.9.0
|
||||||
|
|
||||||
|
@ -6,6 +6,9 @@
|
|||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
|
FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi"));
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
flutter_inappwebview_windows
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
Loading…
x
Reference in New Issue
Block a user