main
  1
  2import 'package:flutter/material.dart';
  3import 'package:health_data_store/health_data_store.dart';
  4
  5/// Utility class for converting dynamic values to concrete data types.
  6///
  7/// The functions this class provides interprets dynamic values as values of the data type. This makes them useful for
  8/// contexts in which user generated data needs to be parsed.
  9///
 10/// An example for this are boolean fields in json data. Users could write `true` which will be converted to a boolean
 11/// automatically and can be just checked with the `is boolean` condition, but the user may also write `"true"` or 1
 12/// which are equally valid but would not get converted automatically.
 13class ConvertUtil {
 14  static bool? parseBool(value) {
 15    if (value is bool) return value;
 16    if (parseString(value)?.toLowerCase() == 'true' || parseInt(value) == 1) return true;
 17    if (parseString(value)?.toLowerCase() == 'false' || parseInt(value) == 0) return false;
 18    return null;
 19  }
 20
 21  static int? parseInt(value) {
 22    if (value is int) return value;
 23    if (value is double) return _isInt(value);
 24    if (value is String) return int.tryParse(value) ?? _isInt(double.tryParse(value));
 25    return null;
 26  }
 27
 28  static int? _isInt(double? value) {
 29    if (value?.toInt() == value) return value?.toInt();
 30    return null;
 31  }
 32
 33  static double? parseDouble(value) {
 34    if (value is double) return value;
 35    if (value is int) return value.toDouble();
 36    if (value is String) return double.tryParse(value);
 37    return null;
 38  }
 39
 40  static String? parseString(value) {
 41    if (value is String) return value;
 42    if (value is int || value is double || value is bool) return value.toString();
 43    // No check for Object. While this would be convertible to string,
 44    return null;
 45  }
 46
 47  static String serializeLocale(Locale? value) {
 48    if (value == null) return 'NULL';
 49    return value.languageCode;
 50  }
 51
 52  static Locale? parseLocale(value) {
 53    if (value is Locale) return value;
 54    // Should not use parseString, as values that get caught by it can not be locales.
 55    if (value is String && value.toLowerCase() == 'null') return null;
 56    if (value is String) return Locale(value);
 57    return null;
 58  }
 59
 60  static Color? parseColor(value) {
 61    if (value is MaterialColor || value is Color) return value;
 62    if (value == null) return null;
 63
 64    if (parseInt(value) != null) {
 65      return Color(parseInt(value)!);
 66    }
 67    return null;
 68  }
 69
 70  static DateRange? parseRange(start, end) {
 71    final startTimestamp = parseInt(start);
 72    final endTimestamp = parseInt(end);
 73    if (startTimestamp == null || endTimestamp == null) return null;
 74    return DateRange(
 75      start: DateTime.fromMillisecondsSinceEpoch(startTimestamp),
 76      end: DateTime.fromMillisecondsSinceEpoch(endTimestamp),
 77    );
 78  }
 79
 80  /// Example usage: `ConvertUtil.parseList<String>(json['columns'])`
 81  static List<T>? parseList<T>(value) {
 82    if (value is List<T>) return value;
 83    if (value is List<dynamic>) {
 84      final List<T> validValues = [];
 85      for (final v in value) {
 86        if (v is T) validValues.add(v);
 87      }
 88      if (value.length == validValues.length) return validValues;
 89    }
 90    if (value is List && value.isEmpty) return [];
 91    return null;
 92  }
 93
 94  /// Try to recreate the theme mode stored as a integer.
 95  static ThemeMode? parseThemeMode(value) => switch(ConvertUtil.parseInt(value)) {
 96    0 => ThemeMode.system,
 97    1 => ThemeMode.dark,
 98    2 => ThemeMode.light,
 99    _ => null,
100  };
101
102  /// Does its best attempt at parsing a time in arbitrary format.
103  static DateTime? parseTime(dynamic time) {
104    final intTime = parseInt(time);
105    if (intTime != null) {
106      if (intTime.toString().length == 10) { // seconds
107        return DateTime.fromMillisecondsSinceEpoch(intTime * 1000);
108      } else if (intTime.toString().length == 13) {  // milliseconds
109        return DateTime.fromMillisecondsSinceEpoch(intTime);
110      } else if (intTime.toString().length > 13) {  // nanoseconds
111        return DateTime.fromMicrosecondsSinceEpoch(intTime ~/ 1000);
112      }
113    }
114
115    final timeStr = parseString(time);
116    return DateTime.tryParse(timeStr ?? '');
117  }
118}