Untitled

 avatar
unknown
plain_text
a year ago
23 kB
7
Indexable
import 'package:flutter/material.dart';
import 'package:self_science_station/model/sensor_model.dart';
import 'package:self_science_station/provider/local_provider.dart';
import 'package:self_science_station/provider/study_provider.dart';
import 'package:self_science_station/provider/user_provider.dart';
import 'package:self_science_station/provider/sensor_provider.dart';
import 'package:provider/provider.dart';
import 'package:self_science_station/screens/checkin_complete_screen.dart';
import 'package:self_science_station/screens/survey_screen.dart';
import 'package:self_science_station/sensor/sensorInstance.dart';
import 'package:self_science_station/utils/utils.dart';
import 'package:video_player/video_player.dart';
import 'package:chewie/chewie.dart';

import 'package:collection/collection.dart';

class SensorScreen extends StatefulWidget {
  final Function() onDone;
  final SensorTypeModel sensor;
  final String sessionID;
  final UniqueKey newKey;

  const SensorScreen(
      {required this.newKey,
      required this.sensor,
      required this.sessionID,
      required this.onDone})
      : super(key: newKey);

  @override
  State<SensorScreen> createState() => _SensorScreenState();
}

class _SensorScreenState extends State<SensorScreen> {
  final ScrollController _scrollController =
      ScrollController(initialScrollOffset: 0);

  bool videoInitialized = false;
  late VideoPlayerController videoPlayerController;
  ChewieController? chewieController;
  late Widget playerWidget;

  List<String> sensorsAlreadyInUse = [];
  bool foundTargetSensor = false;
  bool scanningForSensor = false;

  @override
  void initState() {
    super.initState();
    // Jump to top of scrollview
    WidgetsBinding.instance.addPostFrameCallback((_) async {
      // TODO allow for back button on this screen in the future
      final lp = Provider.of<LocalProvider>(context, listen: false);
      lp.hideBackButton();

      // Start scanning for the sensors
      final sp = Provider.of<SensorProvider>(context, listen: false);
      setState(() {
        sensorsAlreadyInUse = sp.physicalSensors[widget.sensor.id] ?? [];
      });

      print("Scanned sensors already in use: $sensorsAlreadyInUse");
    });

    if (!videoInitialized) {
      initializePlayer();
    }
  }

  Future<void> initializePlayer() async {
    // videoPlayerController = VideoPlayerController.networkUrl(Uri(
    //     scheme: 'https',
    //     host: 'flutter.github.io',
    //     path: 'assets-for-api-docs/assets/videos/butterfly.mp4'));

    String vidurl = widget.sensor.setUpVideoUrl;
    try {
      videoPlayerController = VideoPlayerController.asset(
          'assets/${Uri.parse(vidurl).pathSegments.last}');
    } catch (e) {
      videoPlayerController = VideoPlayerController.networkUrl(
          Uri.parse(widget.sensor.setUpVideoUrl));
    }
    videoPlayerController.setVolume(0.0);
    videoPlayerController.addListener(() {
      if (videoPlayerController.value.hasError) {
        setState(() {
          videoInitialized = false;
        });
      }
    });

    try {
      await videoPlayerController.initialize();
      chewieController = ChewieController(
          videoPlayerController: videoPlayerController,
          autoInitialize: true,
          autoPlay: true,
          looping: true,
          allowFullScreen: true,
          showControls: false,
          showControlsOnInitialize: false,
          overlay: null, // remove overlay that otherwise shows in the beginning
          allowPlaybackSpeedChanging: false //remove interactability
          );

      setState(() {
        videoInitialized = true;
      });
    } catch (e) {
      setState(() {
        videoInitialized = false;
      });
    }
  }

  @override
  void dispose() {
    videoPlayerController.dispose();
    chewieController?.dispose();

    chewieController = null;
    videoInitialized = false;
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    // By listening to the sensor provider, this should be updated once a new sensor has been found in a scan
    final sp = Provider.of<SensorProvider>(context, listen: true);
    // Check to see if there is any delta between the sensors already in use and the sensors we have
    final sensorsInVicinity = sp.physicalSensors[widget.sensor.id] ?? [];

    print("Scanned sensors in vicinity: $sensorsInVicinity");
    final newlyConnectedSensors = sensorsInVicinity
        .where((s) =>
            !sensorsAlreadyInUse.contains(s) ||
            true) // TODO remove true once we figure out how to clear bluetooth scan cache
        .toList();

    return Scaffold(
        body: SafeArea(
      child: Center(
          child: Padding(
              padding: const EdgeInsets.all(
                70.0,
              ),
              child: Scrollbar(
                controller: _scrollController,
                thumbVisibility: true,
                child: SingleChildScrollView(
                  controller: _scrollController,
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: [
                      Row(
                        children: [
                          Text(
                              "${widget.sensor.vendor.toTitleCase()} ${widget.sensor.name} (${widget.sensor.type})",
                              style: const TextStyle(
                                  fontSize: 25, fontWeight: FontWeight.bold)),
                          const SizedBox(width: 10),
                          IconButton(
                              icon: const Icon(Icons.info, size: 30),
                              onPressed: () =>
                                  _sensorInfoModalBuilder(context)),
                        ],
                      ),
                      const SizedBox(
                        height: 5,
                      ),
                      Row(
                        crossAxisAlignment: videoInitialized
                            ? CrossAxisAlignment.start
                            : CrossAxisAlignment.center,
                        children: [
                          VideoContainer(
                              videoInitialized: videoInitialized,
                              chewieController: chewieController,
                              sensorType: widget.sensor.type),
                          Expanded(
                            child: Padding(
                              padding:
                                  const EdgeInsets.only(left: 30.0, top: 30.0),
                              child: Column(
                                children: widget.sensor.instructions
                                    .mapIndexed(
                                      (instructionIdx, instruction) => Column(
                                        children: [
                                          Wrap(children: [
                                            Text("Step ${instructionIdx + 1}",
                                                style: const TextStyle(
                                                    fontWeight: FontWeight.bold,
                                                    fontSize: 20)),
                                            Text(instruction.step,
                                                style: const TextStyle(
                                                    fontSize: 20)),
                                          ]),
                                          const SizedBox(height: 20)
                                        ],
                                      ),
                                    )
                                    .toList(),
                              ),
                            ),
                          )
                        ],
                      ),
                      const SizedBox(height: 30),
                      if (newlyConnectedSensors.isNotEmpty &&
                          !widget.sensor.wifiOnly)
                        Column(children: [
                          const Text("Choose a Sensor to Connect to:",
                              style: TextStyle(
                                  fontSize: 25, fontWeight: FontWeight.bold)),
                          const Text(
                              // TODO this only applies to Polar sensors at the moment
                              "The ID is printed on the top edge of the sensor",
                              style: TextStyle(fontSize: 15)),
                          const SizedBox(height: 20),
                          Wrap(
                              runSpacing: 15.0,
                              children: newlyConnectedSensors
                                  .map((s) => Padding(
                                        padding: const EdgeInsets.all(10.0),
                                        child: ElevatedButton(
                                            style: ButtonStyle(
                                                shape: MaterialStateProperty.all<RoundedRectangleBorder>(
                                                    RoundedRectangleBorder(
                                                        side: BorderSide(
                                                            color:
                                                                Theme.of(context)
                                                                    .primaryColor,
                                                            width:
                                                                2.0), // Add this line
                                                        borderRadius:
                                                            BorderRadius.circular(
                                                                30.0))),
                                                minimumSize:
                                                    MaterialStateProperty.all(
                                                        const Size(250, 80)),
                                                backgroundColor:
                                                    MaterialStateProperty.all(
                                                        Colors.white)),
                                            onPressed: () {
                                              _sensorTestModalBuilder(
                                                  context, s);
                                            },
                                            child: Text(s,
                                                style: const TextStyle(
                                                    fontSize: 20,
                                                    color: Colors.black))),
                                      ))
                                  .toList()),
                        ]),
                      const SizedBox(height: 30),
                      newlyConnectedSensors.isEmpty || widget.sensor.wifiOnly
                          ? ElevatedButton(
                              style: ButtonStyle(
                                  shape: MaterialStateProperty.all<
                                          RoundedRectangleBorder>(
                                      RoundedRectangleBorder(
                                          borderRadius:
                                              BorderRadius.circular(30.0))),
                                  minimumSize: MaterialStateProperty.all(
                                      const Size(250, 80)),
                                  backgroundColor: MaterialStateProperty.all(
                                      Theme.of(context).colorScheme.primary)),
                              // TODO bring back eventually when/if we have client side sensor SDKs
                              onPressed: () {
                                // We don't need to check for connection if it's a wifi only device
                                if (widget.sensor.wifiOnly) {
                                  final sInstance =
                                      sp.getSensorFromType(widget.sensor.id);
                                  sInstance.connectSensor();
                                  sInstance.startRecording("");
                                  widget.onDone();
                                } else {
                                  // Hopefully the length of newlyConnectedSensors is just 1
                                  // We use the most recently connected one just in case
                                  if (newlyConnectedSensors.isNotEmpty) {
                                    _sensorTestModalBuilder(
                                        context, newlyConnectedSensors.last);
                                  }
                                }
                              },
                              child: newlyConnectedSensors.isNotEmpty
                                  ? const Text("Connect",
                                      style: TextStyle(fontSize: 20))
                                  : const SizedBox(
                                      width: 200,
                                      child: Row(
                                        mainAxisAlignment:
                                            MainAxisAlignment.center,
                                        children: [
                                          Text("Scanning",
                                              style: TextStyle(fontSize: 20)),
                                          SizedBox(width: 15),
                                          CircularProgressIndicator(
                                              color: Colors.white),
                                        ],
                                      ),
                                    ))
                          : const SizedBox.shrink(),
                    ],
                  ),
                ),
              ))),
    ));
  }

  Future<void> _sensorInfoModalBuilder(BuildContext context) {
    return showDialog<void>(
      useRootNavigator: false,
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          insetPadding: const EdgeInsets.all(100.0),
          titlePadding: const EdgeInsets.fromLTRB(50.0, 30, 0, 0),
          contentPadding:
              const EdgeInsets.symmetric(horizontal: 50.0, vertical: 30.0),
          title: Text(
              '${widget.sensor.vendor.toTitleCase()} ${widget.sensor.name}',
              style: TextStyle(
                  fontWeight: FontWeight.bold, color: Colors.grey.shade700)),
          content: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Text("Overview:",
                  style: TextStyle(fontWeight: FontWeight.bold)),
              const SizedBox(height: 20),
              Text(widget.sensor.description),
              const SizedBox(height: 20),
              const Text("Measurement Capabilities:",
                  style: TextStyle(fontWeight: FontWeight.bold)),
              const SizedBox(height: 20),
              Wrap(
                  runSpacing: 15.0,
                  children: widget.sensor.canRecord
                      .map(
                        (metric) => Container(
                          margin: const EdgeInsets.only(right: 10.0),
                          padding: const EdgeInsets.symmetric(
                              horizontal: 20.0, vertical: 10.0),
                          decoration: BoxDecoration(
                            borderRadius: BorderRadius.circular(30),
                            border:
                                Border.all(color: Colors.lightBlue.shade100),
                          ),
                          child: Text(metric.name),
                        ),
                      )
                      .toList()),
            ],
          ),
          scrollable: true,
        );
      },
    );
  }

  Future<void> _sensorTestModalBuilder(
      BuildContext context, String targetSensorDeviceID) {
    final studyProvider = Provider.of<StudyProvider>(context, listen: false);
    final up = Provider.of<UserProvider>(context, listen: false);
    final sp = Provider.of<SensorProvider>(context, listen: false);
    final sensorInstanceDeviceID = targetSensorDeviceID;
    // Get sensor instance from ID
    final sInstance = sp.getSensorFromInstanceDeviceID(sensorInstanceDeviceID)!;

    sInstance.connectSensorWithTimeout(const Duration(seconds: 9));

    Widget buildActionButton(
        SensorInstance? updatedSensor, bool isConnected, bool isConnecting) {
      //isConnected = true;
      if (isConnecting) return const SizedBox.shrink();

      if (isConnected && updatedSensor != null) {
        return ElevatedButton(
            onPressed: () async {
              Navigator.pop(context);
              try {
                final isRecording = await updatedSensor.isRecording();
                // Must stop recording before we start again
                if (isRecording) {
                  await updatedSensor.stopRecording();
                }
                // TODO in the future we could maybe capture this data and send it up here
                await updatedSensor.emptyRecordings();
                // Make a unique identifier for the recording
                // Due to constraints on some devices, this is limited to 64 bytes (characters)
                // Use concatenation of the user ID and last 12 chars of session id
                final userID = up.userModel.id;
                final abbreviatedSessionID =
                    widget.sessionID.substring(widget.sessionID.length - 12);
                await updatedSensor
                    .startRecording("${userID}_$abbreviatedSessionID");
                widget.onDone();
              } catch (e) {
                // Try again
                print("Error recording from device: $e");
              }
              // dispose();
            },
            child: const Text("Continue"));
      }

      // return ElevatedButton(
      //     onPressed: () {
      //       Navigator.pop(context);
      //     },
      //     child: const Text("Try Again"));
      return Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          ElevatedButton(
            onPressed: () {
              Navigator.pop(context);
            },
            child: const Text("Try Again"),
          ),
          const SizedBox(width: 20),
          ElevatedButton(
            onPressed: () {
              // Add your skip step functionality here.
              Navigator.pop(context);
              widget.onDone();
              // dispose();
            },
            child: const Text("Skip Step"),
          ),
        ],
      );
    }

    return showDialog<void>(
      useRootNavigator: false,
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          insetPadding: const EdgeInsets.all(100.0),
          contentPadding:
              const EdgeInsets.symmetric(horizontal: 50.0, vertical: 30.0),
          title: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text("ID: ${sInstance.getSensorInstance().deviceID}",
                  style: const TextStyle(fontSize: 10)),
              Text(
                  'Device: ${widget.sensor.vendor.toCapitalized()} ${widget.sensor.name.toCapitalized()}',
                  style: TextStyle(
                      fontWeight: FontWeight.bold,
                      color: Colors.grey.shade700)),
            ],
          ),
          content: StatefulBuilder(
              builder: (BuildContext context, StateSetter setState) {
            return Consumer<SensorProvider>(
              builder: (context, value, child) {
                final updatedSensor =
                    value.getSensorFromInstanceDeviceID(sensorInstanceDeviceID);
                final isSensorConnected =
                    (updatedSensor != null ? updatedSensor.isConnected : false);
                final isSensorConnecting = (updatedSensor != null
                    ? updatedSensor.isConnecting
                    : false);
                //print("RE RENDERED WIDGET");
                return Column(
                  children: [
                    Container(
                        child: isSensorConnecting
                            ? const CircularProgressIndicator()
                            : (isSensorConnected
                                ? Icon(Icons.check_circle,
                                    size: 75, color: Colors.green.shade900)
                                : Icon(Icons.error,
                                    size: 75, color: Colors.red.shade900))),
                    const SizedBox(height: 20),
                    Text(
                        isSensorConnecting
                            ? "Connecting to device..."
                            : (isSensorConnected
                                ? "Connected"
                                : "Unable to connect"),
                        style: const TextStyle(fontWeight: FontWeight.bold)),
                    const SizedBox(height: 20),
                    buildActionButton(
                        updatedSensor, isSensorConnected, isSensorConnecting),
                  ],
                );
              },
            );
          }),
          scrollable: true,
        );
      },
    );
  }
}

class VideoContainer extends StatelessWidget {
  const VideoContainer({
    super.key,
    required this.videoInitialized,
    required this.chewieController,
    required this.sensorType,
  });

  final bool videoInitialized;
  final ChewieController? chewieController;
  final String sensorType;

  @override
  Widget build(BuildContext context) {
    if (videoInitialized && chewieController != null) {
      return Expanded(
          child: Container(
              color: Colors.white,
              child: ConstrainedBox(
                  constraints: const BoxConstraints(maxHeight: 400),
                  child: Chewie(
                    controller: chewieController!,
                  ))));
    } else {
      return Container(
          alignment: Alignment.topCenter,
          color: Colors.white,
          child: ConstrainedBox(
              constraints: const BoxConstraints(maxHeight: 400),
              child:
                  Image.asset("assets/${sensorType.replaceAll(' ', '')}.png")));
    }
  }
}
Editor is loading...
Leave a Comment