diff --git a/lib/bike.dart b/lib/bike.dart index aaea9e1..056b056 100644 --- a/lib/bike.dart +++ b/lib/bike.dart @@ -225,11 +225,6 @@ class BikePageState extends ConsumerState { Center( child: Column( children: [ - Text( - "Background Lock tries to keep your settings locked even when the app is closed. It may cause battery drain.", - style: Theme.of(context).textTheme.bodySmall, - textAlign: TextAlign.center, - ), const SizedBox(height: 10), InkWell( onTap: () { @@ -321,6 +316,7 @@ class LightControlWidget extends ConsumerWidget { children: [ Expanded( child: DiscoverCard( + colorIndex: bike.color, title: "Light", metric: bike.light ? "On" : "Off", selected: bike.light, @@ -352,6 +348,7 @@ class ModeControlWidget extends ConsumerWidget { children: [ Expanded( child: DiscoverCard( + colorIndex: bike.color, title: "Mode", metric: bike.viewMode, selected: bike.viewMode == '1' ? false : true, @@ -375,20 +372,33 @@ class BackgroundLockControlWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { var bikeControl = ref.watch(bikeProvider(bike.id).notifier); - return Padding( - padding: const EdgeInsets.only(top: 20.0), - child: DiscoverCard( - title: "Background Lock", - metric: bike.modeLock ? "On" : "Off", - selected: bike.modeLock, - onTap: () async { - await Permission.notification.request(); - if (Platform.isAndroid) { - await FlutterForegroundTask.requestIgnoreBatteryOptimization(); - } - bikeControl.toggleBackgroundLock(); - }, - ), + return Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 20.0), + child: DiscoverCard( + title: "Background Lock", + metric: bike.modeLock ? "On" : "Off", + selected: bike.modeLock, + colorIndex: bike.color, + onTap: () async { + await Permission.notification.request(); + if (Platform.isAndroid) { + await FlutterForegroundTask.requestIgnoreBatteryOptimization(); + } + bikeControl.toggleBackgroundLock(); + }, + ), + ), + const SizedBox( + height: 10, + ), + Text( + "Background Lock tries to keep your settings locked even when the app is closed. It may cause battery drain.", + style: Theme.of(context).textTheme.bodySmall, + textAlign: TextAlign.center, + ), + ], ); } } @@ -406,6 +416,7 @@ class AssistControlWidget extends ConsumerWidget { children: [ Expanded( child: DiscoverCard( + colorIndex: bike.color, title: "Assist", metric: bike.assist.toString(), selected: bike.assist == 0 ? false : true, diff --git a/lib/colors.dart b/lib/colors.dart new file mode 100644 index 0000000..428e476 --- /dev/null +++ b/lib/colors.dart @@ -0,0 +1,36 @@ +const _colors = [ + (start: 0xff441DFC, end: 0xff4E81EB), + (start: 0xff2E3192, end: 0xff1BFFFF), + (start: 0xffD4145A, end: 0xffFBB03B), + (start: 0xff009245, end: 0xffFCEE21), + (start: 0xff662D8C, end: 0xffED1E79), + (start: 0xffEE9CA7, end: 0xffFFDDE1), + (start: 0xff614385, end: 0xff516395), + (start: 0xff02AABD, end: 0xff00CDAC), + (start: 0xffFF512F, end: 0xffDD2476), + (start: 0xffFF5F6D, end: 0xffFFC371), + (start: 0xff11998E, end: 0xff38EF7D), + (start: 0xffC6EA8D, end: 0xffFE90AF), + (start: 0xffEA8D8D, end: 0xffA890FE), + (start: 0xffD8B5FF, end: 0xff1EAE98), + (start: 0xffFF61D2, end: 0xffFE9090), + (start: 0xffBFF098, end: 0xff6FD6FF), + (start: 0xff4E65FF, end: 0xff92EFFD), + (start: 0xffA9F1DF, end: 0xffFFBBBB), + (start: 0xffC33764, end: 0xff1D2671), + (start: 0xff93A5CF, end: 0xffE4EfE9), + (start: 0xff868F96, end: 0xff596164), + (start: 0xff09203F, end: 0xff537895), + (start: 0xffFFECD2, end: 0xffFCB69F), + (start: 0xffA1C4FD, end: 0xffC2E9FB), + (start: 0xff764BA2, end: 0xff667EEA), + (start: 0xffFDFCFB, end: 0xffE2D1C3) +]; + +getColor(int index) { + return _colors[index]; +} + +getColorList() { + return _colors.map((e) => (start: e.start, end: e.end)).toList(); +} diff --git a/lib/edit_bike.dart b/lib/edit_bike.dart index 2ef955f..dc1f67c 100644 --- a/lib/edit_bike.dart +++ b/lib/edit_bike.dart @@ -3,6 +3,7 @@ import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:superduper/bike.dart'; +import 'package:superduper/colors.dart'; show(BuildContext context, BikeState bike) { showModalBottomSheet( @@ -76,9 +77,16 @@ class CompleteForm extends ConsumerStatefulWidget { class _CompleteFormState extends ConsumerState { final _formKey = GlobalKey(); + late int _selectedColorIndex; var genderOptions = ['Male', 'Female', 'Other']; + @override + void initState() { + super.initState(); + _selectedColorIndex = widget.bike.color; + } + Future _showMyDialog() async { return showDialog( context: context, @@ -120,6 +128,7 @@ class _CompleteFormState extends ConsumerState { @override Widget build(BuildContext context) { var bikeNotifier = ref.watch(bikeProvider(widget.bike.id).notifier); + final colors = getColorList(); return Padding( padding: const EdgeInsets.all(10), child: Column( @@ -134,6 +143,7 @@ class _CompleteFormState extends ConsumerState { initialValue: { 'name': widget.bike.name, 'region': widget.bike.region, + 'color': widget.bike.color, }, child: Column( children: [ @@ -162,11 +172,42 @@ class _CompleteFormState extends ConsumerState { )) .toList(), ), + const SizedBox(height: 40), + InkWell( + onTap: () async { + var colorIndex = + await _showColorPicker(context, _selectedColorIndex); + setState(() { + _selectedColorIndex = colorIndex; + }); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(26), + gradient: LinearGradient( + colors: [ + Color(colors[_selectedColorIndex].start), + Color(colors[_selectedColorIndex].end), + ], + begin: Alignment.bottomLeft, + end: Alignment.topRight, + ), + ), + child: Padding( + padding: const EdgeInsets.only( + left: 24, top: 10, bottom: 10, right: 24), + child: Text( + 'Set Color', + style: Theme.of(context).textTheme.bodyMedium, + ), + ), + ), + ), ], ), ), const SizedBox( - height: 40, + height: 30, ), Row( children: [ @@ -200,6 +241,7 @@ class _CompleteFormState extends ConsumerState { bikeNotifier.writeStateData( widget.bike.copyWith( name: _formKey.currentState?.value['name'], + color: _selectedColorIndex, region: _formKey.currentState?.value['region']), saveToBike: false); Navigator.pop(context); @@ -216,3 +258,42 @@ class _CompleteFormState extends ConsumerState { ); } } + +Future _showColorPicker(BuildContext context, int currentIndex) async { + final colors = getColorList(); + final answer = await showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return Container( + child: ListView.builder( + itemCount: colors.length, + itemBuilder: (BuildContext context, int index) { + return ListTile( + title: Container( + height: 50, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(26), + gradient: LinearGradient( + colors: [ + Color(colors[index].start), + Color(colors[index].end), + ], + begin: Alignment.bottomLeft, + end: Alignment.topRight, + ), + ), + ), + onTap: () { + Navigator.pop(context, index); // return the color index + }, + ); + }, + ), + ); + }, + ); + if (answer != null) { + return answer; + } + return currentIndex; +} diff --git a/lib/models.dart b/lib/models.dart index 8a081ba..0e43c6b 100644 --- a/lib/models.dart +++ b/lib/models.dart @@ -23,19 +23,20 @@ class BikeState with _$BikeState { @Assert('mode >= 0') @Assert('assist >= 0') @Assert('assist <= 4') - const factory BikeState({ - required String id, - required int mode, - @Default(false) bool modeLocked, - required bool light, - @Default(false) bool lightLocked, - required int assist, - @Default(false) bool assistLocked, - required String name, - BikeRegion? region, - @Default(false) bool modeLock, - @Default(false) bool selected, - }) = _BikeState; + @Assert('color >= 0') + const factory BikeState( + {required String id, + required int mode, + @Default(false) bool modeLocked, + required bool light, + @Default(false) bool lightLocked, + required int assist, + @Default(false) bool assistLocked, + required String name, + BikeRegion? region, + @Default(false) bool modeLock, + @Default(false) bool selected, + @Default(0) int color}) = _BikeState; factory BikeState.fromJson(Map json) => _$BikeStateFromJson(json); diff --git a/lib/models.freezed.dart b/lib/models.freezed.dart index 2350fab..e61bae2 100644 --- a/lib/models.freezed.dart +++ b/lib/models.freezed.dart @@ -31,6 +31,7 @@ mixin _$BikeState { BikeRegion? get region => throw _privateConstructorUsedError; bool get modeLock => throw _privateConstructorUsedError; bool get selected => throw _privateConstructorUsedError; + int get color => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -54,7 +55,8 @@ abstract class $BikeStateCopyWith<$Res> { String name, BikeRegion? region, bool modeLock, - bool selected}); + bool selected, + int color}); } /// @nodoc @@ -81,6 +83,7 @@ class _$BikeStateCopyWithImpl<$Res, $Val extends BikeState> Object? region = freezed, Object? modeLock = null, Object? selected = null, + Object? color = null, }) { return _then(_value.copyWith( id: null == id @@ -127,6 +130,10 @@ class _$BikeStateCopyWithImpl<$Res, $Val extends BikeState> ? _value.selected : selected // ignore: cast_nullable_to_non_nullable as bool, + color: null == color + ? _value.color + : color // ignore: cast_nullable_to_non_nullable + as int, ) as $Val); } } @@ -150,7 +157,8 @@ abstract class _$$BikeStateImplCopyWith<$Res> String name, BikeRegion? region, bool modeLock, - bool selected}); + bool selected, + int color}); } /// @nodoc @@ -175,6 +183,7 @@ class __$$BikeStateImplCopyWithImpl<$Res> Object? region = freezed, Object? modeLock = null, Object? selected = null, + Object? color = null, }) { return _then(_$BikeStateImpl( id: null == id @@ -221,6 +230,10 @@ class __$$BikeStateImplCopyWithImpl<$Res> ? _value.selected : selected // ignore: cast_nullable_to_non_nullable as bool, + color: null == color + ? _value.color + : color // ignore: cast_nullable_to_non_nullable + as int, )); } } @@ -239,11 +252,13 @@ class _$BikeStateImpl extends _BikeState { required this.name, this.region, this.modeLock = false, - this.selected = false}) + this.selected = false, + this.color = 0}) : assert(mode <= 3), assert(mode >= 0), assert(assist >= 0), assert(assist <= 4), + assert(color >= 0), super._(); factory _$BikeStateImpl.fromJson(Map json) => @@ -276,10 +291,13 @@ class _$BikeStateImpl extends _BikeState { @override @JsonKey() final bool selected; + @override + @JsonKey() + final int color; @override String toString() { - return 'BikeState(id: $id, mode: $mode, modeLocked: $modeLocked, light: $light, lightLocked: $lightLocked, assist: $assist, assistLocked: $assistLocked, name: $name, region: $region, modeLock: $modeLock, selected: $selected)'; + return 'BikeState(id: $id, mode: $mode, modeLocked: $modeLocked, light: $light, lightLocked: $lightLocked, assist: $assist, assistLocked: $assistLocked, name: $name, region: $region, modeLock: $modeLock, selected: $selected, color: $color)'; } @override @@ -302,13 +320,26 @@ class _$BikeStateImpl extends _BikeState { (identical(other.modeLock, modeLock) || other.modeLock == modeLock) && (identical(other.selected, selected) || - other.selected == selected)); + other.selected == selected) && + (identical(other.color, color) || other.color == color)); } @JsonKey(ignore: true) @override - int get hashCode => Object.hash(runtimeType, id, mode, modeLocked, light, - lightLocked, assist, assistLocked, name, region, modeLock, selected); + int get hashCode => Object.hash( + runtimeType, + id, + mode, + modeLocked, + light, + lightLocked, + assist, + assistLocked, + name, + region, + modeLock, + selected, + color); @JsonKey(ignore: true) @override @@ -336,7 +367,8 @@ abstract class _BikeState extends BikeState { required final String name, final BikeRegion? region, final bool modeLock, - final bool selected}) = _$BikeStateImpl; + final bool selected, + final int color}) = _$BikeStateImpl; const _BikeState._() : super._(); factory _BikeState.fromJson(Map json) = @@ -365,6 +397,8 @@ abstract class _BikeState extends BikeState { @override bool get selected; @override + int get color; + @override @JsonKey(ignore: true) _$$BikeStateImplCopyWith<_$BikeStateImpl> get copyWith => throw _privateConstructorUsedError; diff --git a/lib/models.g.dart b/lib/models.g.dart index b000b84..b4deda2 100644 --- a/lib/models.g.dart +++ b/lib/models.g.dart @@ -19,6 +19,7 @@ _$BikeStateImpl _$$BikeStateImplFromJson(Map json) => region: $enumDecodeNullable(_$BikeRegionEnumMap, json['region']), modeLock: json['modeLock'] as bool? ?? false, selected: json['selected'] as bool? ?? false, + color: json['color'] as int? ?? 0, ); Map _$$BikeStateImplToJson(_$BikeStateImpl instance) => @@ -34,6 +35,7 @@ Map _$$BikeStateImplToJson(_$BikeStateImpl instance) => 'region': _$BikeRegionEnumMap[instance.region], 'modeLock': instance.modeLock, 'selected': instance.selected, + 'color': instance.color, }; const _$BikeRegionEnumMap = { diff --git a/lib/widgets.dart b/lib/widgets.dart index c9d65d6..67f38c4 100644 --- a/lib/widgets.dart +++ b/lib/widgets.dart @@ -1,72 +1,11 @@ import 'package:flutter/material.dart'; - -class CategoryBoxes extends StatefulWidget { - final Function(bool isSelected)? onPressed; - final String? text; - - const CategoryBoxes({super.key, this.onPressed, this.text}); - - @override - State createState() => _CategoryBoxesState(); -} - -class _CategoryBoxesState extends State { - bool isSelected = false; - - @override - Widget build(BuildContext context) { - return Center( - child: GestureDetector( - onTap: () { - setState(() { - isSelected = !isSelected; - widget.onPressed!(isSelected); - }); - }, - child: Padding( - padding: const EdgeInsets.only(right: 10, left: 10), - child: Container( - height: 48, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: isSelected - ? const Color(0xff4A80F0) - : const Color(0xff1C2031), - boxShadow: isSelected - ? [ - BoxShadow( - color: const Color(0xff4A80F0).withOpacity(0.3), - offset: const Offset(0, 4), - blurRadius: 20), - ] - : [], - ), - child: Center( - child: Padding( - padding: - const EdgeInsets.symmetric(horizontal: 16, vertical: 13), - child: Text( - widget.text!, - style: const TextStyle( - color: Colors.white, - fontSize: 17, - fontWeight: FontWeight.normal), - ), - ), - ), - ), - ), - ), - ); - } -} +import 'package:superduper/colors.dart'; class DiscoverCard extends StatelessWidget { final String? title; final String? subtitle; final String? metric; - final Color? gradientStartColor; - final Color? gradientEndColor; + final int colorIndex; final double? height; final double? width; final Widget? vectorBottom; @@ -79,8 +18,6 @@ class DiscoverCard extends StatelessWidget { {super.key, this.title, this.subtitle, - this.gradientStartColor, - this.gradientEndColor, this.height, this.width, this.vectorBottom, @@ -89,15 +26,17 @@ class DiscoverCard extends StatelessWidget { this.onLongPress, this.tag, this.metric, + this.colorIndex = 0, this.selected = true}); @override Widget build(BuildContext context) { - var startColor = gradientStartColor; - var endColor = gradientStartColor; + var defaultColors = getColor(colorIndex); + var startColor = Color(defaultColors.start); + var endColor = Color(defaultColors.end); if (!selected) { - startColor = Colors.grey[800]; - endColor = Colors.grey[800]; + startColor = Colors.grey[800]!; + endColor = Colors.grey[800]!; } return Material( color: Colors.transparent, @@ -109,8 +48,8 @@ class DiscoverCard extends StatelessWidget { borderRadius: BorderRadius.circular(26), gradient: LinearGradient( colors: [ - startColor ?? const Color(0xff441DFC), - endColor ?? const Color(0xff4E81EB), + startColor, + endColor, ], begin: Alignment.bottomLeft, end: Alignment.topRight, diff --git a/pubspec.yaml b/pubspec.yaml index 80e7dea..6f88e31 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: "none" version: 0.3.1+33 environment: - sdk: ">=2.18.6 <4.0.0" + sdk: ">=3.2.0 <4.0.0" dependencies: flutter: