Line data Source code
1 : // @license 2 : // Copyright (c) 2019 - 2021 Dr. Gabriel Gatzsche. All Rights Reserved. 3 : // 4 : // Use of this source code is governed by terms that can be 5 : // found in the LICENSE file in the root of this repository. 6 : 7 : import 'dart:async'; 8 : 9 : /// Represents a value of Type T in the memory. 10 : class GgValue<T> { 11 : // ........................................................................... 12 : /// - [seed] The initial seed of the value. 13 : /// - If [spam] is true, each change of the value will be added to the 14 : /// stream. 15 : /// - If [spam] is false, updates of the value are scheduled as micro 16 : /// tasks. New updates are not added until the last update has been delivered. 17 : /// Only the last set value will be delivered. 18 : /// - [transform] allows you to keep value in a given range or transform it. 19 : /// - [parse] is needed when [T] is not [String], [int], [double] or [bool]. 20 : /// It converts a string into [T]. 21 : /// - [toString] is needed when [T] is not [String], [int], [double] or [bool]. 22 : /// It converts the value into a [String]. 23 1 : GgValue({ 24 : required T seed, 25 : this.spam = false, 26 : this.compare, 27 : this.transform, 28 : T Function(String)? parse, 29 : String Function(T)? toString, 30 : }) : _value = seed, 31 : _parse = parse, 32 : _toString = toString { 33 1 : _initController(); 34 1 : _checkType(); 35 : } 36 : 37 : // ........................................................................... 38 : /// Sets the value and triggers an update on the stream. 39 1 : set value(T value) { 40 2 : if (value == _value) { 41 : return; 42 : } 43 : 44 4 : if (compare != null && compare!(value, _value)) { 45 : return; 46 : } 47 : 48 4 : _value = transform == null ? value : transform!(value); 49 : 50 1 : if (spam) { 51 3 : _controller.add(_value); 52 1 : } else if (!_isAlreadyTriggered) { 53 1 : _isAlreadyTriggered = true; 54 2 : scheduleMicrotask(() { 55 1 : _isAlreadyTriggered = false; 56 2 : if (_controller.hasListener) { 57 3 : _controller.add(_value); 58 : } 59 : }); 60 : } 61 : } 62 : 63 : // ........................................................................... 64 : /// Parses [str] and writes the result into value. 65 1 : set stringValue(String str) { 66 1 : if (_parse != null) { 67 3 : value = _parse!.call(str); 68 1 : } else if (T == int) { 69 2 : value = int.parse(str) as T; 70 1 : } else if (T == double) { 71 2 : value = double.parse(str) as T; 72 1 : } else if (T == bool) { 73 1 : switch (str.toLowerCase()) { 74 1 : case 'false': 75 1 : case '0': 76 1 : case 'no': 77 1 : value = false as T; 78 : break; 79 1 : case 'true': 80 1 : case '1': 81 1 : case 'yes': 82 1 : value = true as T; 83 : } 84 : } else { 85 1 : value = str as T; 86 : } 87 : } 88 : 89 : // ........................................................................... 90 : /// Returns the [value] as [String]. 91 1 : String get stringValue { 92 1 : if (_toString != null) { 93 3 : return _toString!.call(_value); 94 1 : } else if (T == String) { 95 1 : return _value as String; 96 1 : } else if (T == bool) { 97 1 : return (_value as bool) ? 'true' : 'false'; 98 : } else { 99 2 : return _value.toString(); 100 : } 101 : } 102 : 103 : // ........................................................................... 104 : /// Allows reducing the number of updates delivered when the value is changed 105 : /// multiple times. 106 : /// 107 : /// - If [spam] is true, each change of the value will be added to the stream. 108 : /// - If [spam] is false, updates of the value are scheduled as micro tasks. 109 : /// New updates are not added until the last update has been delivered. 110 : /// Only the last set value will be delivered. 111 : bool spam; 112 : 113 : // ........................................................................... 114 : /// If T is not a String or num, this function is used to parse a string 115 : 116 : // ........................................................................... 117 : /// Returns the value 118 2 : T get value => _value; 119 : 120 : /// Returns a stream informing about changes on the value 121 3 : Stream<T> get stream => _controller.stream; 122 : 123 : // ........................................................................... 124 : /// Call this method when the value is about to be released. 125 1 : void dispose() { 126 5 : _dispose.reversed.forEach((e) => e()); 127 : } 128 : 129 : // ........................................................................... 130 : /// Is used to check if the value assigned is valid. 131 : final T Function(T)? transform; 132 : 133 : // ........................................................................... 134 : /// This operator compares to GgValue objects based on the value. When given, 135 : //the [compare] function is used to make the comparison. 136 1 : @override 137 : bool operator ==(Object other) => 138 : identical(this, other) || 139 1 : other is GgValue<T> && 140 1 : ((compare != null && compare!(_value, other._value)) || 141 3 : _value == other._value); 142 : 143 : // ........................................................................... 144 : /// The hashcode of a GgValue is calculated based on the value. 145 1 : @override 146 2 : int get hashCode => _value.hashCode; 147 : 148 : // ###################### 149 : // Private 150 : // ###################### 151 : 152 : final List<Function()> _dispose = []; 153 : 154 : // ........................................................................... 155 1 : void _checkType() { 156 1 : _checkParseMethodNeeded(); 157 1 : _checkToStringMethodNeeded(); 158 : } 159 : 160 : // ........................................................................... 161 1 : void _checkParseMethodNeeded() { 162 4 : if (T != String && T != double && T != int && T != bool) { 163 1 : if (_parse == null) { 164 1 : throw ArgumentError( 165 2 : 'Missing "parse" method for unknown type "${T.toString()}".'); 166 : } 167 : } 168 : } 169 : 170 : // ........................................................................... 171 1 : void _checkToStringMethodNeeded() { 172 4 : if (T != String && T != double && T != int && T != bool) { 173 1 : if (_toString == null) { 174 1 : throw ArgumentError( 175 2 : 'Missing "toString" method for unknown type "${T.toString()}".'); 176 : } 177 : } 178 : } 179 : 180 : // ........................................................................... 181 : final StreamController<T> _controller = StreamController<T>.broadcast(); 182 1 : void _initController() { 183 5 : _dispose.add(() => _controller.close()); 184 : } 185 : 186 : // ........................................................................... 187 : T _value; 188 : 189 : // ........................................................................... 190 : bool _isAlreadyTriggered = false; 191 : 192 : // ........................................................................... 193 : final T Function(String)? _parse; 194 : 195 : // ........................................................................... 196 : final String Function(T)? _toString; 197 : 198 : // ........................................................................... 199 : /// Set a custom comparison operator 200 : final bool Function(T a, T b)? compare; 201 : }