carp_mobility_package 0.1.0 copy "carp_mobility_package: ^0.1.0" to clipboard
carp_mobility_package: ^0.1.0 copied to clipboard

A Flutter package for CARP mobility data collection and processing.

example/lib/main.dart

// ignore_for_file: avoid_print

import 'package:carp_mobility_package/carp_mobility_package.dart';
import 'package:carp_mobility_package/domain/tasks/wt_task.dart';
import 'package:carp_mobility_package/domain/tasks/tug_task.dart';
import 'package:carp_mobility_package/localization/mp_localizations.dart';
import 'package:carp_mobility_package/sensing/sensors/movesense_sensor.dart';
import 'package:carp_serializable/carp_serializable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:mdsflutter/Mds.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:research_package/research_package.dart';

void main() {
  ResearchPackage.ensureInitialized();
  runApp(const MainApp());
}

var buttons = [
  TaskParams(
    title: 'wt_measure_name',
    task: WTTask(
      identifier: 'wt',
      measure: WTMeasure(
        type: 'wt',
        name: 'wt_measure_name',
        numberOfSteps: 15,
      ),
    ),
  ),
  TaskParams(
    title: 'sts_measure_name',
    task: StsTask(
      identifier: 'sts_30s',
      measure: StsMeasure(
        type: 'sts_30s',
        name: '30s Sit-to-Stand',
        duration: const Duration(seconds: 30),
      ),
    ),
  ),
  TaskParams(title: 'tug_measure_name', task: TugTask(identifier: 'tug')),
  TaskParams(
    title: 'srom_measure_flexion',
    task: FlexionTask(
      identifier: 'srom_flexion',
      measure: SromMeasure(
        type: 'srom_flexion',
        name: 'srom_measure_flexion',
        duration: const Duration(seconds: 10),
      ),
    ),
  ),
  TaskParams(
    title: 'srom_measure_extension',
    task: ExtensionTask(
      identifier: 'srom_extension',
      measure: SromMeasure(
        type: 'srom_extension',
        name: 'srom_measure_extension',
        duration: const Duration(seconds: 10),
      ),
    ),
  ),
  TaskParams(
    title: 'srom_measure_abduction',
    task: AbductionTask(
      identifier: 'srom_abduction',
      measure: SromMeasure(
        type: 'srom_abduction',
        name: 'srom_measure_abduction',
        duration: const Duration(seconds: 10),
      ),
    ),
  ),
  TaskParams(
    title: 'srom_measure_rotation_internal',
    task: InternalRotationTask(
      identifier: 'srom_internal_rotation',
      measure: SromMeasure(
        type: 'srom_internal_rotation',
        name: 'srom_measure_internal_rotation',
        duration: const Duration(seconds: 10),
      ),
    ),
  ),
  TaskParams(
    title: 'srom_measure_rotation_external',
    task: ExternalRotationTask(
      identifier: 'srom_external_rotation',
      measure: SromMeasure(
        type: 'srom_external_rotation',
        name: 'srom_measure_external_rotation',
        duration: const Duration(seconds: 10),
      ),
    ),
  ),
];

class MainApp extends StatelessWidget {
  const MainApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      supportedLocales: const [Locale('en'), Locale('da'), Locale('hu')],
      localizationsDelegates: [
        // Research Package (RP) and Mobility Package (MP) translations.
        // Supports translation of both the RP and MP specific text as well as
        // app-specific text.
        // Read more about localization at https://carp.cachet.dk/localization/
        RPLocalizations.delegate,
        MPLocalizations.delegate,

        // Built-in localization of basic text for Cupertino widgets
        GlobalCupertinoLocalizations.delegate,
        // Built-in localization of basic text for Material widgets
        GlobalMaterialLocalizations.delegate,
        // Built-in localization for text direction LTR/RTL
        GlobalWidgetsLocalizations.delegate,
      ],
      localeResolutionCallback: (locale, supportedLocales) {
        // Check if the current device locale is supported
        for (var supportedLocale in supportedLocales) {
          if (supportedLocale.languageCode == locale!.languageCode) {
            return supportedLocale;
          }
        }
        // if the locale of the device is not supported, use the first one
        // from the list (English, in this case).
        return supportedLocales.first;
      },
      theme: researchPackageTheme,
      darkTheme: researchPackageTheme,
      debugShowCheckedModeBanner: false,
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

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

class HomePageState extends State<HomePage> {
  var moveSenseDevices = <String, MovesenseDevice>{};
  MovesenseDevice? selectedDevice;
  Future<void> _requestPermissions() async {
    await [
      Permission.activityRecognition,
      Permission.sensors,
      Permission.locationWhenInUse,
      Permission.bluetoothScan,
      Permission.bluetoothConnect,
    ].request();

    final activityGranted = await Permission.activityRecognition.status;
    final sensorsGranted = await Permission.sensors.status;
    final locGranted = await Permission.locationWhenInUse.status;
    final bluetoothScanGranted = await Permission.bluetoothScan.status;
    final bluetoothConnectGranted = await Permission.bluetoothConnect.status;

    if (kDebugMode) {
      print('Permissions:');
      print('Activity: $activityGranted');
      print('Sensors: $sensorsGranted');
      print('Location: $locGranted');
      print('Bluetooth Scan: $bluetoothScanGranted');
      print('Bluetooth Connect: $bluetoothConnectGranted');
    }
  }

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

  @override
  Widget build(BuildContext context) {
    var locale = RPLocalizations.of(context);
    return Scaffold(
      appBar: AppBar(
        title: Text(
          locale?.translate('app_title') ?? 'Mobility Package Example',
        ),
        actions: [
          IconButton(
            icon: const Icon(Icons.settings),
            onPressed: () {
              showDialog(
                context: context,
                builder: (context) {
                  return StatefulBuilder(
                    builder: (BuildContext context, StateSetter setState) {
                      Mds.startScan((name, address) {
                        if (kDebugMode) {
                          print('Found device: $name, $address');
                        }
                        if (name != null &&
                            name.toLowerCase().contains("movesense")) {
                          moveSenseDevices.update(
                            name,
                            (value) =>
                                MovesenseDevice(name: name, address: address!),
                            ifAbsent:
                                () => MovesenseDevice(
                                  name: name,
                                  address: address!,
                                ),
                          );
                          setState(() {}); // Update the dialog's UI
                        }
                      });

                      return AlertDialog(
                        title: Text(
                          locale?.translate('movesense_connect_title') ??
                              'Connect Movesense',
                        ),
                        content: Column(
                          mainAxisSize: MainAxisSize.min,
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(
                              locale?.translate('movesense_connect_info') ??
                                  'To use Movesense device, you need to connect it first.',
                            ),
                            const SizedBox(height: 8.0),
                            Text(
                              locale?.translate('movesense_connect_info2') ??
                                  'Make sure Bluetooth is enabled and the device is in range.',
                            ),
                            SizedBox(
                              width: MediaQuery.of(context).size.width * 0.75,
                              height: MediaQuery.of(context).size.height * 0.3,
                              child: ListView.builder(
                                shrinkWrap: true,
                                itemCount: moveSenseDevices.length,
                                itemBuilder: (context, index) {
                                  var device =
                                      moveSenseDevices.entries.toList()[index];
                                  return ListTile(
                                    title: Text(device.value.name),
                                    subtitle: Text(device.value.address),
                                    trailing:
                                        device.value.address ==
                                                selectedDevice?.address
                                            ? const Icon(
                                              Icons.check,
                                              color: Colors.green,
                                            )
                                            : null,
                                    onTap: () {
                                      Mds.stopScan();
                                      Mds.connect(
                                        device.value.address,
                                        (serial) {
                                          selectedDevice = device.value;
                                          MovesenseSensor().connectedSerial =
                                              serial;
                                          setState(() {});
                                          ScaffoldMessenger.of(
                                            context,
                                          ).showSnackBar(
                                            SnackBar(
                                              content: Text(
                                                '${locale?.translate('movesense_connected') ?? 'Connected to:'} ${device.value.name}',
                                              ),
                                            ),
                                          );
                                        },
                                        () {
                                          selectedDevice = null;
                                          setState(() {});
                                          MovesenseSensor().connectedSerial =
                                              null;
                                          ScaffoldMessenger.of(
                                            context,
                                          ).showSnackBar(
                                            SnackBar(
                                              content: Text(
                                                '${locale?.translate('movesense_disconnected') ?? 'Disconnected from'} ${device.value.name}',
                                              ),
                                            ),
                                          );
                                        },
                                        (error) {
                                          ScaffoldMessenger.of(
                                            context,
                                          ).showSnackBar(
                                            SnackBar(
                                              content: Text(
                                                '${locale?.translate('movesense_connect_error') ?? 'Failed to connect:'} $error',
                                              ),
                                            ),
                                          );
                                        },
                                        null,
                                      );
                                    },
                                  );
                                },
                              ),
                            ),
                          ],
                        ),
                        actions: [
                          TextButton(
                            child: Text(locale?.translate('close') ?? 'Close'),
                            onPressed: () {
                              Mds.stopScan();
                              Navigator.of(context).pop();
                            },
                          ),
                        ],
                      );
                    },
                  );
                },
              );
            },
          ),
        ],
      ),
      body: ListView.builder(
        itemCount: buttons.length,
        itemBuilder: (context, index) {
          return Padding(
            padding: const EdgeInsets.all(8.0),
            child: ElevatedButton(
              onPressed: () {
                Navigator.of(context).push(
                  MaterialPageRoute(
                    builder: (context) => TaskScreen(task: buttons[index].task),
                  ),
                );
              },
              child: Text(
                locale?.translate(buttons[index].title) ?? buttons[index].title,
                textAlign: TextAlign.center,
              ),
            ),
          );
        },
      ),
    );
  }
}

class TaskParams {
  final String title;
  final RPOrderedTask task;

  TaskParams({required this.title, required this.task});
}

class TaskScreen extends StatelessWidget {
  final RPOrderedTask task;

  const TaskScreen({super.key, required this.task});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: RPUITask(
        key: UniqueKey(),
        task: task,
        onSubmit:
            (p0) => print(
              "Submitted result: ${toJsonString(p0.results.values.last)}",
            ),
        onCancel: (result) {
          if (result?.results.isEmpty ?? true) {
            return;
          }
          print(
            "Cancelled result: ${toJsonString(result?.results.values.last)}",
          );
        },
      ),
    );
  }
}

class MovesenseDevice {
  final String name;
  final String address;

  MovesenseDevice({required this.name, required this.address});
  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    if (other.runtimeType != runtimeType) return false;
    return other is MovesenseDevice &&
        other.name == name &&
        other.address == address;
  }

  @override
  int get hashCode => super.hashCode ^ name.hashCode ^ address.hashCode;
}
0
likes
140
points
35
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter package for CARP mobility data collection and processing.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

async, carp_serializable, fl_chart, flutter, flutter_ringtone_player, flutter_svg, json_annotation, mdsflutter, research_package, sensors_plus, video_player

More

Packages that depend on carp_mobility_package