Add video player widget for exercise details

This commit is contained in:
Roland Geider
2022-01-29 12:43:50 +01:00
parent 5d28847c69
commit c24bc9783e
8 changed files with 173 additions and 16 deletions

View File

@@ -20,6 +20,7 @@ Exercise _$ExerciseFromJson(Map<String, dynamic> json) {
'muscles_secondary',
'equipment',
'images',
'videos',
'comments'
],
);
@@ -41,6 +42,9 @@ Exercise _$ExerciseFromJson(Map<String, dynamic> json) {
images: (json['images'] as List<dynamic>?)
?.map((e) => ExerciseImage.fromJson(e as Map<String, dynamic>))
.toList(),
videos: (json['videos'] as List<dynamic>?)
?.map((e) => Video.fromJson(e as Map<String, dynamic>))
.toList(),
tips: (json['comments'] as List<dynamic>?)
?.map((e) => Comment.fromJson(e as Map<String, dynamic>))
.toList(),
@@ -60,5 +64,6 @@ Map<String, dynamic> _$ExerciseToJson(Exercise instance) => <String, dynamic>{
instance.musclesSecondary.map((e) => e.toJson()).toList(),
'equipment': instance.equipment.map((e) => e.toJson()).toList(),
'images': instance.images.map((e) => e.toJson()).toList(),
'videos': instance.videos.map((e) => e.toJson()).toList(),
'comments': instance.tips.map((e) => e.toJson()).toList(),
};

View File

@@ -17,6 +17,7 @@
*/
import 'package:json_annotation/json_annotation.dart';
import 'package:wger/helpers/json.dart';
part 'video.g.dart';
@@ -28,16 +29,16 @@ class Video {
@JsonKey(required: true)
final String uuid;
@JsonKey(name: 'video', required: true)
final String url;
@JsonKey(name: 'exercise_base', required: true)
final int base;
@JsonKey(name: 'is_front', required: true)
final bool isFront;
@JsonKey(required: true)
final int size;
@JsonKey(required: true)
@JsonKey(required: true, fromJson: stringToNum, toJson: numToString)
final num duration;
@JsonKey(required: true)
@@ -56,14 +57,14 @@ class Video {
final int license;
@JsonKey(name: 'license_author', required: true)
final String licenseAuthor;
final String? licenseAuthor;
const Video({
required this.id,
required this.uuid,
required this.base,
required this.isFront,
required this.size,
required this.url,
required this.duration,
required this.width,
required this.height,

View File

@@ -0,0 +1,56 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'video.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Video _$VideoFromJson(Map<String, dynamic> json) {
$checkKeys(
json,
requiredKeys: const [
'id',
'uuid',
'video',
'exercise_base',
'size',
'duration',
'width',
'height',
'codec',
'codec_long',
'license',
'license_author'
],
);
return Video(
id: json['id'] as int,
uuid: json['uuid'] as String,
base: json['exercise_base'] as int,
size: json['size'] as int,
url: json['video'] as String,
duration: stringToNum(json['duration'] as String?),
width: json['width'] as int,
height: json['height'] as int,
codec: json['codec'] as String,
codecLong: json['codec_long'] as String,
license: json['license'] as int,
licenseAuthor: json['license_author'] as String?,
);
}
Map<String, dynamic> _$VideoToJson(Video instance) => <String, dynamic>{
'id': instance.id,
'uuid': instance.uuid,
'video': instance.url,
'exercise_base': instance.base,
'size': instance.size,
'duration': numToString(instance.duration),
'width': instance.width,
'height': instance.height,
'codec': instance.codec,
'codec_long': instance.codecLong,
'license': instance.license,
'license_author': instance.licenseAuthor,
};

View File

@@ -122,7 +122,7 @@ class ExercisesProvider extends WgerBaseProvider with ChangeNotifier {
Future<void> fetchAndSetExercises() async {
// Load exercises from cache, if available
final prefs = await SharedPreferences.getInstance();
if (prefs.containsKey(PREFS_EXERCISES)) {
if (false && prefs.containsKey(PREFS_EXERCISES)) {
final exerciseData = json.decode(prefs.getString(PREFS_EXERCISES)!);
if (DateTime.parse(exerciseData['expiresIn']).isAfter(DateTime.now())) {
exerciseData['exercises'].forEach((e) => _exercises.add(Exercise.fromJson(e)));

View File

@@ -24,6 +24,7 @@ import 'package:flutter_svg/svg.dart';
import 'package:wger/models/exercises/exercise.dart';
import 'package:wger/models/exercises/muscle.dart';
import 'package:wger/widgets/exercises/images.dart';
import 'package:wger/widgets/exercises/videos.dart';
class ExerciseDetail extends StatelessWidget {
final Exercise _exercise;
@@ -38,6 +39,9 @@ class ExerciseDetail extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Videos
...getVideos(),
// Images
...getImages(),
@@ -168,8 +172,24 @@ class ExerciseDetail extends StatelessWidget {
List<Widget> getImages() {
// TODO: add carousel for the other images
final List<Widget> out = [];
out.add(ExerciseImageWidget(image: _exercise.getMainImage));
out.add(const SizedBox(height: PADDING));
if (_exercise.getMainImage != null) {
out.add(ExerciseImageWidget(image: _exercise.getMainImage));
out.add(const SizedBox(height: PADDING));
}
return out;
}
List<Widget> getVideos() {
// TODO: add carousel for the other videos
final List<Widget> out = [];
if (_exercise.videos.isNotEmpty) {
_exercise.videos.map((v) => ExerciseVideoWidget(video: v)).forEach((element) {
out.add(element);
});
out.add(const SizedBox(height: PADDING));
}
return out;
}

View File

@@ -0,0 +1,74 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (C) 2020, 2021 wger Team
*
* wger Workout Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* wger Workout Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:video_player/video_player.dart';
import 'package:wger/models/exercises/video.dart';
class ExerciseVideoWidget extends StatefulWidget {
const ExerciseVideoWidget({
required this.video,
});
final Video video;
@override
State<ExerciseVideoWidget> createState() => _ExerciseVideoWidgetState();
}
class _ExerciseVideoWidgetState extends State<ExerciseVideoWidget> {
late VideoPlayerController _controller;
@override
void initState() {
super.initState();
_controller = VideoPlayerController.network(widget.video.url)
..initialize().then((_) {
// Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
setState(() {});
});
}
@override
void dispose() {
super.dispose();
_controller.dispose();
}
@override
Widget build(BuildContext context) {
return _controller.value.isInitialized
? Column(
children: [
AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
),
IconButton(
onPressed: () {
setState(() {
_controller.value.isPlaying ? _controller.pause() : _controller.play();
});
},
icon: Icon(_controller.value.isPlaying ? Icons.stop : Icons.play_arrow))
],
)
: Container();
}
}

View File

@@ -112,14 +112,14 @@ packages:
name: camera
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.4+6"
version: "0.9.4+9"
camera_platform_interface:
dependency: transitive
description:
name: camera_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.4"
version: "2.1.5"
camera_web:
dependency: transitive
description:
@@ -475,7 +475,7 @@ packages:
name: image_picker
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.4+4"
version: "0.8.4+5"
image_picker_for_web:
dependency: transitive
description:
@@ -1000,12 +1000,12 @@ packages:
source: hosted
version: "0.1.0"
video_player:
dependency: transitive
dependency: "direct main"
description:
name: video_player
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.14"
version: "2.2.16"
video_player_platform_interface:
dependency: transitive
description:

View File

@@ -32,7 +32,7 @@ dependencies:
sdk: flutter
android_metadata: ^0.2.1
camera: ^0.9.2+2
camera: ^0.9.4+9
charts_flutter: ^0.12.0
collection: ^1.15.0-nullsafety.4
cupertino_icons: ^1.0.0
@@ -42,7 +42,7 @@ dependencies:
flutter_typeahead: ^3.2.0
font_awesome_flutter: ^9.1.0
http: ^0.13.3
image_picker: ^0.8.4+1
image_picker: ^0.8.4+5
intl: ^0.17.0
json_annotation: ^4.3.0
version: ^2.0.0
@@ -53,6 +53,7 @@ dependencies:
table_calendar: ^3.0.2
url_launcher: ^6.0.10
flutter_barcode_scanner: ^2.0.0
video_player: ^2.2.16
dev_dependencies:
flutter_test: