diff options
author | Lisandro Pérez Meyer <lpmeyer@ics.com> | 2023-11-14 17:20:58 -0300 |
---|---|---|
committer | Lisandro Pérez Meyer <lpmeyer@ics.com> | 2023-11-14 17:31:12 -0300 |
commit | 70ec8a79a121471a004e7e4c23157d10157e136f (patch) | |
tree | a4f9c0a4fac4e4274ec4324a289b6ef62e1c5653 /lib |
Initial cleanup push.
Based on agldemo2024 on commit 2a5dc04d801134338150c3f6afc67eaa65599763
Disable device preview.
Disable Lottie animation.
The original commit was b3c493c340fcb4bb0a937692838fc830bec3e9ea
but I am just keeping this change, because the json did not really
needed to change. I think.
Signed-off-by: Lisandro Pérez Meyer <lpmeyer@ics.com>
Diffstat (limited to 'lib')
99 files changed, 11558 insertions, 0 deletions
diff --git a/lib/core/constants/colors.dart b/lib/core/constants/colors.dart new file mode 100644 index 0000000..9835029 --- /dev/null +++ b/lib/core/constants/colors.dart @@ -0,0 +1,21 @@ +import 'dart:ui'; + +import '../../export.dart'; + +class AGLDemoColors { + AGLDemoColors._(); + + static const lightGreyColor = Color(0XFFECEDF1); + static const periwinkleColor = Color(0XFFC1D8FF); + static const jordyBlueColor = Color(0XFF82B1FF); + static const neonBlueColor = Color(0XFF2962FF); + static const resolutionBlueColor = Color(0XFF1A237E); + static const lightRedColor = Color(0XFFEF9A9A); + static const yellowColor = Color(0XFFFFDE03); + static const greenColor = Color(0XFF69F0AE); + static const buttonFillEnabledColor = Color(0XFF1C2D92); + static const blueGlowFillColor = Color(0XFFB8D3FF); + static const backgroundInsetColor = Color(0XFF0C1039); + static const gradientBackgroundDarkColor = Color(0XFF0D113F); + static const redProgressStrokeColor = Color(0XFFFF3D00); +} diff --git a/lib/core/constants/constants.dart b/lib/core/constants/constants.dart new file mode 100644 index 0000000..fe8d9f1 --- /dev/null +++ b/lib/core/constants/constants.dart @@ -0,0 +1,11 @@ +import '../../export.dart'; + +const splashWarning = + 'Please use the IVI system responsibly while driving. Keep your attention on the road, and use voice commands or hands-free controls when interacting with the system. Distracted driving can lead to accidents and serious injury. Follow all traffic laws and drive safely.'; +const aglVeriosn = 'AGL 16.0.2 (pike)'; +const kernelVeriosn = 'Kernel: 5.10.41.-yocto-standard'; +const maxFuelLevel = 55.0; +const maxSpeed = 240.0; +const maxRpm = 8000; +final GlobalKey<ScaffoldState> homeScaffoldKey = GlobalKey(); + diff --git a/lib/core/constants/paths.dart b/lib/core/constants/paths.dart new file mode 100644 index 0000000..c3afb99 --- /dev/null +++ b/lib/core/constants/paths.dart @@ -0,0 +1,914 @@ + + +List<String> signals = [ + 'Vehicle.ADAS.ABS.IsEnabled', + 'Vehicle.ADAS.ABS.IsEngaged', + 'Vehicle.ADAS.ABS.IsError', + 'Vehicle.ADAS.ActiveAutonomyLevel', + 'Vehicle.ADAS.CruiseControl.IsActive', + 'Vehicle.ADAS.CruiseControl.IsEnabled', + 'Vehicle.ADAS.CruiseControl.IsError', + 'Vehicle.ADAS.CruiseControl.SpeedSet', + 'Vehicle.ADAS.DMS.IsEnabled', + 'Vehicle.ADAS.DMS.IsError', + 'Vehicle.ADAS.DMS.IsWarning', + 'Vehicle.ADAS.EBA.IsEnabled', + 'Vehicle.ADAS.EBA.IsEngaged', + 'Vehicle.ADAS.EBA.IsError', + 'Vehicle.ADAS.EBD.IsEnabled', + 'Vehicle.ADAS.EBD.IsEngaged', + 'Vehicle.ADAS.EBD.IsError', + 'Vehicle.ADAS.ESC.IsEnabled', + 'Vehicle.ADAS.ESC.IsEngaged', + 'Vehicle.ADAS.ESC.IsError', + 'Vehicle.ADAS.ESC.IsStrongCrossWindDetected', + 'Vehicle.ADAS.ESC.RoadFriction.LowerBound', + 'Vehicle.ADAS.ESC.RoadFriction.MostProbable', + 'Vehicle.ADAS.ESC.RoadFriction.UpperBound', + 'Vehicle.ADAS.LaneDepartureDetection.IsEnabled', + 'Vehicle.ADAS.LaneDepartureDetection.IsError', + 'Vehicle.ADAS.LaneDepartureDetection.IsWarning', + 'Vehicle.ADAS.ObstacleDetection.IsEnabled', + 'Vehicle.ADAS.ObstacleDetection.IsError', + 'Vehicle.ADAS.ObstacleDetection.IsWarning', + 'Vehicle.ADAS.PowerOptimizeLevel', + 'Vehicle.ADAS.SupportedAutonomyLevel', + 'Vehicle.ADAS.TCS.IsEnabled', + 'Vehicle.ADAS.TCS.IsEngaged', + 'Vehicle.ADAS.TCS.IsError', + 'Vehicle.Acceleration.Lateral', + 'Vehicle.Acceleration.Longitudinal', + 'Vehicle.Acceleration.Vertical', + 'Vehicle.AngularVelocity.Pitch', + 'Vehicle.AngularVelocity.Roll', + 'Vehicle.AngularVelocity.Yaw', + 'Vehicle.AverageSpeed', + 'Vehicle.Body.BodyType', + 'Vehicle.Body.Hood.IsOpen', + 'Vehicle.Body.Horn.IsActive', + 'Vehicle.Body.Lights.Backup.IsDefect', + 'Vehicle.Body.Lights.Backup.IsOn', + 'Vehicle.Body.Lights.Beam.High.IsDefect', + 'Vehicle.Body.Lights.Beam.High.IsOn', + 'Vehicle.Body.Lights.Beam.Low.IsDefect', + 'Vehicle.Body.Lights.Beam.Low.IsOn', + 'Vehicle.Body.Lights.Brake.IsActive', + 'Vehicle.Body.Lights.Brake.IsDefect', + 'Vehicle.Body.Lights.DirectionIndicator.Left.IsDefect', + 'Vehicle.Body.Lights.DirectionIndicator.Left.IsSignaling', + 'Vehicle.Body.Lights.DirectionIndicator.Right.IsDefect', + 'Vehicle.Body.Lights.DirectionIndicator.Right.IsSignaling', + 'Vehicle.Body.Lights.Fog.Front.IsDefect', + 'Vehicle.Body.Lights.Fog.Front.IsOn', + 'Vehicle.Body.Lights.Fog.Rear.IsDefect', + 'Vehicle.Body.Lights.Fog.Rear.IsOn', + 'Vehicle.Body.Lights.Hazard.IsDefect', + 'Vehicle.Body.Lights.Hazard.IsSignaling', + 'Vehicle.Body.Lights.IsHighBeamSwitchOn', + 'Vehicle.Body.Lights.LicensePlate.IsDefect', + 'Vehicle.Body.Lights.LicensePlate.IsOn', + 'Vehicle.Body.Lights.LightSwitch', + 'Vehicle.Body.Lights.Parking.IsDefect', + 'Vehicle.Body.Lights.Parking.IsOn', + 'Vehicle.Body.Lights.Running.IsDefect', + 'Vehicle.Body.Lights.Running.IsOn', + 'Vehicle.Body.Mirrors.DriverSide.IsHeatingOn', + 'Vehicle.Body.Mirrors.DriverSide.Pan', + 'Vehicle.Body.Mirrors.DriverSide.Tilt', + 'Vehicle.Body.Mirrors.PassengerSide.IsHeatingOn', + 'Vehicle.Body.Mirrors.PassengerSide.Pan', + 'Vehicle.Body.Mirrors.PassengerSide.Tilt', + 'Vehicle.Body.PowerOptimizeLevel', + 'Vehicle.Body.Raindetection.Intensity', + 'Vehicle.Body.RearMainSpoilerPosition', + 'Vehicle.Body.RefuelPosition', + 'Vehicle.Body.Trunk.Front.IsLightOn', + 'Vehicle.Body.Trunk.Front.IsLocked', + 'Vehicle.Body.Trunk.Front.IsOpen', + 'Vehicle.Body.Trunk.Rear.IsLightOn', + 'Vehicle.Body.Trunk.Rear.IsLocked', + 'Vehicle.Body.Trunk.Rear.IsOpen', + 'Vehicle.Body.Windshield.Front.IsHeatingOn', + 'Vehicle.Body.Windshield.Front.WasherFluid.IsLevelLow', + 'Vehicle.Body.Windshield.Front.WasherFluid.Level', + 'Vehicle.Body.Windshield.Front.Wiping.Intensity', + 'Vehicle.Body.Windshield.Front.Wiping.IsWipersWorn', + 'Vehicle.Body.Windshield.Front.Wiping.Mode', + 'Vehicle.Body.Windshield.Front.Wiping.System.ActualPosition', + 'Vehicle.Body.Windshield.Front.Wiping.System.DriveCurrent', + 'Vehicle.Body.Windshield.Front.Wiping.System.Frequency', + 'Vehicle.Body.Windshield.Front.Wiping.System.IsBlocked', + 'Vehicle.Body.Windshield.Front.Wiping.System.IsEndingWipeCycle', + 'Vehicle.Body.Windshield.Front.Wiping.System.IsOverheated', + 'Vehicle.Body.Windshield.Front.Wiping.System.IsPositionReached', + 'Vehicle.Body.Windshield.Front.Wiping.System.IsWiperError', + 'Vehicle.Body.Windshield.Front.Wiping.System.IsWiping', + 'Vehicle.Body.Windshield.Front.Wiping.System.Mode', + 'Vehicle.Body.Windshield.Front.Wiping.System.TargetPosition', + 'Vehicle.Body.Windshield.Front.Wiping.WiperWear', + 'Vehicle.Body.Windshield.Rear.IsHeatingOn', + 'Vehicle.Body.Windshield.Rear.WasherFluid.IsLevelLow', + 'Vehicle.Body.Windshield.Rear.WasherFluid.Level', + 'Vehicle.Body.Windshield.Rear.Wiping.Intensity', + 'Vehicle.Body.Windshield.Rear.Wiping.IsWipersWorn', + 'Vehicle.Body.Windshield.Rear.Wiping.Mode', + 'Vehicle.Body.Windshield.Rear.Wiping.System.ActualPosition', + 'Vehicle.Body.Windshield.Rear.Wiping.System.DriveCurrent', + 'Vehicle.Body.Windshield.Rear.Wiping.System.Frequency', + 'Vehicle.Body.Windshield.Rear.Wiping.System.IsBlocked', + 'Vehicle.Body.Windshield.Rear.Wiping.System.IsEndingWipeCycle', + 'Vehicle.Body.Windshield.Rear.Wiping.System.IsOverheated', + 'Vehicle.Body.Windshield.Rear.Wiping.System.IsPositionReached', + 'Vehicle.Body.Windshield.Rear.Wiping.System.IsWiperError', + 'Vehicle.Body.Windshield.Rear.Wiping.System.IsWiping', + 'Vehicle.Body.Windshield.Rear.Wiping.System.Mode', + 'Vehicle.Body.Windshield.Rear.Wiping.System.TargetPosition', + 'Vehicle.Body.Windshield.Rear.Wiping.WiperWear', + 'Vehicle.Cabin.Convertible.Status', + 'Vehicle.Cabin.Door.Row1.DriverSide.IsChildLockActive', + 'Vehicle.Cabin.Door.Row1.DriverSide.IsLocked', + 'Vehicle.Cabin.Door.Row1.DriverSide.IsOpen', + 'Vehicle.Cabin.Door.Row1.DriverSide.Shade.Position', + 'Vehicle.Cabin.Door.Row1.DriverSide.Shade.Switch', + 'Vehicle.Cabin.Door.Row1.DriverSide.Window.IsOpen', + 'Vehicle.Cabin.Door.Row1.DriverSide.Window.Position', + 'Vehicle.Cabin.Door.Row1.DriverSide.Window.Switch', + 'Vehicle.Cabin.Door.Row1.PassengerSide.IsChildLockActive', + 'Vehicle.Cabin.Door.Row1.PassengerSide.IsLocked', + 'Vehicle.Cabin.Door.Row1.PassengerSide.IsOpen', + 'Vehicle.Cabin.Door.Row1.PassengerSide.Shade.Position', + 'Vehicle.Cabin.Door.Row1.PassengerSide.Shade.Switch', + 'Vehicle.Cabin.Door.Row1.PassengerSide.Window.IsOpen', + 'Vehicle.Cabin.Door.Row1.PassengerSide.Window.Position', + 'Vehicle.Cabin.Door.Row1.PassengerSide.Window.Switch', + 'Vehicle.Cabin.Door.Row2.DriverSide.IsChildLockActive', + 'Vehicle.Cabin.Door.Row2.DriverSide.IsLocked', + 'Vehicle.Cabin.Door.Row2.DriverSide.IsOpen', + 'Vehicle.Cabin.Door.Row2.DriverSide.Shade.Position', + 'Vehicle.Cabin.Door.Row2.DriverSide.Shade.Switch', + 'Vehicle.Cabin.Door.Row2.DriverSide.Window.IsOpen', + 'Vehicle.Cabin.Door.Row2.DriverSide.Window.Position', + 'Vehicle.Cabin.Door.Row2.DriverSide.Window.Switch', + 'Vehicle.Cabin.Door.Row2.PassengerSide.IsChildLockActive', + 'Vehicle.Cabin.Door.Row2.PassengerSide.IsLocked', + 'Vehicle.Cabin.Door.Row2.PassengerSide.IsOpen', + 'Vehicle.Cabin.Door.Row2.PassengerSide.Shade.Position', + 'Vehicle.Cabin.Door.Row2.PassengerSide.Shade.Switch', + 'Vehicle.Cabin.Door.Row2.PassengerSide.Window.IsOpen', + 'Vehicle.Cabin.Door.Row2.PassengerSide.Window.Position', + 'Vehicle.Cabin.Door.Row2.PassengerSide.Window.Switch', + 'Vehicle.Cabin.DoorCount', + 'Vehicle.Cabin.DriverPosition', + 'Vehicle.Cabin.HVAC.AmbientAirTemperature', + 'Vehicle.Cabin.HVAC.IsAirConditioningActive', + 'Vehicle.Cabin.HVAC.IsFrontDefrosterActive', + 'Vehicle.Cabin.HVAC.IsRearDefrosterActive', + 'Vehicle.Cabin.HVAC.IsRecirculationActive', + 'Vehicle.Cabin.HVAC.PowerOptimizeLevel', + 'Vehicle.Cabin.HVAC.Station.Row1.Driver.AirDistribution', + 'Vehicle.Cabin.HVAC.Station.Row1.Driver.FanSpeed', + 'Vehicle.Cabin.HVAC.Station.Row1.Driver.Temperature', + 'Vehicle.Cabin.HVAC.Station.Row1.Passenger.AirDistribution', + 'Vehicle.Cabin.HVAC.Station.Row1.Passenger.FanSpeed', + 'Vehicle.Cabin.HVAC.Station.Row1.Passenger.Temperature', + 'Vehicle.Cabin.HVAC.Station.Row2.Driver.AirDistribution', + 'Vehicle.Cabin.HVAC.Station.Row2.Driver.FanSpeed', + 'Vehicle.Cabin.HVAC.Station.Row2.Driver.Temperature', + 'Vehicle.Cabin.HVAC.Station.Row2.Passenger.AirDistribution', + 'Vehicle.Cabin.HVAC.Station.Row2.Passenger.FanSpeed', + 'Vehicle.Cabin.HVAC.Station.Row2.Passenger.Temperature', + 'Vehicle.Cabin.HVAC.Station.Row3.Driver.AirDistribution', + 'Vehicle.Cabin.HVAC.Station.Row3.Driver.FanSpeed', + 'Vehicle.Cabin.HVAC.Station.Row3.Driver.Temperature', + 'Vehicle.Cabin.HVAC.Station.Row3.Passenger.AirDistribution', + 'Vehicle.Cabin.HVAC.Station.Row3.Passenger.FanSpeed', + 'Vehicle.Cabin.HVAC.Station.Row3.Passenger.Temperature', + 'Vehicle.Cabin.HVAC.Station.Row4.Driver.AirDistribution', + 'Vehicle.Cabin.HVAC.Station.Row4.Driver.FanSpeed', + 'Vehicle.Cabin.HVAC.Station.Row4.Driver.Temperature', + 'Vehicle.Cabin.HVAC.Station.Row4.Passenger.AirDistribution', + 'Vehicle.Cabin.HVAC.Station.Row4.Passenger.FanSpeed', + 'Vehicle.Cabin.HVAC.Station.Row4.Passenger.Temperature', + 'Vehicle.Cabin.Infotainment.HMI.Brightness', + 'Vehicle.Cabin.Infotainment.HMI.CurrentLanguage', + 'Vehicle.Cabin.Infotainment.HMI.DateFormat', + 'Vehicle.Cabin.Infotainment.HMI.DayNightMode', + 'Vehicle.Cabin.Infotainment.HMI.DisplayOffDuration', + 'Vehicle.Cabin.Infotainment.HMI.DistanceUnit', + 'Vehicle.Cabin.Infotainment.HMI.EVEconomyUnits', + 'Vehicle.Cabin.Infotainment.HMI.FontSize', + 'Vehicle.Cabin.Infotainment.HMI.FuelEconomyUnits', + 'Vehicle.Cabin.Infotainment.HMI.FuelVolumeUnit', + 'Vehicle.Cabin.Infotainment.HMI.IsScreenAlwaysOn', + 'Vehicle.Cabin.Infotainment.HMI.LastActionTime', + 'Vehicle.Cabin.Infotainment.HMI.TemperatureUnit', + 'Vehicle.Cabin.Infotainment.HMI.TimeFormat', + 'Vehicle.Cabin.Infotainment.HMI.TirePressureUnit', + 'Vehicle.Cabin.Infotainment.Media.Action', + 'Vehicle.Cabin.Infotainment.Media.DeclinedURI', + 'Vehicle.Cabin.Infotainment.Media.Played.Album', + 'Vehicle.Cabin.Infotainment.Media.Played.Artist', + 'Vehicle.Cabin.Infotainment.Media.Played.PlaybackRate', + 'Vehicle.Cabin.Infotainment.Media.Played.Source', + 'Vehicle.Cabin.Infotainment.Media.Played.Track', + 'Vehicle.Cabin.Infotainment.Media.Played.URI', + 'Vehicle.Cabin.Infotainment.Media.SelectedURI', + 'Vehicle.Cabin.Infotainment.Media.Volume', + 'Vehicle.Cabin.Infotainment.Navigation.DestinationSet.Latitude', + 'Vehicle.Cabin.Infotainment.Navigation.DestinationSet.Longitude', + 'Vehicle.Cabin.Infotainment.Navigation.GuidanceVoice', + 'Vehicle.Cabin.Infotainment.Navigation.Mute', + 'Vehicle.Cabin.Infotainment.Navigation.Volume', + 'Vehicle.Cabin.Infotainment.PowerOptimizeLevel', + 'Vehicle.Cabin.Infotainment.SmartphoneProjection.Active', + 'Vehicle.Cabin.Infotainment.SmartphoneProjection.Source', + 'Vehicle.Cabin.Infotainment.SmartphoneProjection.SupportedMode', + 'Vehicle.Cabin.IsWindowChildLockEngaged', + 'Vehicle.Cabin.Light.AmbientLight.Row1.DriverSide.Color', + 'Vehicle.Cabin.Light.AmbientLight.Row1.DriverSide.Intensity', + 'Vehicle.Cabin.Light.AmbientLight.Row1.DriverSide.IsLightOn', + 'Vehicle.Cabin.Light.AmbientLight.Row1.PassengerSide.Color', + 'Vehicle.Cabin.Light.AmbientLight.Row1.PassengerSide.Intensity', + 'Vehicle.Cabin.Light.AmbientLight.Row1.PassengerSide.IsLightOn', + 'Vehicle.Cabin.Light.AmbientLight.Row2.DriverSide.Color', + 'Vehicle.Cabin.Light.AmbientLight.Row2.DriverSide.Intensity', + 'Vehicle.Cabin.Light.AmbientLight.Row2.DriverSide.IsLightOn', + 'Vehicle.Cabin.Light.AmbientLight.Row2.PassengerSide.Color', + 'Vehicle.Cabin.Light.AmbientLight.Row2.PassengerSide.Intensity', + 'Vehicle.Cabin.Light.AmbientLight.Row2.PassengerSide.IsLightOn', + 'Vehicle.Cabin.Light.InteractiveLightBar.Color', + 'Vehicle.Cabin.Light.InteractiveLightBar.Effect', + 'Vehicle.Cabin.Light.InteractiveLightBar.Intensity', + 'Vehicle.Cabin.Light.InteractiveLightBar.IsLightOn', + 'Vehicle.Cabin.Light.IsDomeOn', + 'Vehicle.Cabin.Light.IsGloveBoxOn', + 'Vehicle.Cabin.Light.PerceivedAmbientLight', + 'Vehicle.Cabin.Light.Spotlight.Row1.DriverSide.Color', + 'Vehicle.Cabin.Light.Spotlight.Row1.DriverSide.Intensity', + 'Vehicle.Cabin.Light.Spotlight.Row1.DriverSide.IsLightOn', + 'Vehicle.Cabin.Light.Spotlight.Row1.PassengerSide.Color', + 'Vehicle.Cabin.Light.Spotlight.Row1.PassengerSide.Intensity', + 'Vehicle.Cabin.Light.Spotlight.Row1.PassengerSide.IsLightOn', + 'Vehicle.Cabin.Light.Spotlight.Row2.DriverSide.Color', + 'Vehicle.Cabin.Light.Spotlight.Row2.DriverSide.Intensity', + 'Vehicle.Cabin.Light.Spotlight.Row2.DriverSide.IsLightOn', + 'Vehicle.Cabin.Light.Spotlight.Row2.PassengerSide.Color', + 'Vehicle.Cabin.Light.Spotlight.Row2.PassengerSide.Intensity', + 'Vehicle.Cabin.Light.Spotlight.Row2.PassengerSide.IsLightOn', + 'Vehicle.Cabin.Light.Spotlight.Row3.DriverSide.Color', + 'Vehicle.Cabin.Light.Spotlight.Row3.DriverSide.Intensity', + 'Vehicle.Cabin.Light.Spotlight.Row3.DriverSide.IsLightOn', + 'Vehicle.Cabin.Light.Spotlight.Row3.PassengerSide.Color', + 'Vehicle.Cabin.Light.Spotlight.Row3.PassengerSide.Intensity', + 'Vehicle.Cabin.Light.Spotlight.Row3.PassengerSide.IsLightOn', + 'Vehicle.Cabin.Light.Spotlight.Row4.DriverSide.Color', + 'Vehicle.Cabin.Light.Spotlight.Row4.DriverSide.Intensity', + 'Vehicle.Cabin.Light.Spotlight.Row4.DriverSide.IsLightOn', + 'Vehicle.Cabin.Light.Spotlight.Row4.PassengerSide.Color', + 'Vehicle.Cabin.Light.Spotlight.Row4.PassengerSide.Intensity', + 'Vehicle.Cabin.Light.Spotlight.Row4.PassengerSide.IsLightOn', + 'Vehicle.Cabin.PowerOptimizeLevel', + 'Vehicle.Cabin.RearShade.Position', + 'Vehicle.Cabin.RearShade.Switch', + 'Vehicle.Cabin.RearviewMirror.DimmingLevel', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Airbag.IsDeployed', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Backrest.Lumbar.Height', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Backrest.Lumbar.Support', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Backrest.Recline', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Backrest.SideBolster.Support', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Headrest.Angle', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Headrest.Height', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Heating', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Height', + 'Vehicle.Cabin.Seat.Row1.DriverSide.IsBelted', + 'Vehicle.Cabin.Seat.Row1.DriverSide.IsOccupied', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Massage', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Occupant.Identifier.Issuer', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Occupant.Identifier.Subject', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Position', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Seating.Length', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Switch.Backrest.IsReclineBackwardEngaged', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Switch.Backrest.IsReclineForwardEngaged', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Switch.Backrest.Lumbar.IsDownEngaged', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Switch.Backrest.Lumbar.IsLessSupportEngaged', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Switch.Backrest.Lumbar.IsMoreSupportEngaged', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Switch.Backrest.Lumbar.IsUpEngaged', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Switch.Backrest.SideBolster.IsLessSupportEngaged', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Switch.Backrest.SideBolster.IsMoreSupportEngaged', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Switch.Headrest.IsBackwardEngaged', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Switch.Headrest.IsDownEngaged', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Switch.Headrest.IsForwardEngaged', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Switch.Headrest.IsUpEngaged', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Switch.IsBackwardEngaged', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Switch.IsCoolerEngaged', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Switch.IsDownEngaged', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Switch.IsForwardEngaged', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Switch.IsTiltBackwardEngaged', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Switch.IsTiltForwardEngaged', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Switch.IsUpEngaged', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Switch.IsWarmerEngaged', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Switch.Massage.IsDecreaseEngaged', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Switch.Massage.IsIncreaseEngaged', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Switch.Seating.IsBackwardEngaged', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Switch.Seating.IsForwardEngaged', + 'Vehicle.Cabin.Seat.Row1.DriverSide.Tilt', + 'Vehicle.Cabin.Seat.Row1.Middle.Airbag.IsDeployed', + 'Vehicle.Cabin.Seat.Row1.Middle.Backrest.Lumbar.Height', + 'Vehicle.Cabin.Seat.Row1.Middle.Backrest.Lumbar.Support', + 'Vehicle.Cabin.Seat.Row1.Middle.Backrest.Recline', + 'Vehicle.Cabin.Seat.Row1.Middle.Backrest.SideBolster.Support', + 'Vehicle.Cabin.Seat.Row1.Middle.Headrest.Angle', + 'Vehicle.Cabin.Seat.Row1.Middle.Headrest.Height', + 'Vehicle.Cabin.Seat.Row1.Middle.Heating', + 'Vehicle.Cabin.Seat.Row1.Middle.Height', + 'Vehicle.Cabin.Seat.Row1.Middle.IsBelted', + 'Vehicle.Cabin.Seat.Row1.Middle.IsOccupied', + 'Vehicle.Cabin.Seat.Row1.Middle.Massage', + 'Vehicle.Cabin.Seat.Row1.Middle.Occupant.Identifier.Issuer', + 'Vehicle.Cabin.Seat.Row1.Middle.Occupant.Identifier.Subject', + 'Vehicle.Cabin.Seat.Row1.Middle.Position', + 'Vehicle.Cabin.Seat.Row1.Middle.Seating.Length', + 'Vehicle.Cabin.Seat.Row1.Middle.Switch.Backrest.IsReclineBackwardEngaged', + 'Vehicle.Cabin.Seat.Row1.Middle.Switch.Backrest.IsReclineForwardEngaged', + 'Vehicle.Cabin.Seat.Row1.Middle.Switch.Backrest.Lumbar.IsDownEngaged', + 'Vehicle.Cabin.Seat.Row1.Middle.Switch.Backrest.Lumbar.IsLessSupportEngaged', + 'Vehicle.Cabin.Seat.Row1.Middle.Switch.Backrest.Lumbar.IsMoreSupportEngaged', + 'Vehicle.Cabin.Seat.Row1.Middle.Switch.Backrest.Lumbar.IsUpEngaged', + 'Vehicle.Cabin.Seat.Row1.Middle.Switch.Backrest.SideBolster.IsLessSupportEngaged', + 'Vehicle.Cabin.Seat.Row1.Middle.Switch.Backrest.SideBolster.IsMoreSupportEngaged', + 'Vehicle.Cabin.Seat.Row1.Middle.Switch.Headrest.IsBackwardEngaged', + 'Vehicle.Cabin.Seat.Row1.Middle.Switch.Headrest.IsDownEngaged', + 'Vehicle.Cabin.Seat.Row1.Middle.Switch.Headrest.IsForwardEngaged', + 'Vehicle.Cabin.Seat.Row1.Middle.Switch.Headrest.IsUpEngaged', + 'Vehicle.Cabin.Seat.Row1.Middle.Switch.IsBackwardEngaged', + 'Vehicle.Cabin.Seat.Row1.Middle.Switch.IsCoolerEngaged', + 'Vehicle.Cabin.Seat.Row1.Middle.Switch.IsDownEngaged', + 'Vehicle.Cabin.Seat.Row1.Middle.Switch.IsForwardEngaged', + 'Vehicle.Cabin.Seat.Row1.Middle.Switch.IsTiltBackwardEngaged', + 'Vehicle.Cabin.Seat.Row1.Middle.Switch.IsTiltForwardEngaged', + 'Vehicle.Cabin.Seat.Row1.Middle.Switch.IsUpEngaged', + 'Vehicle.Cabin.Seat.Row1.Middle.Switch.IsWarmerEngaged', + 'Vehicle.Cabin.Seat.Row1.Middle.Switch.Massage.IsDecreaseEngaged', + 'Vehicle.Cabin.Seat.Row1.Middle.Switch.Massage.IsIncreaseEngaged', + 'Vehicle.Cabin.Seat.Row1.Middle.Switch.Seating.IsBackwardEngaged', + 'Vehicle.Cabin.Seat.Row1.Middle.Switch.Seating.IsForwardEngaged', + 'Vehicle.Cabin.Seat.Row1.Middle.Tilt', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Airbag.IsDeployed', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Backrest.Lumbar.Height', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Backrest.Lumbar.Support', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Backrest.Recline', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Backrest.SideBolster.Support', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Headrest.Angle', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Headrest.Height', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Heating', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Height', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.IsBelted', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.IsOccupied', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Massage', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Occupant.Identifier.Issuer', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Occupant.Identifier.Subject', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Position', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Seating.Length', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Switch.Backrest.IsReclineBackwardEngaged', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Switch.Backrest.IsReclineForwardEngaged', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Switch.Backrest.Lumbar.IsDownEngaged', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Switch.Backrest.Lumbar.IsLessSupportEngaged', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Switch.Backrest.Lumbar.IsMoreSupportEngaged', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Switch.Backrest.Lumbar.IsUpEngaged', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Switch.Backrest.SideBolster.', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Switch.Backrest.SideBolster.', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Switch.Headrest.IsBackwardEngaged', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Switch.Headrest.IsDownEngaged', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Switch.Headrest.IsForwardEngaged', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Switch.Headrest.IsUpEngaged', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Switch.IsBackwardEngaged', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Switch.IsCoolerEngaged', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Switch.IsDownEngaged', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Switch.IsForwardEngaged', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Switch.IsTiltBackwardEngaged', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Switch.IsTiltForwardEngaged', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Switch.IsUpEngaged', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Switch.IsWarmerEngaged', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Switch.Massage.IsDecreaseEngaged', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Switch.Massage.IsIncreaseEngaged', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Switch.Seating.IsBackwardEngaged', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Switch.Seating.IsForwardEngaged', + 'Vehicle.Cabin.Seat.Row1.PassengerSide.Tilt', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Airbag.IsDeployed', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Backrest.Lumbar.Height', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Backrest.Lumbar.Support', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Backrest.Recline', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Backrest.SideBolster.Support', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Headrest.Angle', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Headrest.Height', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Heating', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Height', + 'Vehicle.Cabin.Seat.Row2.DriverSide.IsBelted', + 'Vehicle.Cabin.Seat.Row2.DriverSide.IsOccupied', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Massage', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Occupant.Identifier.Issuer', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Occupant.Identifier.Subject', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Position', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Seating.Length', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Switch.Backrest.IsReclineBackwardEngaged', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Switch.Backrest.IsReclineForwardEngaged', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Switch.Backrest.Lumbar.IsDownEngaged', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Switch.Backrest.Lumbar.IsLessSupportEngaged', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Switch.Backrest.Lumbar.IsMoreSupportEngaged', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Switch.Backrest.Lumbar.IsUpEngaged', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Switch.Backrest.SideBolster.IsLessSupportEngaged', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Switch.Backrest.SideBolster.IsMoreSupportEngaged', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Switch.Headrest.IsBackwardEngaged', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Switch.Headrest.IsDownEngaged', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Switch.Headrest.IsForwardEngaged', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Switch.Headrest.IsUpEngaged', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Switch.IsBackwardEngaged', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Switch.IsCoolerEngaged', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Switch.IsDownEngaged', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Switch.IsForwardEngaged', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Switch.IsTiltBackwardEngaged', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Switch.IsTiltForwardEngaged', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Switch.IsUpEngaged', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Switch.IsWarmerEngaged', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Switch.Massage.IsDecreaseEngaged', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Switch.Massage.IsIncreaseEngaged', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Switch.Seating.IsBackwardEngaged', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Switch.Seating.IsForwardEngaged', + 'Vehicle.Cabin.Seat.Row2.DriverSide.Tilt', + 'Vehicle.Cabin.Seat.Row2.Middle.Airbag.IsDeployed', + 'Vehicle.Cabin.Seat.Row2.Middle.Backrest.Lumbar.Height', + 'Vehicle.Cabin.Seat.Row2.Middle.Backrest.Lumbar.Support', + 'Vehicle.Cabin.Seat.Row2.Middle.Backrest.Recline', + 'Vehicle.Cabin.Seat.Row2.Middle.Backrest.SideBolster.Support', + 'Vehicle.Cabin.Seat.Row2.Middle.Headrest.Angle', + 'Vehicle.Cabin.Seat.Row2.Middle.Headrest.Height', + 'Vehicle.Cabin.Seat.Row2.Middle.Heating', + 'Vehicle.Cabin.Seat.Row2.Middle.Height', + 'Vehicle.Cabin.Seat.Row2.Middle.IsBelted', + 'Vehicle.Cabin.Seat.Row2.Middle.IsOccupied', + 'Vehicle.Cabin.Seat.Row2.Middle.Massage', + 'Vehicle.Cabin.Seat.Row2.Middle.Occupant.Identifier.Issuer', + 'Vehicle.Cabin.Seat.Row2.Middle.Occupant.Identifier.Subject', + 'Vehicle.Cabin.Seat.Row2.Middle.Position', + 'Vehicle.Cabin.Seat.Row2.Middle.Seating.Length', + 'Vehicle.Cabin.Seat.Row2.Middle.Switch.Backrest.IsReclineBackwardEngaged', + 'Vehicle.Cabin.Seat.Row2.Middle.Switch.Backrest.IsReclineForwardEngaged', + 'Vehicle.Cabin.Seat.Row2.Middle.Switch.Backrest.Lumbar.IsDownEngaged', + 'Vehicle.Cabin.Seat.Row2.Middle.Switch.Backrest.Lumbar.IsLessSupportEngaged', + 'Vehicle.Cabin.Seat.Row2.Middle.Switch.Backrest.Lumbar.IsMoreSupportEngaged', + 'Vehicle.Cabin.Seat.Row2.Middle.Switch.Backrest.Lumbar.IsUpEngaged', + 'Vehicle.Cabin.Seat.Row2.Middle.Switch.Backrest.SideBolster.IsLessSupportEngaged', + 'Vehicle.Cabin.Seat.Row2.Middle.Switch.Backrest.SideBolster.IsMoreSupportEngaged', + 'Vehicle.Cabin.Seat.Row2.Middle.Switch.Headrest.IsBackwardEngaged', + 'Vehicle.Cabin.Seat.Row2.Middle.Switch.Headrest.IsDownEngaged', + 'Vehicle.Cabin.Seat.Row2.Middle.Switch.Headrest.IsForwardEngaged', + 'Vehicle.Cabin.Seat.Row2.Middle.Switch.Headrest.IsUpEngaged', + 'Vehicle.Cabin.Seat.Row2.Middle.Switch.IsBackwardEngaged', + 'Vehicle.Cabin.Seat.Row2.Middle.Switch.IsCoolerEngaged', + 'Vehicle.Cabin.Seat.Row2.Middle.Switch.IsDownEngaged', + 'Vehicle.Cabin.Seat.Row2.Middle.Switch.IsForwardEngaged', + 'Vehicle.Cabin.Seat.Row2.Middle.Switch.IsTiltBackwardEngaged', + 'Vehicle.Cabin.Seat.Row2.Middle.Switch.IsTiltForwardEngaged', + 'Vehicle.Cabin.Seat.Row2.Middle.Switch.IsUpEngaged', + 'Vehicle.Cabin.Seat.Row2.Middle.Switch.IsWarmerEngaged', + 'Vehicle.Cabin.Seat.Row2.Middle.Switch.Massage.IsDecreaseEngaged', + 'Vehicle.Cabin.Seat.Row2.Middle.Switch.Massage.IsIncreaseEngaged', + 'Vehicle.Cabin.Seat.Row2.Middle.Switch.Seating.IsBackwardEngaged', + 'Vehicle.Cabin.Seat.Row2.Middle.Switch.Seating.IsForwardEngaged', + 'Vehicle.Cabin.Seat.Row2.Middle.Tilt', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Airbag.IsDeployed', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Backrest.Lumbar.Height', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Backrest.Lumbar.Support', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Backrest.Recline', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Backrest.SideBolster.Support', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Headrest.Angle', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Headrest.Height', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Heating', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Height', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.IsBelted', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.IsOccupied', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Massage', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Occupant.Identifier.Issuer', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Occupant.Identifier.Subject', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Position', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Seating.Length', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Switch.Backrest.IsReclineBackwardEngaged', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Switch.Backrest.IsReclineForwardEngaged', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Switch.Backrest.Lumbar.IsDownEngaged', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Switch.Backrest.Lumbar.IsLessSupportEngaged', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Switch.Backrest.Lumbar.IsMoreSupportEngaged', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Switch.Backrest.Lumbar.IsUpEngaged', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Switch.Backrest.SideBolster.', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Switch.Backrest.SideBolster.', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Switch.Headrest.IsBackwardEngaged', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Switch.Headrest.IsDownEngaged', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Switch.Headrest.IsForwardEngaged', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Switch.Headrest.IsUpEngaged', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Switch.IsBackwardEngaged', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Switch.IsCoolerEngaged', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Switch.IsDownEngaged', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Switch.IsForwardEngaged', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Switch.IsTiltBackwardEngaged', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Switch.IsTiltForwardEngaged', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Switch.IsUpEngaged', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Switch.IsWarmerEngaged', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Switch.Massage.IsDecreaseEngaged', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Switch.Massage.IsIncreaseEngaged', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Switch.Seating.IsBackwardEngaged', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Switch.Seating.IsForwardEngaged', + 'Vehicle.Cabin.Seat.Row2.PassengerSide.Tilt', + 'Vehicle.Cabin.SeatPosCount', + 'Vehicle.Cabin.SeatRowCount', + 'Vehicle.Cabin.Sunroof.Position', + 'Vehicle.Cabin.Sunroof.Shade.Position', + 'Vehicle.Cabin.Sunroof.Shade.Switch', + 'Vehicle.Cabin.Sunroof.Switch', + 'Vehicle.CargoVolume', + 'Vehicle.Chassis.Accelerator.PedalPosition', + 'Vehicle.Chassis.Axle.Row1.AxleWidth', + 'Vehicle.Chassis.Axle.Row1.SteeringAngle', + 'Vehicle.Chassis.Axle.Row1.TireAspectRatio', + 'Vehicle.Chassis.Axle.Row1.TireDiameter', + 'Vehicle.Chassis.Axle.Row1.TireWidth', + 'Vehicle.Chassis.Axle.Row1.TrackWidth', + 'Vehicle.Chassis.Axle.Row1.TreadWidth', + 'Vehicle.Chassis.Axle.Row1.Wheel.Left.Brake.FluidLevel', + 'Vehicle.Chassis.Axle.Row1.Wheel.Left.Brake.IsBrakesWorn', + 'Vehicle.Chassis.Axle.Row1.Wheel.Left.Brake.IsFluidLevelLow', + 'Vehicle.Chassis.Axle.Row1.Wheel.Left.Brake.PadWear', + 'Vehicle.Chassis.Axle.Row1.Wheel.Left.Speed', + 'Vehicle.Chassis.Axle.Row1.Wheel.Left.Tire.IsPressureLow', + 'Vehicle.Chassis.Axle.Row1.Wheel.Left.Tire.Pressure', + 'Vehicle.Chassis.Axle.Row1.Wheel.Left.Tire.Temperature', + 'Vehicle.Chassis.Axle.Row1.Wheel.Right.Brake.FluidLevel', + 'Vehicle.Chassis.Axle.Row1.Wheel.Right.Brake.IsBrakesWorn', + 'Vehicle.Chassis.Axle.Row1.Wheel.Right.Brake.IsFluidLevelLow', + 'Vehicle.Chassis.Axle.Row1.Wheel.Right.Brake.PadWear', + 'Vehicle.Chassis.Axle.Row1.Wheel.Right.Speed', + 'Vehicle.Chassis.Axle.Row1.Wheel.Right.Tire.IsPressureLow', + 'Vehicle.Chassis.Axle.Row1.Wheel.Right.Tire.Pressure', + 'Vehicle.Chassis.Axle.Row1.Wheel.Right.Tire.Temperature', + 'Vehicle.Chassis.Axle.Row1.WheelCount', + 'Vehicle.Chassis.Axle.Row1.WheelDiameter', + 'Vehicle.Chassis.Axle.Row1.WheelWidth', + 'Vehicle.Chassis.Axle.Row2.AxleWidth', + 'Vehicle.Chassis.Axle.Row2.SteeringAngle', + 'Vehicle.Chassis.Axle.Row2.TireAspectRatio', + 'Vehicle.Chassis.Axle.Row2.TireDiameter', + 'Vehicle.Chassis.Axle.Row2.TireWidth', + 'Vehicle.Chassis.Axle.Row2.TrackWidth', + 'Vehicle.Chassis.Axle.Row2.TreadWidth', + 'Vehicle.Chassis.Axle.Row2.Wheel.Left.Brake.FluidLevel', + 'Vehicle.Chassis.Axle.Row2.Wheel.Left.Brake.IsBrakesWorn', + 'Vehicle.Chassis.Axle.Row2.Wheel.Left.Brake.IsFluidLevelLow', + 'Vehicle.Chassis.Axle.Row2.Wheel.Left.Brake.PadWear', + 'Vehicle.Chassis.Axle.Row2.Wheel.Left.Speed', + 'Vehicle.Chassis.Axle.Row2.Wheel.Left.Tire.IsPressureLow', + 'Vehicle.Chassis.Axle.Row2.Wheel.Left.Tire.Pressure', + 'Vehicle.Chassis.Axle.Row2.Wheel.Left.Tire.Temperature', + 'Vehicle.Chassis.Axle.Row2.Wheel.Right.Brake.FluidLevel', + 'Vehicle.Chassis.Axle.Row2.Wheel.Right.Brake.IsBrakesWorn', + 'Vehicle.Chassis.Axle.Row2.Wheel.Right.Brake.IsFluidLevelLow', + 'Vehicle.Chassis.Axle.Row2.Wheel.Right.Brake.PadWear', + 'Vehicle.Chassis.Axle.Row2.Wheel.Right.Speed', + 'Vehicle.Chassis.Axle.Row2.Wheel.Right.Tire.IsPressureLow', + 'Vehicle.Chassis.Axle.Row2.Wheel.Right.Tire.Pressure', + 'Vehicle.Chassis.Axle.Row2.Wheel.Right.Tire.Temperature', + 'Vehicle.Chassis.Axle.Row2.WheelCount', + 'Vehicle.Chassis.Axle.Row2.WheelDiameter', + 'Vehicle.Chassis.Axle.Row2.WheelWidth', + 'Vehicle.Chassis.AxleCount', + 'Vehicle.Chassis.Brake.IsDriverEmergencyBrakingDetected', + 'Vehicle.Chassis.Brake.PedalPosition', + 'Vehicle.Chassis.ParkingBrake.IsAutoApplyEnabled', + 'Vehicle.Chassis.ParkingBrake.IsEngaged', + 'Vehicle.Chassis.SteeringWheel.Angle', + 'Vehicle.Chassis.SteeringWheel.Extension', + 'Vehicle.Chassis.SteeringWheel.Tilt', + 'Vehicle.Chassis.Wheelbase', + 'Vehicle.Connectivity.IsConnectivityAvailable', + 'Vehicle.CurbWeight', + 'Vehicle.CurrentLocation.Altitude', + 'Vehicle.CurrentLocation.GNSSReceiver.FixType', + 'Vehicle.CurrentLocation.GNSSReceiver.MountingPosition.X', + 'Vehicle.CurrentLocation.GNSSReceiver.MountingPosition.Y', + 'Vehicle.CurrentLocation.GNSSReceiver.MountingPosition.Z', + 'Vehicle.CurrentLocation.Heading', + 'Vehicle.CurrentLocation.HorizontalAccuracy', + 'Vehicle.CurrentLocation.Latitude', + 'Vehicle.CurrentLocation.Longitude', + 'Vehicle.CurrentLocation.Timestamp', + 'Vehicle.CurrentLocation.VerticalAccuracy', + 'Vehicle.CurrentOverallWeight', + 'Vehicle.Driver.AttentiveProbability', + 'Vehicle.Driver.DistractionLevel', + 'Vehicle.Driver.FatigueLevel', + 'Vehicle.Driver.HeartRate', + 'Vehicle.Driver.Identifier.Issuer', + 'Vehicle.Driver.Identifier.Subject', + 'Vehicle.Driver.IsEyesOnRoad', + 'Vehicle.Driver.IsHandsOnWheel', + 'Vehicle.EmissionsCO2', + 'Vehicle.Exterior.AirTemperature', + 'Vehicle.Exterior.Humidity', + 'Vehicle.Exterior.LightIntensity', + 'Vehicle.GrossWeight', + 'Vehicle.Height', + 'Vehicle.IsBrokenDown', + 'Vehicle.IsMoving', + 'Vehicle.Length', + 'Vehicle.LowVoltageBattery.CurrentCurrent', + 'Vehicle.LowVoltageBattery.CurrentVoltage', + 'Vehicle.LowVoltageBattery.NominalCapacity', + 'Vehicle.LowVoltageBattery.NominalVoltage', + 'Vehicle.LowVoltageSystemState', + 'Vehicle.MaxTowBallWeight', + 'Vehicle.MaxTowWeight', + 'Vehicle.OBD.AbsoluteLoad', + 'Vehicle.OBD.AcceleratorPositionD', + 'Vehicle.OBD.AcceleratorPositionE', + 'Vehicle.OBD.AcceleratorPositionF', + 'Vehicle.OBD.AirStatus', + 'Vehicle.OBD.AmbientAirTemperature', + 'Vehicle.OBD.BarometricPressure', + 'Vehicle.OBD.Catalyst.Bank1.Temperature1', + 'Vehicle.OBD.Catalyst.Bank1.Temperature2', + 'Vehicle.OBD.Catalyst.Bank2.Temperature1', + 'Vehicle.OBD.Catalyst.Bank2.Temperature2', + 'Vehicle.OBD.CommandedEGR', + 'Vehicle.OBD.CommandedEVAP', + 'Vehicle.OBD.CommandedEquivalenceRatio', + 'Vehicle.OBD.ControlModuleVoltage', + 'Vehicle.OBD.CoolantTemperature', + 'Vehicle.OBD.DTCList', + 'Vehicle.OBD.DistanceSinceDTCClear', + 'Vehicle.OBD.DistanceWithMIL', + 'Vehicle.OBD.DriveCycleStatus.DTCCount', + 'Vehicle.OBD.DriveCycleStatus.IgnitionType', + 'Vehicle.OBD.DriveCycleStatus.IsMILOn', + 'Vehicle.OBD.EGRError', + 'Vehicle.OBD.EVAPVaporPressure', + 'Vehicle.OBD.EVAPVaporPressureAbsolute', + 'Vehicle.OBD.EVAPVaporPressureAlternate', + 'Vehicle.OBD.EngineLoad', + 'Vehicle.OBD.EngineSpeed', + 'Vehicle.OBD.EthanolPercent', + 'Vehicle.OBD.FreezeDTC', + 'Vehicle.OBD.FuelInjectionTiming', + 'Vehicle.OBD.FuelLevel', + 'Vehicle.OBD.FuelPressure', + 'Vehicle.OBD.FuelRailPressureAbsolute', + 'Vehicle.OBD.FuelRailPressureDirect', + 'Vehicle.OBD.FuelRailPressureVac', + 'Vehicle.OBD.FuelRate', + 'Vehicle.OBD.FuelStatus', + 'Vehicle.OBD.FuelType', + 'Vehicle.OBD.HybridBatteryRemaining', + 'Vehicle.OBD.IntakeTemp', + 'Vehicle.OBD.IsPTOActive', + 'Vehicle.OBD.LongTermFuelTrim1', + 'Vehicle.OBD.LongTermFuelTrim2', + 'Vehicle.OBD.LongTermO2Trim1', + 'Vehicle.OBD.LongTermO2Trim2', + 'Vehicle.OBD.LongTermO2Trim3', + 'Vehicle.OBD.LongTermO2Trim4', + 'Vehicle.OBD.MAF', + 'Vehicle.OBD.MAP', + 'Vehicle.OBD.MaxMAF', + 'Vehicle.OBD.O2.Sensor1.ShortTermFuelTrim', + 'Vehicle.OBD.O2.Sensor1.Voltage', + 'Vehicle.OBD.O2.Sensor2.ShortTermFuelTrim', + 'Vehicle.OBD.O2.Sensor2.Voltage', + 'Vehicle.OBD.O2.Sensor3.ShortTermFuelTrim', + 'Vehicle.OBD.O2.Sensor3.Voltage', + 'Vehicle.OBD.O2.Sensor4.ShortTermFuelTrim', + 'Vehicle.OBD.O2.Sensor4.Voltage', + 'Vehicle.OBD.O2.Sensor5.ShortTermFuelTrim', + 'Vehicle.OBD.O2.Sensor5.Voltage', + 'Vehicle.OBD.O2.Sensor6.ShortTermFuelTrim', + 'Vehicle.OBD.O2.Sensor6.Voltage', + 'Vehicle.OBD.O2.Sensor7.ShortTermFuelTrim', + 'Vehicle.OBD.O2.Sensor7.Voltage', + 'Vehicle.OBD.O2.Sensor8.ShortTermFuelTrim', + 'Vehicle.OBD.O2.Sensor8.Voltage', + 'Vehicle.OBD.O2WR.Sensor1.Current', + 'Vehicle.OBD.O2WR.Sensor1.Lambda', + 'Vehicle.OBD.O2WR.Sensor1.Voltage', + 'Vehicle.OBD.O2WR.Sensor2.Current', + 'Vehicle.OBD.O2WR.Sensor2.Lambda', + 'Vehicle.OBD.O2WR.Sensor2.Voltage', + 'Vehicle.OBD.O2WR.Sensor3.Current', + 'Vehicle.OBD.O2WR.Sensor3.Lambda', + 'Vehicle.OBD.O2WR.Sensor3.Voltage', + 'Vehicle.OBD.O2WR.Sensor4.Current', + 'Vehicle.OBD.O2WR.Sensor4.Lambda', + 'Vehicle.OBD.O2WR.Sensor4.Voltage', + 'Vehicle.OBD.O2WR.Sensor5.Current', + 'Vehicle.OBD.O2WR.Sensor5.Lambda', + 'Vehicle.OBD.O2WR.Sensor5.Voltage', + 'Vehicle.OBD.O2WR.Sensor6.Current', + 'Vehicle.OBD.O2WR.Sensor6.Lambda', + 'Vehicle.OBD.O2WR.Sensor6.Voltage', + 'Vehicle.OBD.O2WR.Sensor7.Current', + 'Vehicle.OBD.O2WR.Sensor7.Lambda', + 'Vehicle.OBD.O2WR.Sensor7.Voltage', + 'Vehicle.OBD.O2WR.Sensor8.Current', + 'Vehicle.OBD.O2WR.Sensor8.Lambda', + 'Vehicle.OBD.O2WR.Sensor8.Voltage', + 'Vehicle.OBD.OBDStandards', + 'Vehicle.OBD.OilTemperature', + 'Vehicle.OBD.OxygenSensorsIn2Banks', + 'Vehicle.OBD.OxygenSensorsIn4Banks', + 'Vehicle.OBD.PidsA', + 'Vehicle.OBD.PidsB', + 'Vehicle.OBD.PidsC', + 'Vehicle.OBD.RelativeAcceleratorPosition', + 'Vehicle.OBD.RelativeThrottlePosition', + 'Vehicle.OBD.RunTime', + 'Vehicle.OBD.RunTimeMIL', + 'Vehicle.OBD.ShortTermFuelTrim1', + 'Vehicle.OBD.ShortTermFuelTrim2', + 'Vehicle.OBD.ShortTermO2Trim1', + 'Vehicle.OBD.ShortTermO2Trim2', + 'Vehicle.OBD.ShortTermO2Trim3', + 'Vehicle.OBD.ShortTermO2Trim4', + 'Vehicle.OBD.Speed', + 'Vehicle.OBD.Status.DTCCount', + 'Vehicle.OBD.Status.IgnitionType', + 'Vehicle.OBD.Status.IsMILOn', + 'Vehicle.OBD.ThrottleActuator', + 'Vehicle.OBD.ThrottlePosition', + 'Vehicle.OBD.ThrottlePositionB', + 'Vehicle.OBD.ThrottlePositionC', + 'Vehicle.OBD.TimeSinceDTCCleared', + 'Vehicle.OBD.TimingAdvance', + 'Vehicle.OBD.WarmupsSinceDTCClear', + 'Vehicle.PowerOptimizeLevel', + 'Vehicle.Powertrain.AccumulatedBrakingEnergy', + 'Vehicle.Powertrain.CombustionEngine.AspirationType', + 'Vehicle.Powertrain.CombustionEngine.Bore', + 'Vehicle.Powertrain.CombustionEngine.CompressionRatio', + 'Vehicle.Powertrain.CombustionEngine.Configuration', + 'Vehicle.Powertrain.CombustionEngine.DieselExhaustFluid.Capacity', + 'Vehicle.Powertrain.CombustionEngine.DieselExhaustFluid.IsLevelLow', + 'Vehicle.Powertrain.CombustionEngine.DieselExhaustFluid.Level', + 'Vehicle.Powertrain.CombustionEngine.DieselExhaustFluid.Range', + 'Vehicle.Powertrain.CombustionEngine.DieselParticulateFilter.DeltaPressure', + 'Vehicle.Powertrain.CombustionEngine.DieselParticulateFilter.InletTemperature', + 'Vehicle.Powertrain.CombustionEngine.DieselParticulateFilter.OutletTemperature', + 'Vehicle.Powertrain.CombustionEngine.Displacement', + 'Vehicle.Powertrain.CombustionEngine.ECT', + 'Vehicle.Powertrain.CombustionEngine.EOP', + 'Vehicle.Powertrain.CombustionEngine.EOT', + 'Vehicle.Powertrain.CombustionEngine.EngineCode', + 'Vehicle.Powertrain.CombustionEngine.EngineCoolantCapacity', + 'Vehicle.Powertrain.CombustionEngine.EngineHours', + 'Vehicle.Powertrain.CombustionEngine.EngineOilCapacity', + 'Vehicle.Powertrain.CombustionEngine.EngineOilLevel', + 'Vehicle.Powertrain.CombustionEngine.IdleHours', + 'Vehicle.Powertrain.CombustionEngine.IsRunning', + 'Vehicle.Powertrain.CombustionEngine.MAF', + 'Vehicle.Powertrain.CombustionEngine.MAP', + 'Vehicle.Powertrain.CombustionEngine.MaxPower', + 'Vehicle.Powertrain.CombustionEngine.MaxTorque', + 'Vehicle.Powertrain.CombustionEngine.NumberOfCylinders', + 'Vehicle.Powertrain.CombustionEngine.NumberOfValvesPerCylinder', + 'Vehicle.Powertrain.CombustionEngine.OilLifeRemaining', + 'Vehicle.Powertrain.CombustionEngine.Power', + 'Vehicle.Powertrain.CombustionEngine.Speed', + 'Vehicle.Powertrain.CombustionEngine.StrokeLength', + 'Vehicle.Powertrain.CombustionEngine.TPS', + 'Vehicle.Powertrain.CombustionEngine.Torque', + 'Vehicle.Powertrain.ElectricMotor.CoolantTemperature', + 'Vehicle.Powertrain.ElectricMotor.EngineCode', + 'Vehicle.Powertrain.ElectricMotor.MaxPower', + 'Vehicle.Powertrain.ElectricMotor.MaxRegenPower', + 'Vehicle.Powertrain.ElectricMotor.MaxRegenTorque', + 'Vehicle.Powertrain.ElectricMotor.MaxTorque', + 'Vehicle.Powertrain.ElectricMotor.Power', + 'Vehicle.Powertrain.ElectricMotor.Speed', + 'Vehicle.Powertrain.ElectricMotor.Temperature', + 'Vehicle.Powertrain.ElectricMotor.Torque', + 'Vehicle.Powertrain.FuelSystem.AbsoluteLevel', + 'Vehicle.Powertrain.FuelSystem.AverageConsumption', + 'Vehicle.Powertrain.FuelSystem.ConsumptionSinceStart', + 'Vehicle.Powertrain.FuelSystem.HybridType', + 'Vehicle.Powertrain.FuelSystem.InstantConsumption', + 'Vehicle.Powertrain.FuelSystem.IsEngineStopStartEnabled', + 'Vehicle.Powertrain.FuelSystem.IsFuelLevelLow', + 'Vehicle.Powertrain.FuelSystem.Range', + 'Vehicle.Powertrain.FuelSystem.RelativeLevel', + 'Vehicle.Powertrain.FuelSystem.SupportedFuel', + 'Vehicle.Powertrain.FuelSystem.SupportedFuelTypes', + 'Vehicle.Powertrain.FuelSystem.TankCapacity', + 'Vehicle.Powertrain.PowerOptimizeLevel', + 'Vehicle.Powertrain.Range', + 'Vehicle.Powertrain.TractionBattery.AccumulatedChargedEnergy', + 'Vehicle.Powertrain.TractionBattery.AccumulatedChargedThroughput', + 'Vehicle.Powertrain.TractionBattery.AccumulatedConsumedEnergy', + 'Vehicle.Powertrain.TractionBattery.AccumulatedConsumedThroughput', + 'Vehicle.Powertrain.TractionBattery.CellVoltage.Max', + 'Vehicle.Powertrain.TractionBattery.CellVoltage.Min', + 'Vehicle.Powertrain.TractionBattery.Charging.ChargeCurrent.DC', + 'Vehicle.Powertrain.TractionBattery.Charging.ChargeCurrent.Phase1', + 'Vehicle.Powertrain.TractionBattery.Charging.ChargeCurrent.Phase2', + 'Vehicle.Powertrain.TractionBattery.Charging.ChargeCurrent.Phase3', + 'Vehicle.Powertrain.TractionBattery.Charging.ChargeLimit', + 'Vehicle.Powertrain.TractionBattery.Charging.ChargePlugType', + 'Vehicle.Powertrain.TractionBattery.Charging.ChargePortFlap', + 'Vehicle.Powertrain.TractionBattery.Charging.ChargeRate', + 'Vehicle.Powertrain.TractionBattery.Charging.ChargeVoltage.DC', + 'Vehicle.Powertrain.TractionBattery.Charging.ChargeVoltage.Phase1', + 'Vehicle.Powertrain.TractionBattery.Charging.ChargeVoltage.Phase2', + 'Vehicle.Powertrain.TractionBattery.Charging.ChargeVoltage.Phase3', + 'Vehicle.Powertrain.TractionBattery.Charging.IsCharging', + 'Vehicle.Powertrain.TractionBattery.Charging.IsChargingCableConnected', + 'Vehicle.Powertrain.TractionBattery.Charging.IsChargingCableLocked', + 'Vehicle.Powertrain.TractionBattery.Charging.IsDischarging', + 'Vehicle.Powertrain.TractionBattery.Charging.MaximumChargingCurrent.DC', + 'Vehicle.Powertrain.TractionBattery.Charging.MaximumChargingCurrent.Phase1', + 'Vehicle.Powertrain.TractionBattery.Charging.MaximumChargingCurrent.Phase2', + 'Vehicle.Powertrain.TractionBattery.Charging.MaximumChargingCurrent.Phase3', + 'Vehicle.Powertrain.TractionBattery.Charging.Mode', + 'Vehicle.Powertrain.TractionBattery.Charging.PowerLoss', + 'Vehicle.Powertrain.TractionBattery.Charging.StartStopCharging', + 'Vehicle.Powertrain.TractionBattery.Charging.Temperature', + 'Vehicle.Powertrain.TractionBattery.Charging.TimeToComplete', + 'Vehicle.Powertrain.TractionBattery.Charging.Timer.Mode', + 'Vehicle.Powertrain.TractionBattery.Charging.Timer.Time', + 'Vehicle.Powertrain.TractionBattery.CurrentCurrent', + 'Vehicle.Powertrain.TractionBattery.CurrentPower', + 'Vehicle.Powertrain.TractionBattery.CurrentVoltage', + 'Vehicle.Powertrain.TractionBattery.DCDC.PowerLoss', + 'Vehicle.Powertrain.TractionBattery.DCDC.Temperature', + 'Vehicle.Powertrain.TractionBattery.GrossCapacity', + 'Vehicle.Powertrain.TractionBattery.Id', + 'Vehicle.Powertrain.TractionBattery.IsGroundConnected', + 'Vehicle.Powertrain.TractionBattery.IsPowerConnected', + 'Vehicle.Powertrain.TractionBattery.MaxVoltage', + 'Vehicle.Powertrain.TractionBattery.NetCapacity', + 'Vehicle.Powertrain.TractionBattery.NominalVoltage', + 'Vehicle.Powertrain.TractionBattery.PowerLoss', + 'Vehicle.Powertrain.TractionBattery.ProductionDate', + 'Vehicle.Powertrain.TractionBattery.Range', + 'Vehicle.Powertrain.TractionBattery.StateOfCharge.Current', + 'Vehicle.Powertrain.TractionBattery.StateOfCharge.CurrentEnergy', + 'Vehicle.Powertrain.TractionBattery.StateOfCharge.Displayed', + 'Vehicle.Powertrain.TractionBattery.StateOfHealth', + 'Vehicle.Powertrain.TractionBattery.Temperature.Average', + 'Vehicle.Powertrain.TractionBattery.Temperature.Max', + 'Vehicle.Powertrain.TractionBattery.Temperature.Min', + 'Vehicle.Powertrain.Transmission.ClutchEngagement', + 'Vehicle.Powertrain.Transmission.ClutchWear', + 'Vehicle.Powertrain.Transmission.CurrentGear', + 'Vehicle.Powertrain.Transmission.DiffLockFrontEngagement', + 'Vehicle.Powertrain.Transmission.DiffLockRearEngagement', + 'Vehicle.Powertrain.Transmission.DriveType', + 'Vehicle.Powertrain.Transmission.GearChangeMode', + 'Vehicle.Powertrain.Transmission.GearCount', + 'Vehicle.Powertrain.Transmission.IsElectricalPowertrainEngaged', + 'Vehicle.Powertrain.Transmission.IsLowRangeEngaged', + 'Vehicle.Powertrain.Transmission.IsParkLockEngaged', + 'Vehicle.Powertrain.Transmission.PerformanceMode', + 'Vehicle.Powertrain.Transmission.SelectedGear', + 'Vehicle.Powertrain.Transmission.Temperature', + 'Vehicle.Powertrain.Transmission.TorqueDistribution', + 'Vehicle.Powertrain.Transmission.TravelledDistance', + 'Vehicle.Powertrain.Transmission.Type', + 'Vehicle.Powertrain.Type', + 'Vehicle.RoofLoad', + 'Vehicle.Service.DistanceToService', + 'Vehicle.Service.IsServiceDue', + 'Vehicle.Service.TimeToService', + 'Vehicle.Speed', + 'Vehicle.StartTime', + 'Vehicle.Trailer.IsConnected', + 'Vehicle.TraveledDistance', + 'Vehicle.TraveledDistanceSinceStart', + 'Vehicle.TripDuration', + 'Vehicle.TripMeterReading', + 'Vehicle.VehicleIdentification.AcrissCode', + 'Vehicle.VehicleIdentification.BodyType', + 'Vehicle.VehicleIdentification.Brand', + 'Vehicle.VehicleIdentification.DateVehicleFirstRegistered', + 'Vehicle.VehicleIdentification.KnownVehicleDamages', + 'Vehicle.VehicleIdentification.MeetsEmissionStandard', + 'Vehicle.VehicleIdentification.Model', + 'Vehicle.VehicleIdentification.OptionalExtras', + 'Vehicle.VehicleIdentification.ProductionDate', + 'Vehicle.VehicleIdentification.PurchaseDate', + 'Vehicle.VehicleIdentification.VIN', + 'Vehicle.VehicleIdentification.VehicleConfiguration', + 'Vehicle.VehicleIdentification.VehicleInteriorColor', + 'Vehicle.VehicleIdentification.VehicleInteriorType', + 'Vehicle.VehicleIdentification.VehicleModelDate', + 'Vehicle.VehicleIdentification.VehicleSeatingCapacity', + 'Vehicle.VehicleIdentification.VehicleSpecialUsage', + 'Vehicle.VehicleIdentification.WMI', + 'Vehicle.VehicleIdentification.Year', + 'Vehicle.VersionVSS.Label', + 'Vehicle.VersionVSS.Major', + 'Vehicle.VersionVSS.Minor', + 'Vehicle.VersionVSS.Patch', + 'Vehicle.Width', +]; diff --git a/lib/core/constants/val_client_helper.dart b/lib/core/constants/val_client_helper.dart new file mode 100644 index 0000000..0d7a284 --- /dev/null +++ b/lib/core/constants/val_client_helper.dart @@ -0,0 +1,58 @@ +import 'package:protos/protos.dart'; + +class ValClientHelper { + final ClientChannel channel; + final VALClient stub; + + ValClientHelper({required this.channel, required this.stub}); + + void setUint32(String path, int value, [bool actuator = true]) async { + var dp = Datapoint()..uint32 = value; + set(path, dp, actuator); + } + + void setInt32(String path, int value, [bool actuator = true]) async { + var dp = Datapoint()..int32 = value; + set(path, dp, actuator); + } + + void setBool(String path, bool value, [bool actuator = true]) async { + var dp = Datapoint()..bool_12 = value; + set(path, dp, actuator); + } + + void setString(String path, String value, [bool actuator = true]) async { + var dp = Datapoint()..string = value; + set(path, dp, actuator); + } + + void setFloat(String path, double value, [bool actuator = true]) async { + var dp = Datapoint()..float = value; + set(path, dp, actuator); + } + + void setDouble(String path, double value, [bool actuator = true]) async { + var dp = Datapoint()..double_18 = value; + set(path, dp, actuator); + } + + void set(String path, Datapoint dp, bool actuator) async { + var entry = DataEntry()..path = path; + var update = EntryUpdate(); + if (actuator) { + entry.actuatorTarget = dp; + update.fields.add(Field.FIELD_ACTUATOR_TARGET); + } else { + entry.value = dp; + update.fields.add(Field.FIELD_VALUE); + } + update.entry = entry; + var request = SetRequest(); + request.updates.add(update); + Map<String, String> metadata = {}; + // if (config.authorization.isNotEmpty) { + // metadata = {'authorization': "Bearer ${config.authorization}"}; + // } + await stub.set(request, options: CallOptions(metadata: metadata)); + } +} diff --git a/lib/core/constants/vss_path.dart b/lib/core/constants/vss_path.dart new file mode 100644 index 0000000..8875d1e --- /dev/null +++ b/lib/core/constants/vss_path.dart @@ -0,0 +1,58 @@ +class VSSPath { + static const String vehicleSpeed = 'Vehicle.Speed'; + static const String vehicleInsideTemperature = + 'Vehicle.Cabin.HVAC.AmbientAirTemperature'; + static const String vehicleOutsideTemperature = + 'Vehicle.Exterior.AirTemperature'; + static const String vehicleRange = 'Vehicle.Powertrain.FuelSystem.Range'; + static const String vehicleFuelLevel = 'Vehicle.OBD.FuelLevel'; + static const String vehicleMediaVolume = + 'Vehicle.Cabin.Infotainment.Media.Volume'; + static const String vehicleIsChildLockActiveLeft = + 'Vehicle.Cabin.Door.Row1.DriverSide.IsChildLockActive'; + static const String vehicleIsChildLockActiveRight = + 'Vehicle.Cabin.Door.Row1.PassengerSide.IsChildLockActive'; + static const String vehicleEngineSpeed = + 'Vehicle.Powertrain.CombustionEngine.Speed'; + static const String vehicleFrontLeftTire = + 'Vehicle.Chassis.Axle.Row1.Wheel.Left.Tire.Pressure'; + static const String vehicleFrontRightTire = + 'Vehicle.Chassis.Axle.Row1.Wheel.Right.Tire.Pressure'; + static const String vehicleRearLeftTire = + 'Vehicle.Chassis.Axle.Row2.Wheel.Left.Tire.Pressure'; + static const String vehicleRearRightTire = + 'Vehicle.Chassis.Axle.Row2.Wheel.Right.Tire.Pressure'; + static const String vehicleIsAirConditioningActive = + 'Vehicle.Cabin.HVAC.IsAirConditioningActive'; + static const String vehicleIsFrontDefrosterActive = + 'Vehicle.Cabin.HVAC.IsFrontDefrosterActive'; + static const String vehicleIsRearDefrosterActive = + 'Vehicle.Cabin.HVAC.IsRearDefrosterActive'; + static const String vehicleIsRecirculationActive = + 'Vehicle.Cabin.HVAC.IsRecirculationActive'; + static const String vehicleFanSpeed = + 'Vehicle.Cabin.HVAC.Station.Row1.Driver.FanSpeed'; + + List<String> getSignalsList() { + return const [ + vehicleSpeed, + vehicleInsideTemperature, + vehicleOutsideTemperature, + vehicleRange, + vehicleFuelLevel, + vehicleMediaVolume, + vehicleIsChildLockActiveLeft, + vehicleIsChildLockActiveRight, + vehicleEngineSpeed, + vehicleFrontLeftTire, + vehicleFrontRightTire, + vehicleRearLeftTire, + vehicleRearRightTire, + vehicleIsAirConditioningActive, + vehicleIsFrontDefrosterActive, + vehicleIsRearDefrosterActive, + vehicleIsRecirculationActive, + vehicleFanSpeed + ]; + } +} diff --git a/lib/core/utils/helpers.dart b/lib/core/utils/helpers.dart new file mode 100644 index 0000000..f2dbca2 --- /dev/null +++ b/lib/core/utils/helpers.dart @@ -0,0 +1,17 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class Helpers { + static Shadow dropShadowRegular = Shadow( + color: Colors.black.withOpacity(0.70), + offset: const Offset(1, 2), + blurRadius: 3); + static BoxShadow boxDropShadowRegular = BoxShadow( + color: Colors.black.withOpacity(0.70), + offset: const Offset(1, 2), + blurRadius: 3); + + static Shadow dropShadowBig = Shadow( + color: Colors.black.withOpacity(0.70), + offset: const Offset(1, 4), + blurRadius: 4); +} diff --git a/lib/core/utils/widgets/back_button.dart b/lib/core/utils/widgets/back_button.dart new file mode 100644 index 0000000..8f0862b --- /dev/null +++ b/lib/core/utils/widgets/back_button.dart @@ -0,0 +1,16 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class CustomBackButton extends ConsumerWidget { + const CustomBackButton({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BackButton( + onPressed: () { + ref.read(appProvider.notifier).update( + (state) => state = AppState.home, + ); + }, + ); + } +} diff --git a/lib/data/data_providers/app.dart b/lib/data/data_providers/app.dart new file mode 100644 index 0000000..acfaa01 --- /dev/null +++ b/lib/data/data_providers/app.dart @@ -0,0 +1,25 @@ +import '../../export.dart'; + +class App extends StatelessWidget { + const App({super.key}); + + @override + Widget build(BuildContext context) { + return const AppView(); + } +} + +class AppView extends StatelessWidget { + const AppView({super.key}); + + @override + Widget build(BuildContext context) { + return ProviderScope( + child: MaterialApp( + debugShowCheckedModeBanner: false, + theme: theme, + home: const HomeScreen(), + ), + ); + } +} diff --git a/lib/data/data_providers/app_provider.dart b/lib/data/data_providers/app_provider.dart new file mode 100644 index 0000000..c528a1a --- /dev/null +++ b/lib/data/data_providers/app_provider.dart @@ -0,0 +1,63 @@ +import 'package:flutter_ics_homescreen/data/data_providers/datetime_notifier.dart'; +import 'package:flutter_ics_homescreen/data/data_providers/hybrid_notifier.dart'; +import 'package:flutter_ics_homescreen/data/data_providers/signal_notifier.dart'; +import 'package:flutter_ics_homescreen/data/data_providers/units_notifier.dart'; +import 'package:flutter_ics_homescreen/data/data_providers/audio_notifier.dart'; +import 'package:flutter_ics_homescreen/data/data_providers/users_notifier.dart'; +import 'package:flutter_ics_homescreen/data/models/date_time.dart'; +import 'package:flutter_ics_homescreen/export.dart'; + +import '../models/users.dart'; +import 'vehicle_notifier.dart'; + +enum AppState { + home, + dashboard, + hvac, + apps, + mediaPlayer, + settings, + splash, + dateTime, + bluetooth, + wifi, + wired, + audioSettings, + profiles, + newProfile, + units, + versionInfo, + weather, + distanceUnit, + tempUnit, + clock, + date, + time +} + +final appProvider = StateProvider<AppState>((ref) => AppState.splash); +final vehicleProvider = StateNotifierProvider<VehicleNotifier, Vehicle>((ref) { + return VehicleNotifier(const Vehicle.initial()); +}); +final signalsProvider = StateNotifierProvider<SignalNotifier, Signals>((ref) { + return SignalNotifier(const Signals.initial()); +}); + +final unitStateProvider = StateNotifierProvider<UnitsNotifier, Units>((ref) { + return UnitsNotifier(const Units.initial()); +}); +final audioStateProvider = StateNotifierProvider<AudioNotifier, Audio>((ref) { + return AudioNotifier(const Audio.initial()); +}); + +final usersProvider = StateNotifierProvider<UsersNotifier, Users>((ref) { + return UsersNotifier(Users.initial()); +}); + +final dateTimeStateProvider = + StateNotifierProvider<DateTimeNotifier, DateAndTime>((ref) { + return DateTimeNotifier(DateAndTime.initial()); +}); +final hybridtateProvider = StateNotifierProvider<HybridNotifier, Hybrid>((ref) { + return HybridNotifier(const Hybrid.initial()); +}); diff --git a/lib/data/data_providers/audio_notifier.dart b/lib/data/data_providers/audio_notifier.dart new file mode 100644 index 0000000..8981036 --- /dev/null +++ b/lib/data/data_providers/audio_notifier.dart @@ -0,0 +1,25 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class AudioNotifier extends StateNotifier<Audio> { + AudioNotifier(super.state); + + void resetToDefaults() { + state = state.copyWith(treble: 5.0, bass: 5.0, rearFront: 5.0); + } + + void setVolume(double newVal) { + state = state.copyWith(volume: newVal); + } + + void setTreble(double newVal) { + state = state.copyWith(treble: newVal); + } + + void setBass(double newVal) { + state = state.copyWith(bass: newVal); + } + + void setRearFront(double newVal) { + state = state.copyWith(rearFront: newVal); + } +} diff --git a/lib/data/data_providers/datetime_notifier.dart b/lib/data/data_providers/datetime_notifier.dart new file mode 100644 index 0000000..6947a34 --- /dev/null +++ b/lib/data/data_providers/datetime_notifier.dart @@ -0,0 +1,14 @@ +import 'package:flutter_ics_homescreen/data/models/date_time.dart'; +import 'package:flutter_ics_homescreen/export.dart'; + +class DateTimeNotifier extends StateNotifier<DateAndTime> { + DateTimeNotifier(super.state); + + void setDate(String newVal) { + state = state.copyWith(date: newVal); + } + + void setTime(String newVal) { + state = state.copyWith(time: newVal); + } +} diff --git a/lib/data/data_providers/hybrid_notifier.dart b/lib/data/data_providers/hybrid_notifier.dart new file mode 100644 index 0000000..a19bf58 --- /dev/null +++ b/lib/data/data_providers/hybrid_notifier.dart @@ -0,0 +1,91 @@ +import '../../export.dart'; + +class HybridNotifier extends StateNotifier<Hybrid> { + HybridNotifier(super.state); + + void setHybridState(HybridState hybridState) { + switch (hybridState) { + case HybridState.idle: + state = state.copyWith( + topArrowState: ArrowState.blue, + leftArrowState: ArrowState.blue, + rightArrowState: ArrowState.blue, + batteryState: BatteryState.white, + ); + break; + case HybridState.engineOutput: + state = state.copyWith( + topArrowState: ArrowState.red, + leftArrowState: ArrowState.red, + rightArrowState: ArrowState.blue, + batteryState: BatteryState.red, + ); + break; + case HybridState.regenerativeBreaking: + state = state.copyWith( + topArrowState: ArrowState.blue, + leftArrowState: ArrowState.blue, + rightArrowState: ArrowState.green, + batteryState: BatteryState.green); + + break; + case HybridState.baterryOutput: + state = state.copyWith( + topArrowState: ArrowState.blue, + leftArrowState: ArrowState.blue, + rightArrowState: ArrowState.yellow, + batteryState: BatteryState.yellow); + break; + default: + } + state = state.copyWith(hybridState: hybridState); + } + + void updateHybridState() { + // Variable to store the current state + HybridState currentState = state.hybridState; + + // Variable to store the previous state + HybridState previousState = currentState; + + // Variable to store the average speed value + double avgSpeed = 0.0; + + // Variable for storing the average value of RPM + double avgRpm = 0.0; + + // Variable to store the brake value state + bool brake = false; + + // Collect 10 samples + for (int i = 0; i < 10; i++) { + // Get the current values for speed, engine rpm and brake status + + // speed = vehicleProvider(); + // rpm = _rpmFromServer(); + // brake = _brakeFromServer(); + + // Calculate the average speed value + // avgSpeed = (avgSpeed * (i + 1) + speed) / (i + 2); + + // Calculate the average engine rpm + // avgRpm = (avgRpm * (i + 1) + rpm) / (i + 2); + } + + // define new state + // if (avgSpeed == 0 && avgRpm == 0) { + // currentState = HybridState.idle; + // } else if (avgRpm > 0 && avgSpeed > 0) { + // currentState = HybridState.engineOutput; + // } else if (avgRpm == 0 && brake) { + // currentState = HybridState.regenerativeBreaking; + // } else if (avgSpeed > 0 && avgRpm <= avgSpeed) { + // currentState = HybridState.baterryOutput; + // } + + // Zaktualizuj stan + if (currentState != previousState) { + state = state.copyWith(hybridState: currentState); + } + } +} diff --git a/lib/data/data_providers/signal_notifier.dart b/lib/data/data_providers/signal_notifier.dart new file mode 100644 index 0000000..3059174 --- /dev/null +++ b/lib/data/data_providers/signal_notifier.dart @@ -0,0 +1,14 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class SignalNotifier extends StateNotifier<Signals> { + SignalNotifier(super.state); + + void startListen() {} + void toggleBluetooth() { + state = state.copyWith(isBluetoothConnected: !state.isBluetoothConnected); + } + + void toggleWifi() { + state = state.copyWith(isWifiConnected: !state.isWifiConnected); + } +} diff --git a/lib/data/data_providers/units_notifier.dart b/lib/data/data_providers/units_notifier.dart new file mode 100644 index 0000000..f7c25aa --- /dev/null +++ b/lib/data/data_providers/units_notifier.dart @@ -0,0 +1,13 @@ +import '../../export.dart'; + +class UnitsNotifier extends StateNotifier<Units> { + UnitsNotifier(super.state); + + void setDistanceUnit(DistanceUnit unit) { + state = state.copyWith(distanceUnit: unit); + } + + void setTemperatureUnit(TemperatureUnit unit) { + state = state.copyWith(temperatureUnit: unit); + } +} diff --git a/lib/data/data_providers/users_notifier.dart b/lib/data/data_providers/users_notifier.dart new file mode 100644 index 0000000..8b48382 --- /dev/null +++ b/lib/data/data_providers/users_notifier.dart @@ -0,0 +1,49 @@ +import 'package:flutter_ics_homescreen/data/models/users.dart'; +import 'package:flutter_ics_homescreen/export.dart'; +import 'package:uuid/uuid.dart'; + +import '../models/user.dart'; + +class UsersNotifier extends StateNotifier<Users> { + UsersNotifier(super.state) { + loadUsers(); + } + + void loadUsers() { + state = state.copyWith(users: _users); + } + + final List<User> _users = [ + const User(id: '1', name: 'Heather'), + const User(id: '2', name: 'George'), + const User(id: '3', name: 'Riley'), + ]; + void selectUser(String userId) { + var seletedUser = state.users.firstWhere((user) => user.id == userId); + state = state.copyWith(selectedUser: seletedUser); + } + + void removeUser(String userId) { + state.users.removeWhere((user) => user.id == userId); + if (state.users.isNotEmpty) { + state = state.copyWith(selectedUser: state.users.first); + } + if (state.users.isEmpty) { + state = state.copyWith(selectedUser: const User(id: '', name: '')); + } + } + + void addUser(String userName) { + final id = const Uuid().v1(); + final user = User(id: id, name: userName); + + state.users.insert(0, user); + state = state.copyWith(selectedUser: state.users.first); + } + + void editUser(User user) { + // final id = const Uuid().v1(); + // final user = User(id: id, name: userName); + //_users.add(user); + } +} diff --git a/lib/data/data_providers/vehicle_notifier.dart b/lib/data/data_providers/vehicle_notifier.dart new file mode 100644 index 0000000..7842311 --- /dev/null +++ b/lib/data/data_providers/vehicle_notifier.dart @@ -0,0 +1,367 @@ +import 'dart:async'; + +import 'package:flutter_ics_homescreen/export.dart'; +import 'package:flutter/services.dart'; +import 'package:protos/protos.dart'; + +class VehicleNotifier extends StateNotifier<Vehicle> { + VehicleNotifier(super.state); + + late ClientChannel channel; + late VALClient stub; + + void updateSpeed(double newValue) { + state = state.copyWith(speed: newValue); + } + + void handleSignalsUpdate(EntryUpdate update) { + switch (update.entry.path) { + case VSSPath.vehicleSpeed: + if (update.entry.value.hasFloat()) { + state = state.copyWith(speed: update.entry.value.float); + } + break; + case VSSPath.vehicleInsideTemperature: + if (update.entry.value.hasFloat()) { + state = state.copyWith(insideTemperature: update.entry.value.float); + } + break; + case VSSPath.vehicleOutsideTemperature: + if (update.entry.value.hasFloat()) { + state = state.copyWith(outsideTemperature: update.entry.value.float); + } + break; + case VSSPath.vehicleRange: + if (update.entry.value.hasUint32()) { + state = state.copyWith(range: update.entry.value.uint32); + } + break; + case VSSPath.vehicleFuelLevel: + if (update.entry.value.hasFloat()) { + state = state.copyWith(fuelLevel: update.entry.value.float); + } + break; + // case VSSPath.vehicleMediaVolume: + // if (update.entry.value.hasInt32()) { + // ref + // .read(vehicleMediaVolume.notifier) + // .update((state) => state = update.entry.value.uint32); + // } + // break; + case VSSPath.vehicleIsChildLockActiveLeft: + if (update.entry.value.hasBool_12()) { + state = + state.copyWith(isChildLockActiveLeft: update.entry.value.bool_12); + } + break; + case VSSPath.vehicleIsChildLockActiveRight: + if (update.entry.value.hasBool_12()) { + state = state.copyWith( + isChildLockActiveRight: update.entry.value.bool_12); + } + break; + case VSSPath.vehicleEngineSpeed: + if (update.entry.value.hasUint32()) { + state = state.copyWith(engineSpeed: update.entry.value.uint32); + } + break; + case VSSPath.vehicleFrontLeftTire: + if (update.entry.value.hasUint32()) { + state = state.copyWith(frontLeftTire: update.entry.value.uint32); + } + break; + case VSSPath.vehicleFrontRightTire: + if (update.entry.value.hasUint32()) { + state = state.copyWith(frontRightTire: update.entry.value.uint32); + } + break; + case VSSPath.vehicleRearLeftTire: + if (update.entry.value.hasUint32()) { + state = state.copyWith(rearLeftTire: update.entry.value.uint32); + } + break; + case VSSPath.vehicleRearRightTire: + if (update.entry.value.hasUint32()) { + state = state.copyWith(rearRightTire: update.entry.value.uint32); + } + break; + + /// + case VSSPath.vehicleIsAirConditioningActive: + if (update.entry.value.hasBool_12()) { + state = state.copyWith( + isAirConditioningActive: update.entry.value.bool_12); + } + break; + case VSSPath.vehicleIsFrontDefrosterActive: + if (update.entry.value.hasBool_12()) { + state = state.copyWith( + isFrontDefrosterActive: update.entry.value.bool_12); + } + break; + case VSSPath.vehicleIsRearDefrosterActive: + if (update.entry.value.hasBool_12()) { + state = + state.copyWith(isRearDefrosterActive: update.entry.value.bool_12); + } + break; + case VSSPath.vehicleIsRecirculationActive: + if (update.entry.value.hasBool_12()) { + state = + state.copyWith(isRecirculationActive: update.entry.value.bool_12); + } + break; + case VSSPath.vehicleFanSpeed: + if (update.entry.value.hasUint32()) { + state = state.copyWith(fanSpeed: update.entry.value.uint32); + } + break; + + // default: + // debugPrint("ERROR: Unexpected path ${update.entry.path}"); + // break; + } + } + + void startListen() async { + String hostName = 'localhost'; + int port = 8080; + try { + var data = await rootBundle.loadString('config/config.yaml'); + final dynamic yamlMap = loadYaml(data); + + if (yamlMap.containsKey('hostname')) { + hostName = yamlMap['hostname']; + } + + if (yamlMap.containsKey('port')) { + port = yamlMap['port']; + } + } catch (e) { + //debugPrint('ERROR: Could not read from file: $configFile'); + debugPrint(e.toString()); + } + channel = ClientChannel( + hostName, + port: port, + options: const ChannelOptions( + credentials: ChannelCredentials.insecure(), + ), + ); + + debugPrint('Start Listen on port: $port'); + stub = VALClient(channel); + List<String> fewSignals = VSSPath().getSignalsList(); + var request = SubscribeRequest(); + for (int i = 0; i < fewSignals.length; i++) { + var entry = SubscribeEntry(); + entry.path = fewSignals[i]; + entry.fields.add(Field.FIELD_PATH); + entry.fields.add(Field.FIELD_VALUE); + request.entries.add(entry); + // _stub.subscribe(request).listen((value) async { + // //debugPrint(value.toString()); + // }); + } + try { + // ignore: unused_local_variable + Map<String, String> metadata = {}; + //var responseStream = _stub.subscribe(request); + stub.subscribe(request).listen((value) async { + for (var update in value.updates) { + if (!(update.hasEntry() && update.entry.hasPath())) continue; + handleSignalsUpdate(update); + } + }, onError: (stacktrace, errorDescriptor) { + debugPrint(stacktrace.toString()); + state = const Vehicle.initialForDebug(); + + }); + } catch (e) { + debugPrint(e.toString()); + } + } + + void setChildLock({required String side}) { + var helper = ValClientHelper(channel: channel, stub: stub); + try { + switch (side) { + case 'left': + helper.setBool( + VSSPath.vehicleIsChildLockActiveLeft, + !state.isChildLockActiveLeft, + false, + ); + state = state.copyWith( + isChildLockActiveLeft: !state.isChildLockActiveLeft); + break; + case 'right': + helper.setBool( + VSSPath.vehicleIsChildLockActiveRight, + !state.isChildLockActiveRight, + false, + ); + state = state.copyWith( + isChildLockActiveRight: !state.isChildLockActiveRight); + break; + default: + debugPrint("ERROR: Unexpected side value $side}"); + break; + } + } catch (e) { + debugPrint(e.toString()); + } + } + + void updateFanSpeed(int newValue) { + state = state.copyWith(fanSpeed: newValue); + } + + void setHVACMode({required String mode}) { + var helper = ValClientHelper(channel: channel, stub: stub); + try { + switch (mode) { + case 'airCondition': + helper.setBool( + VSSPath.vehicleIsAirConditioningActive, + !state.isAirConditioningActive, + false, + ); + state = state.copyWith( + isAirConditioningActive: !state.isAirConditioningActive); + break; + case 'frontDefrost': + helper.setBool( + VSSPath.vehicleIsFrontDefrosterActive, + !state.isFrontDefrosterActive, + false, + ); + state = state.copyWith( + isFrontDefrosterActive: !state.isFrontDefrosterActive); + break; + case 'rearDefrost': + helper.setBool( + VSSPath.vehicleIsRearDefrosterActive, + !state.isRearDefrosterActive, + false, + ); + state = state.copyWith( + isRearDefrosterActive: !state.isRearDefrosterActive); + break; + case 'recirculation': + helper.setBool( + VSSPath.vehicleIsRecirculationActive, + !state.isRecirculationActive, + false, + ); + state = state.copyWith( + isRecirculationActive: !state.isRecirculationActive); + break; + default: + debugPrint("ERROR: Unexpected mode value $mode}"); + break; + } + } catch (e) { + debugPrint(e.toString()); + } + } + + void setInitialState() { + var speed = state.speed; + var rpm = state.engineSpeed; + var fuelLevel = state.fuelLevel; + var insideTemp = state.insideTemperature; + var outsideTemp = state.outsideTemperature; + var range = state.range; + var psi = state.frontLeftTire; + var actualSpeed = 0.0; + var actualRpm = 0; + var actualFuelLevel = 0.0; + var actualInsideTemp = 0.0; + var actualOutsideTemp = 0.0; + var actualRange = 0; + var actualPsi = 0; + + state = const Vehicle.initial(); + Timer speedTimer = + Timer.periodic(const Duration(milliseconds: 600), (timer) { + actualSpeed = actualSpeed + 10; + + if (actualSpeed > speed) { + actualSpeed = speed; + + timer.cancel(); + } + state = state.copyWith(speed: actualSpeed); + }); + Timer rpmTimer = Timer.periodic(const Duration(milliseconds: 400), (timer) { + actualRpm = actualRpm + 150; + + if (actualRpm > rpm) { + actualRpm = rpm; + timer.cancel(); + } + state = state.copyWith(engineSpeed: actualRpm); + }); + Timer fuelLevelTimer = + Timer.periodic(const Duration(milliseconds: 400), (timer) { + actualFuelLevel = actualFuelLevel + 1; + + if (actualFuelLevel > fuelLevel) { + actualFuelLevel = fuelLevel; + + timer.cancel(); + } + state = state.copyWith(fuelLevel: actualFuelLevel); + }); + Timer outsideTemperatureTimer = + Timer.periodic(const Duration(milliseconds: 300), (timer) { + actualOutsideTemp = actualOutsideTemp + 0.5; + + if (actualOutsideTemp > outsideTemp) { + actualOutsideTemp = outsideTemp; + + timer.cancel(); + } + state = state.copyWith(outsideTemperature: actualOutsideTemp); + }); + Timer insideTemperatureTimer = + Timer.periodic(const Duration(milliseconds: 300), (timer) { + actualInsideTemp = actualInsideTemp + 0.5; + + if (actualInsideTemp > insideTemp) { + actualInsideTemp = insideTemp; + + timer.cancel(); + } + state = state.copyWith(insideTemperature: actualInsideTemp); + }); + Timer rangeTimer = + Timer.periodic(const Duration(milliseconds: 300), (timer) { + actualRange = actualRange + 5; + + if (actualRange > range) { + actualRange = range; + + timer.cancel(); + } + state = state.copyWith(range: actualRange); + }); + Timer psiTimer = + Timer.periodic(const Duration(milliseconds: 1200), (timer) { + actualPsi = actualPsi + 5; + + if (actualPsi > psi) { + actualPsi = psi; + + timer.cancel(); + } + state = state.copyWith( + frontLeftTire: actualPsi, + rearLeftTire: actualPsi, + frontRightTire: actualPsi, + rearRightTire: actualPsi, + ); + }); + } +} diff --git a/lib/data/data_providers/vss_provider.dart b/lib/data/data_providers/vss_provider.dart new file mode 100644 index 0000000..ca02de6 --- /dev/null +++ b/lib/data/data_providers/vss_provider.dart @@ -0,0 +1,105 @@ +import 'package:flutter_ics_homescreen/export.dart'; +import 'package:protos/protos.dart'; + +class VSS { + static handleSignalUpdates( + EntryUpdate update, + ) { + //final ref = ProviderContainer().read(vehicleStateProvider); + switch (update.entry.path) { + case VSSPath.vehicleSpeed: + if (update.entry.value.hasFloat()) { + //print(ref); + } + break; + // case VSSPath.vehicleInsideTemperature: + // if (update.entry.value.hasFloat()) { + // ref + // .read(vehicleInsideTemperature.notifier) + // .update((state) => state = update.entry.value.float); + // } + // break; + // case VSSPath.vehicleOutsideTemperature: + // if (update.entry.value.hasFloat()) { + // ref + // .read(vehicleOutSideTemperature.notifier) + // .update((state) => state = update.entry.value.float); + // } + // break; + // case VSSPath.vehicleRange: + // if (update.entry.value.hasInt32()) { + // ref + // .read(vehicleRange.notifier) + // .update((state) => state = update.entry.value.uint32); + // } + // break; + // case VSSPath.vehicleFuelLevel: + // if (update.entry.value.hasInt32()) { + // ref + // .read(vehicleFuelLevel.notifier) + // .update((state) => state = update.entry.value.uint32); + // } + // break; + // case VSSPath.vehicleMediaVolume: + // if (update.entry.value.hasInt32()) { + // ref + // .read(vehicleMediaVolume.notifier) + // .update((state) => state = update.entry.value.uint32); + // } + // break; + // case VSSPath.vehicleIsChildLockActiveLeft: + // if (update.entry.value.hasBool_12()) { + // ref + // .read(vehicleIsChildLockActiveLeft.notifier) + // .update((state) => state = update.entry.value.bool_12); + // } + // break; + // case VSSPath.vehicleIsChildLockActiveRight: + // if (update.entry.value.hasBool_12()) { + // ref + // .read(vehicleIsChildLockActiveRight.notifier) + // .update((state) => state = update.entry.value.bool_12); + // } + // break; + // case VSSPath.vehicleEngineSpeed: + // if (update.entry.value.hasFloat()) { + // ref + // .read(vehicleEngineSpeed.notifier) + // .update((state) => state = update.entry.value.float); + // } + // break; + // case VSSPath.vehicleFrontLeftTire: + // if (update.entry.value.hasFloat()) { + // ref + // .read(vehicleFrontLeftTire.notifier) + // .update((state) => state = update.entry.value.float); + // } + // break; + // case VSSPath.vehicleFrontRightTire: + // if (update.entry.value.hasFloat()) { + // ref + // .read(vehicleFrontRightTire.notifier) + // .update((state) => state = update.entry.value.float); + // } + // break; + // case VSSPath.vehicleRearLeftTire: + // if (update.entry.value.hasFloat()) { + // ref + // .read(vehicleRearLeftTire.notifier) + // .update((state) => state = update.entry.value.float); + // } + // break; + // case VSSPath.vehicleRearRightTire: + // if (update.entry.value.hasFloat()) { + // ref + // .read(vehicleRearRightTire.notifier) + // .update((state) => state = update.entry.value.float); + // } + // break; + + default: + debugPrint("ERROR: Unexpected path ${update.entry.path}"); + break; + } + } +} diff --git a/lib/data/models/audio.dart b/lib/data/models/audio.dart new file mode 100644 index 0000000..69df18b --- /dev/null +++ b/lib/data/models/audio.dart @@ -0,0 +1,84 @@ +import 'dart:convert'; + +import 'package:flutter_ics_homescreen/export.dart'; + +@immutable +class Audio { + final double volume; + final double treble; + final double bass; + final double rearFront; + const Audio({ + required this.volume, + required this.treble, + required this.bass, + required this.rearFront, + }); + + const Audio.initial() + : volume = 5.0, + treble = 5.0, + bass = 5.0, + rearFront = 5.0; + + + Audio copyWith({ + double? volume, + double? treble, + double? bass, + double? rearFront, + }) { + return Audio( + volume: volume ?? this.volume, + treble: treble ?? this.treble, + bass: bass ?? this.bass, + rearFront: rearFront ?? this.rearFront, + ); + } + + Map<String, dynamic> toMap() { + return { + 'volume': volume, + 'treble': treble, + 'bass': bass, + 'rearFront': rearFront, + }; + } + + factory Audio.fromMap(Map<String, dynamic> map) { + return Audio( + volume: map['volume']?.toDouble() ?? 0.0, + treble: map['treble']?.toDouble() ?? 0.0, + bass: map['bass']?.toDouble() ?? 0.0, + rearFront: map['rearFront']?.toDouble() ?? 0.0, + ); + } + + String toJson() => json.encode(toMap()); + + factory Audio.fromJson(String source) => Audio.fromMap(json.decode(source)); + + @override + String toString() { + return 'Audio(volume: $volume, treble: $treble, bass: $bass, rearFront: $rearFront)'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is Audio && + other.volume == volume && + other.treble == treble && + other.bass == bass && + other.rearFront == rearFront; + } + + @override + int get hashCode { + return volume.hashCode ^ + treble.hashCode ^ + bass.hashCode ^ + rearFront.hashCode; + } +} diff --git a/lib/data/models/connections_signals.dart b/lib/data/models/connections_signals.dart new file mode 100644 index 0000000..cd6d695 --- /dev/null +++ b/lib/data/models/connections_signals.dart @@ -0,0 +1,92 @@ +import 'dart:convert'; + +import 'package:flutter_ics_homescreen/export.dart'; + +@immutable +class Signals { + final bool isBluetoothConnected; + final bool isDataConnected; + final bool isWifiConnected; + final int dataSignalStrenght; + final int wifiSignalStrenght; + const Signals({ + required this.isBluetoothConnected, + required this.isDataConnected, + required this.isWifiConnected, + required this.dataSignalStrenght, + required this.wifiSignalStrenght, + }); + const Signals.initial() + : isBluetoothConnected = false, + isDataConnected = false, + isWifiConnected = false, + dataSignalStrenght = 0, + wifiSignalStrenght = 0; + + Signals copyWith({ + bool? isBluetoothConnected, + bool? isDataConnected, + bool? isWifiConnected, + int? dataSignalStrenght, + int? wifiSignalStrenght, + }) { + return Signals( + isBluetoothConnected: isBluetoothConnected ?? this.isBluetoothConnected, + isDataConnected: isDataConnected ?? this.isDataConnected, + isWifiConnected: isWifiConnected ?? this.isWifiConnected, + dataSignalStrenght: dataSignalStrenght ?? this.dataSignalStrenght, + wifiSignalStrenght: wifiSignalStrenght ?? this.wifiSignalStrenght, + ); + } + + Map<String, dynamic> toMap() { + return { + 'isBluetoothConnected': isBluetoothConnected, + 'isDataConnected': isDataConnected, + 'isWifiConnected': isWifiConnected, + 'dataSignalStrenght': dataSignalStrenght, + 'wifiSignalStrenght': wifiSignalStrenght, + }; + } + + factory Signals.fromMap(Map<String, dynamic> map) { + return Signals( + isBluetoothConnected: map['isBluetoothConnected'] ?? false, + isDataConnected: map['isDataConnected'] ?? false, + isWifiConnected: map['isWifiConnected'] ?? false, + dataSignalStrenght: map['dataSignalStrenght']?.toInt() ?? 0, + wifiSignalStrenght: map['wifiSignalStrenght']?.toInt() ?? 0, + ); + } + + String toJson() => json.encode(toMap()); + + factory Signals.fromJson(String source) => + Signals.fromMap(json.decode(source)); + + @override + String toString() { + return 'Signals(isBluetoothConnected: $isBluetoothConnected, isDataConnected: $isDataConnected, isWifiConnected: $isWifiConnected, dataSignalStrenght: $dataSignalStrenght, wifiSignalStrenght: $wifiSignalStrenght)'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is Signals && + other.isBluetoothConnected == isBluetoothConnected && + other.isDataConnected == isDataConnected && + other.isWifiConnected == isWifiConnected && + other.dataSignalStrenght == dataSignalStrenght && + other.wifiSignalStrenght == wifiSignalStrenght; + } + + @override + int get hashCode { + return isBluetoothConnected.hashCode ^ + isDataConnected.hashCode ^ + isWifiConnected.hashCode ^ + dataSignalStrenght.hashCode ^ + wifiSignalStrenght.hashCode; + } +} diff --git a/lib/data/models/date_time.dart b/lib/data/models/date_time.dart new file mode 100644 index 0000000..62d7743 --- /dev/null +++ b/lib/data/models/date_time.dart @@ -0,0 +1,59 @@ +import 'dart:convert'; + +import 'package:flutter_ics_homescreen/export.dart'; +import 'package:intl/intl.dart'; + +@immutable +class DateAndTime { + final String date; + final String time; + const DateAndTime({ + required this.date, + required this.time, + }); + + DateAndTime.initial() + : date = DateFormat().add_yMMMMd().format(DateTime.now()), + time = DateFormat('hh:mm a').format(DateTime.now()); + DateAndTime copyWith({ + String? date, + String? time, + }) { + return DateAndTime( + date: date ?? this.date, + time: time ?? this.time, + ); + } + + Map<String, dynamic> toMap() { + return { + 'date': date, + 'time': time, + }; + } + + factory DateAndTime.fromMap(Map<String, dynamic> map) { + return DateAndTime( + date: map['date'], + time: map['time'], + ); + } + + String toJson() => json.encode(toMap()); + + factory DateAndTime.fromJson(String source) => + DateAndTime.fromMap(json.decode(source)); + + @override + String toString() => 'DateAndTime(date: $date, time: $time)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is DateAndTime && other.date == date && other.time == time; + } + + @override + int get hashCode => date.hashCode ^ time.hashCode; +} diff --git a/lib/data/models/hybrid.dart b/lib/data/models/hybrid.dart new file mode 100644 index 0000000..d567f14 --- /dev/null +++ b/lib/data/models/hybrid.dart @@ -0,0 +1,98 @@ +enum HybridState { + idle, + engineOutput, + regenerativeBreaking, + baterryOutput, +} + +enum ArrowState { blue, red, green, yellow } + +enum BatteryState { white, red, green, yellow, orange } + +class Hybrid { + final HybridState hybridState; + final ArrowState topArrowState; + final ArrowState leftArrowState; + final ArrowState rightArrowState; + final BatteryState batteryState; + Hybrid({ + required this.hybridState, + required this.topArrowState, + required this.leftArrowState, + required this.rightArrowState, + required this.batteryState, + }); + + const Hybrid.initial() + : hybridState = HybridState.idle, + topArrowState = ArrowState.blue, + leftArrowState = ArrowState.blue, + rightArrowState = ArrowState.blue, + batteryState = BatteryState.white; + + Hybrid copyWith({ + HybridState? hybridState, + ArrowState? topArrowState, + ArrowState? leftArrowState, + ArrowState? rightArrowState, + BatteryState? batteryState, + }) { + return Hybrid( + hybridState: hybridState ?? this.hybridState, + topArrowState: topArrowState ?? this.topArrowState, + leftArrowState: leftArrowState ?? this.leftArrowState, + rightArrowState: rightArrowState ?? this.rightArrowState, + batteryState: batteryState ?? this.batteryState, + ); + } + + // Map<String, dynamic> toMap() { + // return { + // 'hybridState': hybridState.toMap(), + // 'topArrowState': topArrowState.toMap(), + // 'leftArrowState': leftArrowState.toMap(), + // 'rightArrowState': rightArrowState.toMap(), + // 'batteryState': batteryState.toMap(), + // }; + // } + + // factory Hybrid.fromMap(Map<String, dynamic> map) { + // return Hybrid( + // hybridState: HybridState.fromMap(map['hybridState']), + // topArrowState: ArrowState.fromMap(map['topArrowState']), + // leftArrowState: ArrowState.fromMap(map['leftArrowState']), + // rightArrowState: ArrowState.fromMap(map['rightArrowState']), + // batteryState: BatteryState.fromMap(map['batteryState']), + // ); + // } + + // String toJson() => json.encode(toMap()); + + // factory Hybrid.fromJson(String source) => Hybrid.fromMap(json.decode(source)); + + @override + String toString() { + return 'Hybrid(hybridState: $hybridState, topArrowState: $topArrowState, leftArrowState: $leftArrowState, rightArrowState: $rightArrowState, batteryState: $batteryState)'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is Hybrid && + other.hybridState == hybridState && + other.topArrowState == topArrowState && + other.leftArrowState == leftArrowState && + other.rightArrowState == rightArrowState && + other.batteryState == batteryState; + } + + @override + int get hashCode { + return hybridState.hashCode ^ + topArrowState.hashCode ^ + leftArrowState.hashCode ^ + rightArrowState.hashCode ^ + batteryState.hashCode; + } +} diff --git a/lib/data/models/units.dart b/lib/data/models/units.dart new file mode 100644 index 0000000..9e71213 --- /dev/null +++ b/lib/data/models/units.dart @@ -0,0 +1,45 @@ +import '../../export.dart'; + +enum DistanceUnit { kilometers, miles } + +enum TemperatureUnit { celsius, fahrenheit } + +@immutable +class Units { + final DistanceUnit distanceUnit; + final TemperatureUnit temperatureUnit; + + const Units( + this.distanceUnit, + this.temperatureUnit, + ); + const Units.initial() + : distanceUnit = DistanceUnit.kilometers, + temperatureUnit = TemperatureUnit.celsius; + + Units copyWith({ + DistanceUnit? distanceUnit, + TemperatureUnit? temperatureUnit, + }) { + return Units( + distanceUnit ?? this.distanceUnit, + temperatureUnit ?? this.temperatureUnit, + ); + } + + @override + String toString() => + 'Units(distanceUnit: $distanceUnit, temperatureUnit: $temperatureUnit)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is Units && + other.distanceUnit == distanceUnit && + other.temperatureUnit == temperatureUnit; + } + + @override + int get hashCode => distanceUnit.hashCode ^ temperatureUnit.hashCode; +} diff --git a/lib/data/models/user.dart b/lib/data/models/user.dart new file mode 100644 index 0000000..b163c5e --- /dev/null +++ b/lib/data/models/user.dart @@ -0,0 +1,54 @@ +import 'dart:convert'; + +import 'package:flutter_ics_homescreen/export.dart'; + +@immutable +class User { + final String id; + final String name; + const User({ + required this.id, + required this.name, + }); + + User copyWith({ + String? id, + String? name, + }) { + return User( + id: id ?? this.id, + name: name ?? this.name, + ); + } + + Map<String, dynamic> toMap() { + return { + 'id': id, + 'name': name, + }; + } + + factory User.fromMap(Map<String, dynamic> map) { + return User( + id: map['id'] ?? '', + name: map['name'] ?? '', + ); + } + + String toJson() => json.encode(toMap()); + + factory User.fromJson(String source) => User.fromMap(json.decode(source)); + + @override + String toString() => 'User(id: $id, name: $name)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is User && other.id == id && other.name == name; + } + + @override + int get hashCode => id.hashCode ^ name.hashCode; +} diff --git a/lib/data/models/users.dart b/lib/data/models/users.dart new file mode 100644 index 0000000..9b4d027 --- /dev/null +++ b/lib/data/models/users.dart @@ -0,0 +1,64 @@ +import 'dart:convert'; + +import 'package:flutter_ics_homescreen/data/models/user.dart'; +import 'package:flutter_ics_homescreen/export.dart'; +// ignore: depend_on_referenced_packages +import 'package:collection/collection.dart'; + +@immutable +class Users { + final List<User> users; + final User selectedUser; + const Users({ + required this.users, + required this.selectedUser, + }); + Users.initial() + //: users = <User>[], + : users = [], + selectedUser = const User(id: '', name: ''); + + Users copyWith({ + List<User>? users, + User? selectedUser, + }) { + return Users( + users: users ?? this.users, + selectedUser: selectedUser ?? this.selectedUser, + ); + } + + Map<String, dynamic> toMap() { + return { + 'users': users.map((x) => x.toMap()).toList(), + 'selectedUser': selectedUser.toMap(), + }; + } + + factory Users.fromMap(Map<String, dynamic> map) { + return Users( + users: List<User>.from(map['users']?.map((x) => User.fromMap(x))), + selectedUser: User.fromMap(map['selectedUser']), + ); + } + + String toJson() => json.encode(toMap()); + + factory Users.fromJson(String source) => Users.fromMap(json.decode(source)); + + @override + String toString() => 'Users(users: $users, selectedUser: $selectedUser)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + final listEquals = const DeepCollectionEquality().equals; + + return other is Users && + listEquals(other.users, users) && + other.selectedUser == selectedUser; + } + + @override + int get hashCode => users.hashCode ^ selectedUser.hashCode; +} diff --git a/lib/data/models/vehicle.dart b/lib/data/models/vehicle.dart new file mode 100644 index 0000000..f27b750 --- /dev/null +++ b/lib/data/models/vehicle.dart @@ -0,0 +1,233 @@ +import 'dart:convert'; + +import '../../export.dart'; + +@immutable +class Vehicle { + final double speed; + final double insideTemperature; + final double outsideTemperature; + final int range; + final double fuelLevel; + final int mediaVolume; + final bool isChildLockActiveLeft; + final bool isChildLockActiveRight; + final int engineSpeed; + final int frontLeftTire; + final int frontRightTire; + final int rearLeftTire; + final int rearRightTire; + final bool isAirConditioningActive; + final bool isFrontDefrosterActive; + final bool isRearDefrosterActive; + final bool isRecirculationActive; + final int fanSpeed; + + const Vehicle( + this.speed, + this.insideTemperature, + this.outsideTemperature, + this.range, + this.fuelLevel, + this.mediaVolume, + this.isChildLockActiveLeft, + this.isChildLockActiveRight, + this.engineSpeed, + this.frontLeftTire, + this.frontRightTire, + this.rearLeftTire, + this.rearRightTire, + this.isAirConditioningActive, + this.isFrontDefrosterActive, + this.isRearDefrosterActive, + this.isRecirculationActive, + this.fanSpeed); + + const Vehicle.initial() + : speed = 0, + insideTemperature = 0, + outsideTemperature = 0, + range = 0, + fuelLevel = 0, + mediaVolume = 50, + isChildLockActiveLeft = false, + isChildLockActiveRight = true, + engineSpeed = 0, + frontLeftTire = 33, + frontRightTire = 31, + rearLeftTire = 31, + rearRightTire = 32, + isAirConditioningActive = false, + isFrontDefrosterActive = false, + isRearDefrosterActive = false, + isRecirculationActive = false, + fanSpeed = 0; + + const Vehicle.initialForDebug() + : speed = 60, + insideTemperature = 25, + outsideTemperature = 32.0, + range = 21, + fuelLevel = 49.5, + mediaVolume = 50, + isChildLockActiveLeft = false, + isChildLockActiveRight = true, + engineSpeed = 6500, + frontLeftTire = 33, + frontRightTire = 31, + rearLeftTire = 31, + rearRightTire = 32, + isAirConditioningActive = false, + isFrontDefrosterActive = false, + isRearDefrosterActive = false, + isRecirculationActive = false, + fanSpeed = 0; + + Vehicle copyWith( + {double? speed, + double? insideTemperature, + double? outsideTemperature, + int? range, + double? fuelLevel, + int? mediaVolume, + bool? isChildLockActiveLeft, + bool? isChildLockActiveRight, + int? engineSpeed, + int? frontLeftTire, + int? frontRightTire, + int? rearLeftTire, + int? rearRightTire, + bool? isAirConditioningActive, + bool? isFrontDefrosterActive, + bool? isRearDefrosterActive, + bool? isRecirculationActive, + int? fanSpeed}) { + return Vehicle( + speed ?? this.speed, + insideTemperature ?? this.insideTemperature, + outsideTemperature ?? this.outsideTemperature, + range ?? this.range, + fuelLevel ?? this.fuelLevel, + mediaVolume ?? this.mediaVolume, + isChildLockActiveLeft ?? this.isChildLockActiveLeft, + isChildLockActiveRight ?? this.isChildLockActiveRight, + engineSpeed ?? this.engineSpeed, + frontLeftTire ?? this.frontLeftTire, + frontRightTire ?? this.frontRightTire, + rearLeftTire ?? this.rearLeftTire, + rearRightTire ?? this.rearRightTire, + isAirConditioningActive ?? this.isAirConditioningActive, + isFrontDefrosterActive ?? this.isFrontDefrosterActive, + isRearDefrosterActive ?? this.isRearDefrosterActive, + isRecirculationActive ?? this.isRecirculationActive, + fanSpeed ?? this.fanSpeed); + } + + Map<String, dynamic> toMap() { + return { + 'speed': speed, + 'insideTemperature': insideTemperature, + 'outsideTemperature': outsideTemperature, + 'range': range, + 'fuelLevel': fuelLevel, + 'mediaVolume': mediaVolume, + 'isChildLockActiveLeft': isChildLockActiveLeft, + 'isChildLockActiveRight': isChildLockActiveRight, + 'engineSpeed': engineSpeed, + 'frontLeftTire': frontLeftTire, + 'frontRightTire': frontRightTire, + 'rearLeftTire': rearLeftTire, + 'rearRightTire': rearRightTire, + 'isAirConditioningActive': isAirConditioningActive, + 'isFrontDefrosterActive': isFrontDefrosterActive, + 'isRearDefrosterActive': isRearDefrosterActive, + 'isRecirculationActive': isRecirculationActive, + 'fanSpeed': fanSpeed + }; + } + + factory Vehicle.fromMap(Map<String, dynamic> map) { + return Vehicle( + map['speed']?.toDouble() ?? 0.0, + map['insideTemperature']?.toDouble() ?? 0.0, + map['outsideTemperature']?.toDouble() ?? 0.0, + map['range']?.toInt() ?? 0, + map['fuelLevel']?.toDouble() ?? 0.0, + map['mediaVolume']?.toInt() ?? 0, + map['isChildLockActiveLeft'] ?? false, + map['isChildLockActiveRight'] ?? false, + map['engineSpeed']?.toInt() ?? 0, + map['frontLeftTire']?.toInt() ?? 0, + map['frontRightTire']?.toInt() ?? 0, + map['rearLeftTire']?.toInt() ?? 0, + map['rearRightTire']?.toInt() ?? 0, + map['isAirConditioningActive'] ?? false, + map['isFrontDefrosterActive'] ?? false, + map['isRearDefrosterActive'] ?? false, + map['isRecirculationActive'] ?? false, + map['fanSpeed'] ?? 0, + ); + } + + String toJson() => json.encode(toMap()); + + factory Vehicle.fromJson(String source) => + Vehicle.fromMap(json.decode(source)); + + @override + String toString() { + return 'Vehicle(speed: $speed, insideTemperature: $insideTemperature, outsideTemperature: $outsideTemperature, range: $range, fuelLevel: $fuelLevel, mediaVolume: $mediaVolume, isChildLockActiveLeft: $isChildLockActiveLeft, isChildLockActiveRight: $isChildLockActiveRight, engineSpeed: $engineSpeed, frontLeftTire: $frontLeftTire, frontRightTire: $frontRightTire, rearLeftTire: $rearLeftTire, rearRightTire: $rearRightTire, isAirConditioningActive: $isAirConditioningActive, isFrontDefrosterActive: $isFrontDefrosterActive, isRearDefrosterActive: $isRearDefrosterActive, isRecirculationActive: $isRecirculationActive,fanSpeed:$fanSpeed)'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is Vehicle && + other.speed == speed && + other.insideTemperature == insideTemperature && + other.outsideTemperature == outsideTemperature && + other.range == range && + other.fuelLevel == fuelLevel && + other.mediaVolume == mediaVolume && + other.isChildLockActiveLeft == isChildLockActiveLeft && + other.isChildLockActiveRight == isChildLockActiveRight && + other.engineSpeed == engineSpeed && + other.frontLeftTire == frontLeftTire && + other.frontRightTire == frontRightTire && + other.rearLeftTire == rearLeftTire && + other.rearRightTire == rearRightTire && + other.isAirConditioningActive == isAirConditioningActive && + other.isFrontDefrosterActive == isFrontDefrosterActive && + other.isRearDefrosterActive == isRearDefrosterActive && + other.isRecirculationActive == isRecirculationActive && + other.fanSpeed == fanSpeed; + } + + @override + int get hashCode { + return speed.hashCode ^ + insideTemperature.hashCode ^ + outsideTemperature.hashCode ^ + range.hashCode ^ + fuelLevel.hashCode ^ + mediaVolume.hashCode ^ + isChildLockActiveLeft.hashCode ^ + isChildLockActiveRight.hashCode ^ + engineSpeed.hashCode ^ + frontLeftTire.hashCode ^ + frontRightTire.hashCode ^ + rearLeftTire.hashCode ^ + rearRightTire.hashCode ^ + isAirConditioningActive.hashCode ^ + isFrontDefrosterActive.hashCode ^ + isRearDefrosterActive.hashCode ^ + isRecirculationActive.hashCode ^ + fanSpeed.hashCode; + } +// } +// / class VehicleNotifier extends StateNotifier<Vehicle> { +// // VehicleNotifier() : super(Vehicle()); + +// // } +} diff --git a/lib/data/theme/theme.dart b/lib/data/theme/theme.dart new file mode 100644 index 0000000..a04d03d --- /dev/null +++ b/lib/data/theme/theme.dart @@ -0,0 +1,43 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +// ignore_for_file: overridden_fields + +ThemeData theme = ThemeData( + scaffoldBackgroundColor: const Color(0xFF0D113F), + useMaterial3: false, + fontFamily: GoogleFonts.firaSans().fontFamily, + // Define the default brightness and colors. + colorScheme: ColorScheme.fromSeed( + seedColor: Colors.blue, + // ··· + brightness: Brightness.dark, + ), + + // Define the default `TextTheme`. Use this to specify the default + // text styling for headlines, titles, bodies of text, and more. + textTheme: TextTheme( + + displayLarge: const TextStyle( + fontSize: 72, + fontWeight: FontWeight.bold, + + ), + // ··· + titleLarge: GoogleFonts.oswald( + fontSize: 30, + fontStyle: FontStyle.italic, + color: Colors.red + ), + + titleMedium: GoogleFonts.firaSans( + color: AGLDemoColors.periwinkleColor, fontSize: 40), + bodyMedium: GoogleFonts.firaSans(color: AGLDemoColors.periwinkleColor), + + + ), + appBarTheme: const AppBarTheme( + backgroundColor: Colors.blue, + foregroundColor: Colors.white, //here you can give the text color + titleTextStyle: TextStyle() + ), +); diff --git a/lib/export.dart b/lib/export.dart new file mode 100644 index 0000000..08dff4f --- /dev/null +++ b/lib/export.dart @@ -0,0 +1,101 @@ +export 'data/data_providers/app.dart'; +export 'data/data_providers/app_provider.dart'; +export 'presentation/router/routes/routes.dart'; +export 'data/data_providers/vss_provider.dart'; +export 'data/theme/theme.dart'; + +//Models +export 'data/models/vehicle.dart'; +export 'data/models/units.dart'; +export 'data/models/audio.dart'; +export 'data/models/connections_signals.dart'; +export 'data/models/hybrid.dart'; + +//Screens +export 'presentation/screens/home/home.dart'; +export 'presentation/screens/home/widgets/custom_tile.dart'; +export 'presentation/screens/dashboard/dashboard.dart'; +export 'presentation/screens/dashboard/widgets/hybrid/hybrid.dart'; +export 'presentation/screens/dashboard/widgets/car_status.dart'; +export 'presentation/screens/dashboard/widgets/range.dart'; +export 'presentation/screens/dashboard/widgets/temperature.dart'; +export 'presentation/screens/dashboard/widgets/circle_indicator.dart'; +export 'presentation/screens/dashboard/widgets/child_lock.dart'; +export 'presentation/screens/dashboard/widgets/hybrid_mode.dart'; +export 'presentation/common_widget/custom_bottom_bar.dart'; +export 'presentation/common_widget/custom_top_bar.dart'; +export 'presentation/screens/media_player/media_player.dart'; +export 'presentation/screens/hvac/hvac.dart'; +export 'presentation/screens/settings/settings.dart'; +export 'presentation/screens/settings/widgets/settings_list_tile.dart'; +export 'presentation/screens/settings/settings_screens/date_time/date_time_screen.dart'; +export 'presentation/screens/settings/settings_screens/bluetooth/bluetooth_screen.dart'; +export 'presentation/screens/settings/settings_screens/wifi/wifi_screen.dart'; +export 'presentation/screens/settings/settings_screens/wired/wired_screen.dart'; +export 'presentation/screens/settings/settings_screens/audio_settings/audio_settings_screen.dart'; +export 'presentation/screens/settings/settings_screens/profiles/profiles_screen.dart'; +export 'presentation/screens/settings/settings_screens/units/units_screen.dart'; +export 'presentation/screens/settings/settings_screens/version_info/version_info_screend.dart'; +export 'presentation/screens/settings/settings_screens/units/distance/distance_unit_screen.dart'; +export 'presentation/screens/settings/settings_screens/units/temperature/temperature_unit_screen.dart'; +export 'package:flutter_ics_homescreen/presentation/screens/settings/settings_screens/profiles/widgets/new_profile_screen.dart'; + +export 'presentation/screens/apps/apps.dart'; +export 'presentation/screens/splash/splash.dart'; +export 'presentation/screens/splash/widget/splash_content.dart'; +//export 'presentation/screens/apps/apps_content.dart'; +export 'presentation/screens/hvac/hvac_content.dart'; +export 'presentation/screens/media_player/media_controls.dart'; +export 'presentation/screens/media_player/play_list_table.dart'; +export 'presentation/screens/media_player/player_navigation.dart'; +export 'presentation/screens/media_player/segmented_buttons.dart'; +export 'presentation/screens/media_player/media_content.dart'; +export 'presentation/screens/hvac/widgets/climate_controls.dart'; +export 'presentation/screens/hvac/widgets/fan_focus.dart'; +export 'presentation/screens/hvac/widgets/fan_speed_controls.dart'; +export 'presentation/screens/hvac/widgets/temperature_control.dart'; +export 'presentation/common_widget/custom_title.dart'; +export 'presentation/screens/weather/weather.dart'; +export 'presentation/screens/weather/hourly_forecast.dart'; +export 'presentation/screens/clock/clock.dart'; + +export 'core/utils/widgets/back_button.dart'; +export 'core/constants/vss_path.dart'; +export 'core/constants/val_client_helper.dart'; +export 'core/constants/constants.dart'; +export 'core/constants/paths.dart'; +//Common widgets +export 'presentation/common_widget/settings_top_bar.dart'; +export 'presentation/common_widget/generic_button.dart'; + +//Packages +export 'package:flow_builder/flow_builder.dart'; +export 'package:flutter_riverpod/flutter_riverpod.dart'; +export 'package:google_fonts/google_fonts.dart'; +export 'package:flutter_analog_clock/flutter_analog_clock.dart'; +export 'package:yaml/yaml.dart'; +export 'package:lottie/lottie.dart'; +//export 'package:new_virtual_keyboard/virtual_keyboard.dart'; + + +//export 'package:intl/intl.dart'; + +//export 'package:protos/protos.dart'; + +//other +export 'dart:typed_data'; +export 'dart:io'; + +export 'package:flutter/material.dart'; +export 'package:flutter_svg/svg.dart'; +export 'package:flutter_ics_homescreen/core/constants/colors.dart'; +export 'package:flutter_ics_homescreen/presentation/screens/hvac/widgets/semi_circle_painter.dart'; +export 'package:flutter_ics_homescreen/presentation/common_widget/volume_and_fan_control.dart'; + + + +//proto + + +// export 'l10n/l10n.dart'; +// export 'package:flutter_gen/gen_l10n/app_localizations.dart'; diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..ec5a5d0 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,15 @@ +import 'package:device_preview/device_preview.dart'; + +import 'export.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + // MediaKit.ensureInitialized(); //replace with lottie + runApp(DevicePreview( + enabled: false, // Chnage to false to disable device preview + tools: const [ + ...DevicePreview.defaultTools, + ], + builder: (context) => const App(), + )); +} diff --git a/lib/presentation/common_widget/custom_bottom_bar.dart b/lib/presentation/common_widget/custom_bottom_bar.dart new file mode 100644 index 0000000..13084c0 --- /dev/null +++ b/lib/presentation/common_widget/custom_bottom_bar.dart @@ -0,0 +1,211 @@ +// ignore_for_file: prefer_const_constructors + +import '../../export.dart'; + +class CustomBottomBar extends ConsumerStatefulWidget { + const CustomBottomBar({super.key}); + + @override + CustomBottomBarState createState() => CustomBottomBarState(); +} + +class CustomBottomBarState extends ConsumerState<CustomBottomBar> { + @override + void initState() { + super.initState(); + // "ref" can be used in all life-cycles of a StatefulWidget. + //ref.read(counterProvider); + } + + double iconSize = 57; + List<BottomBarItems> navItems = [ + BottomBarItems(title: "Home", image: "Dashboard"), + BottomBarItems(title: "HVAC", image: "HVAC"), + BottomBarItems(title: "Media", image: "MediaPlayer"), + BottomBarItems(title: "Settings", image: "Settings"), + BottomBarItems(title: "Apps", image: "Apps") + ]; + String selectedNav = "Home"; + + void _onItemTapped(String title) { + AppState status = AppState.dashboard; + switch (title) { + case "Home": + status = AppState.dashboard; + case "HVAC": + status = AppState.hvac; + case "Media": + status = AppState.mediaPlayer; + case "Settings": + status = AppState.settings; + case "Apps": + status = AppState.apps; + } + setState(() { + selectedNav = title; + }); + + ref.read(appProvider.notifier).update((state) => state = status); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(24.0), + child: Row( + children: navItems + .map((e) => Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + Container( + + margin: const EdgeInsets.symmetric( + horizontal: 2, + ), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + selectedNav == e.title + ? Color.fromARGB(217, 41, 98, 255) + : Color.fromARGB(163, 28, 46, 146), + selectedNav == e.title + ? Color.fromARGB(0, 41, 98, 255) + : Color.fromARGB(0, 41, 98, 255), + ], + stops: [ + selectedNav == e.title ? 0.3 : 0, + selectedNav == e.title ? 1 : 0.8, + ], + begin: Alignment.bottomCenter, + end: Alignment.topCenter), + // color: selectedNav == e + // ? AGLDemoColors.neonBlueColor + // : AGLDemoColors.buttonFillEnabledColor, + border: Border( + bottom: const BorderSide( + width: 1.0, + color: AGLDemoColors.jordyBlueColor), + ), + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + _onItemTapped(e.title); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 7), + decoration: BoxDecoration( + border: Border( + left: selectedNav == e.title + ? const BorderSide(color: Colors.white30) + : BorderSide.none, + right: selectedNav == e.title + ? const BorderSide(color: Colors.white30) + : BorderSide.none, + // bottom: BorderSide( + // color: selectedNav == e.title + // ? Colors.white + // : Colors.white24, + // width: + // selectedNav == e.title ? 2 : 1) + )), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset( + "assets/${e.image}${selectedNav == e.title ? "Selected" : ""}.svg", + width: iconSize, + height: iconSize, + ), + Text( + e.title, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 26, + shadows: [ + Shadow( + color: + Colors.black.withOpacity(0.7), + offset: Offset( + 1, + e.title == selectedNav + ? 1.5 + : 3), + blurRadius: e.title == selectedNav + ? 1 + : 3) + ], + color: e.title == selectedNav + ? Colors.white + : AGLDemoColors.periwinkleColor, + fontWeight: selectedNav == e.title + ? FontWeight.bold + : FontWeight.w300), + ), + ], + ), + ), + ), + ), + ), + //if (selectedNav == e.title) + Container( + margin: const EdgeInsets.symmetric(horizontal: 2), + color: selectedNav == e.title + ? Colors.white + : Colors.transparent, + height: 3, + ) + ], + ))) + .toList()), + ); + + // return Container( + // // decoration: const BoxDecoration( + // // gradient: LinearGradient( + // // begin: Alignment.topCenter, + // // end: Alignment.bottomCenter, + // // stops: [0.01, 1], + // // colors: <Color>[Colors.black, Color(0xFF1A237E)], + // // ), + // // ), + // //color: Color(0xFF0D113F), + // child: BottomNavigationBar( + // elevation: 0, + // showSelectedLabels: true, + // showUnselectedLabels: true, + // //backgroundColor: Colors.white, + // backgroundColor: const Color(0xFF0D113F), + // type: BottomNavigationBarType.fixed, + // items: const <BottomNavigationBarItem>[ + // BottomNavigationBarItem( + // icon: Icon(Icons.directions_car), + // label: 'Home', + // backgroundColor: Color(0xFF0D113F)), + // BottomNavigationBarItem(icon: Icon(Icons.ac_unit), label: 'HVAC'), + // BottomNavigationBarItem( + // icon: Icon(Icons.library_music), label: 'Media'), + // BottomNavigationBarItem( + // icon: Icon(Icons.settings), label: 'Settings'), + // BottomNavigationBarItem(icon: Icon(Icons.apps), label: 'Apps'), + // ], + // currentIndex: _selectedIndex, + // selectedItemColor: Colors.white, + // unselectedItemColor: Colors.grey, + + // onTap: _onItemTapped, + // ), + // ); + } +} + +class BottomBarItems { + final String title; + final String image; + + BottomBarItems({required this.title, required this.image}); +} diff --git a/lib/presentation/common_widget/custom_title.dart b/lib/presentation/common_widget/custom_title.dart new file mode 100644 index 0000000..f60c11e --- /dev/null +++ b/lib/presentation/common_widget/custom_title.dart @@ -0,0 +1,103 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class CommonTitle extends StatelessWidget { + const CommonTitle( + {super.key, + required this.title, + this.hasBackButton = false, + this.onPressed}); + + final String title; + final bool? hasBackButton; + final VoidCallback? onPressed; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(top: 80), + child: SizedBox( + //color: Colors.amber, + height: 120, + child: Stack( + fit: StackFit.expand, + children: [ + Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.bottomCenter, + end: Alignment.center, + colors: [ + AGLDemoColors.jordyBlueColor.withOpacity(0.2), + AGLDemoColors.jordyBlueColor.withOpacity(0) + ], + stops: const [ + 0.0, + 1 + ])), + alignment: Alignment.center, + child: Text( + title, + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.white, + fontSize: 40, + fontWeight: FontWeight.w500), + ), + ), + if (hasBackButton!) + Align( + alignment: AlignmentDirectional.centerStart, + child: Padding( + padding: const EdgeInsets.only(left: 20.0), + child: InkWell( + customBorder: const CircleBorder(), + onTap: onPressed, + child: const Icon( + Icons.arrow_back, + color: Colors.white, + size: 48, + )), + ), + ), + ], + ), + ), + ), + Container( + height: 2, + decoration: BoxDecoration( + gradient: const LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Colors.white10, + AGLDemoColors.jordyBlueColor, + Colors.white10 + ], + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.5), + blurRadius: 6, + spreadRadius: 3, + offset: const Offset(0, 6), + ), + ], + )), + Container( + height: 48, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Colors.black, Colors.black.withOpacity(0.0)], + ), + ), + ) + ], + ); + } +} diff --git a/lib/presentation/common_widget/custom_top_bar.dart b/lib/presentation/common_widget/custom_top_bar.dart new file mode 100644 index 0000000..c268f53 --- /dev/null +++ b/lib/presentation/common_widget/custom_top_bar.dart @@ -0,0 +1,106 @@ +import 'package:intl/intl.dart'; + +import '../../export.dart'; + +class CustomTopBar extends ConsumerStatefulWidget + implements PreferredSizeWidget { + const CustomTopBar({super.key}); + + @override + CustomTopBarState createState() => CustomTopBarState(); + + @override + Size get preferredSize => const Size.fromHeight(80); +} + +class CustomTopBarState extends ConsumerState<CustomTopBar> { + @override + Widget build(BuildContext context) { + final singnalsConnection = + ref.watch(signalsProvider.select((sinals) => sinals)); + final user = ref.watch(usersProvider.select((user) => user)); + final time2 = + ref.watch(dateTimeStateProvider.select((dateTime) => dateTime)); + + DateFormat dateFormat = DateFormat('HH:mm'); + //var time = dateFormat.format(DateTime.now()); + var time = time2.time; + + return AppBar( + elevation: 0, + backgroundColor: Colors.transparent, + //leadingWidth: 100, + + title: Stack( + //mainAxisAlignment: MainAxisAlignment.center, + children: [ + Positioned.fill( + child: Align( + alignment: Alignment.centerLeft, + child: Wrap( + children: [ + RichText( + text: TextSpan( + text: '$time ', + style: const TextStyle(color: Colors.white, fontSize: 26), + children: <InlineSpan>[ + const WidgetSpan( + child: SizedBox(width: 16), // 16px space + ), + TextSpan( + text: user.selectedUser.name, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ], + ), + ), + ], + ), + ), + ), + Align( + alignment: Alignment.center, + child: Image.asset( + 'assets/topBarLogo.png', + width: 659, + height: 56, + ), + ), + Positioned.fill( + child: Align( + alignment: Alignment.centerRight, + child: Wrap( + children: [ + Icon( + singnalsConnection.isBluetoothConnected + ? Icons.bluetooth + : Icons.bluetooth_disabled, + size: 24, + ), + const SizedBox( + width: 24, + ), + const Icon( + Icons.signal_cellular_4_bar_outlined, + size: 24, + ), + const SizedBox( + width: 24, + ), + Icon( + singnalsConnection.isWifiConnected + ? Icons.wifi + : Icons.wifi_off, + size: 24, + ), + ], + ), + ), + ), + ], + ), + + // ), + ); + } +} diff --git a/lib/presentation/common_widget/fan_bar.dart b/lib/presentation/common_widget/fan_bar.dart new file mode 100644 index 0000000..e119089 --- /dev/null +++ b/lib/presentation/common_widget/fan_bar.dart @@ -0,0 +1,188 @@ +import '../../export.dart'; +import 'package:flutter_ics_homescreen/presentation/custom_icons/custom_icons.dart'; + +enum FanMode { off, min, medium, max } + +class FanBar extends ConsumerStatefulWidget { + const FanBar({super.key}); + + @override + FanBarState createState() => FanBarState(); +} + +class FanBarState extends ConsumerState<FanBar> { + int selectedFanSpeed = 0; + + @override + Widget build(BuildContext context) { + final vehicle = ref.watch(vehicleProvider.select((vehicle) => vehicle)); + selectedFanSpeed = vehicle.fanSpeed; + + return Column(children: [ + Container( + padding: EdgeInsets.zero, + //width: 80, + height: 256, + decoration: const ShapeDecoration( + // gradient: RadialGradient( + // colors: [Color.fromARGB(255, 19, 24, 75), Colors.black], + // stops: [0, 0.9], + // radius: 1, + // ), + color: AGLDemoColors.buttonFillEnabledColor, + shape: StadiumBorder( + side: BorderSide( + color: Color(0xFF5477D4), + width: 1, + )), + ), + //alignment: Alignment.topLeft, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Material( + color: Colors.transparent, + child: SizedBox( + width: 80, + height: 64, + child: IconButton( + isSelected: selectedFanSpeed == 3, + padding: EdgeInsets.zero, + color: AGLDemoColors.periwinkleColor, + onPressed: () { + ref.read(vehicleProvider.notifier).updateFanSpeed(3); + }, + icon: selectedFanSpeed == 3 + ? Stack( + alignment: Alignment.center, + children: [ + SvgPicture.asset('assets/fanButtonBg.svg'), + const Icon( + CustomIcons.fan_on_enabled, + color: Colors.white, + size: 40, + ), + ], + ) + : const Icon( + CustomIcons.fan_on_enabled, + size: 40, + ), + ), + ), + ), + Material( + color: Colors.transparent, + child: SizedBox( + width: 64, + height: 64, + child: IconButton( + isSelected: selectedFanSpeed == 2, + padding: EdgeInsets.zero, + color: AGLDemoColors.periwinkleColor, + onPressed: () { + ref.read(vehicleProvider.notifier).updateFanSpeed(2); + }, + icon: selectedFanSpeed == 2 + ? Stack( + alignment: Alignment.center, + children: [ + SvgPicture.asset('assets/fanButtonBg.svg'), + const Icon( + CustomIcons.fan_on_enabled, + color: Colors.white, + size: 28, + ), + ], + ) + : const Icon( + CustomIcons.fan_on_enabled, + size: 28, + ), + ), + ), + ), + Material( + color: Colors.transparent, + child: SizedBox( + width: 64, + height: 64, + child: IconButton( + isSelected: selectedFanSpeed == 1, + iconSize: 20, + padding: EdgeInsets.zero, + color: AGLDemoColors.periwinkleColor, + onPressed: () { + ref.read(vehicleProvider.notifier).updateFanSpeed(1); + }, + icon: selectedFanSpeed == 1 + ? Stack( + alignment: Alignment.center, + children: [ + SvgPicture.asset('assets/fanButtonBg.svg'), + const Icon( + CustomIcons.fan_on_enabled, + color: Colors.white, + size: 20, + ), + ], + ) + : const Icon( + CustomIcons.fan_on_enabled, + size: 20, + ), + ), + ), + ), + ], + ), + ), + const SizedBox(height: 10), + Container( + width: 80, + height: 80, + decoration: const ShapeDecoration( + // gradient: RadialGradient( + // colors: [Color.fromARGB(255, 19, 24, 75), Colors.black], + // stops: [0, 0.9], + // radius: 1, + // ), + color: AGLDemoColors.buttonFillEnabledColor, + shape: StadiumBorder( + side: BorderSide( + color: Color(0xFF5477D4), + width: 1, + )), + ), + //alignment: Alignment.topLeft, + child: Material( + color: Colors.transparent, + child: IconButton( + isSelected: selectedFanSpeed == 0, + padding: EdgeInsets.zero, + color: AGLDemoColors.periwinkleColor, + onPressed: () { + ref.read(vehicleProvider.notifier).updateFanSpeed(0); + }, + icon: selectedFanSpeed == 0 + ? Stack( + alignment: Alignment.center, + children: [ + SvgPicture.asset('assets/fanButtonBg.svg'), + const Icon( + Icons.mode_fan_off, + color: Colors.white, + size: 28.4, + ), + ], + ) + : const Icon( + Icons.mode_fan_off, + size: 28.4, + ), + ), + )), + ]); + } +} diff --git a/lib/presentation/common_widget/generic_button.dart b/lib/presentation/common_widget/generic_button.dart new file mode 100644 index 0000000..cca354f --- /dev/null +++ b/lib/presentation/common_widget/generic_button.dart @@ -0,0 +1,91 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class GenericButton extends StatefulWidget { + final double heigth; + final double width; + final String text; + final Function onTap; + + const GenericButton({ + super.key, + required this.heigth, + required this.width, + required this.text, + required this.onTap, + }); + + @override + State<GenericButton> createState() => _GenericButtonState(); +} + +class _GenericButtonState extends State<GenericButton> { + LinearGradient gradientEnable1 = const LinearGradient(colors: <Color>[ + Color(0xFF2962FF), + Color(0x802962FF), + ]); + LinearGradient gradientEnable2 = const LinearGradient(colors: <Color>[ + Color(0xFF1A237E), + Color(0xFF141F64), + ]); + @override + Widget build(BuildContext context) { + return GestureDetector( + onTapDown: (details) { + setState(() { + gradientEnable1 = const LinearGradient(colors: <Color>[ + Color(0x802962FF), + Color(0xFF2962FF), + ]); + gradientEnable2 = const LinearGradient(colors: <Color>[ + Color(0xFF1A237E), + Color(0xFF1C2D92), + ]); + }); + //change style + }, + onTapUp: (details) { + setState(() { + gradientEnable1 = const LinearGradient(colors: <Color>[ + Color(0xFF2962FF), + Color(0x802962FF), + ]); + gradientEnable2 = const LinearGradient(colors: <Color>[ + Color(0xFF1A237E), + Color(0xFF141F64), + ]); + }); + widget.onTap(); + }, + child: Container( + height: widget.heigth, + width: widget.width, + decoration: BoxDecoration( + gradient: Gradient.lerp(gradientEnable1, gradientEnable2, 0.5), + boxShadow: const [ + BoxShadow( + color: Colors.black, + blurRadius: 2, + offset: Offset(0, 2), // Shadow position + ), + ], + border: Border.all( + color: const Color(0xFF285DF4), + width: 1, + ), + borderRadius: const BorderRadius.all( + Radius.circular(4), + ), + ), + child: Center( + child: Text( + widget.text, + style: const TextStyle( + color: Color(0xFFC1D8FF), + fontSize: 44, + ), + ), + ), + ), + ); + } +} diff --git a/lib/presentation/common_widget/settings_top_bar.dart b/lib/presentation/common_widget/settings_top_bar.dart new file mode 100644 index 0000000..fb4e953 --- /dev/null +++ b/lib/presentation/common_widget/settings_top_bar.dart @@ -0,0 +1,37 @@ +import '../../export.dart'; + +class SettingsTopBar extends ConsumerStatefulWidget + implements PreferredSizeWidget { + final String title; + const SettingsTopBar(this.title, {super.key}); + + @override + SettingsTopBarState createState() => SettingsTopBarState(); + @override + Size get preferredSize => const Size.fromHeight(50); +} + +class SettingsTopBarState extends ConsumerState<SettingsTopBar> { + @override + Widget build( + BuildContext context, + ) { + return AppBar( + elevation: 0, + backgroundColor: const Color(0xFF0D113F), + leading: BackButton( + onPressed: () { + context.flow<AppState>().update((state) => AppState.settings); + }, + ), + title: Text( + widget.title, + style: const TextStyle( + fontStyle: FontStyle.normal, + fontWeight: FontWeight.normal, + fontSize: 20), + ), + //centerTitle: true, + ); + } +} diff --git a/lib/presentation/common_widget/volume_and_fan_control.dart b/lib/presentation/common_widget/volume_and_fan_control.dart new file mode 100644 index 0000000..051e360 --- /dev/null +++ b/lib/presentation/common_widget/volume_and_fan_control.dart @@ -0,0 +1,36 @@ +import '../common_widget/volume_bar.dart'; +import '../common_widget/fan_bar.dart'; + +import '../../export.dart'; + +class VolumeFanControl extends ConsumerWidget { + const VolumeFanControl({ + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final state = ref.watch(appProvider); + final size = MediaQuery.of(context).size; + final gapSize = size.height * 0.06; + + return Padding( + padding: const EdgeInsets.only(left: 10), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Visibility.maintain( + visible: state == AppState.mediaPlayer ? false : true, + child: const VolumeBar()), + SizedBox( + height: gapSize, + ), + Visibility.maintain( + visible: state == AppState.hvac ? false : true, + child: const FanBar(), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/common_widget/volume_bar.dart b/lib/presentation/common_widget/volume_bar.dart new file mode 100644 index 0000000..a029698 --- /dev/null +++ b/lib/presentation/common_widget/volume_bar.dart @@ -0,0 +1,356 @@ +import 'package:flutter_ics_homescreen/presentation/custom_icons/custom_icons.dart'; + +import '../../export.dart'; + +class VolumeBar extends ConsumerStatefulWidget { + const VolumeBar({super.key}); + + @override + VolumeBarState createState() => VolumeBarState(); +} + +class VolumeBarState extends ConsumerState<VolumeBar> { + double val = 0; + @override + void initState() { + super.initState(); + // "ref" can be used in all life-cycles of a StatefulWidget. + //ref.read(counterProvider); + } + + void increaseVolume() { + setState(() { + if (val < 20) { + val++; + ref.read(audioStateProvider.notifier).setVolume(val); + } + }); + } + + void decreaseVolume() { + setState(() { + if (val > 0) { + val--; + ref.read(audioStateProvider.notifier).setVolume(val); + } + }); + } + + void setVolume(double newWalue) { + setState(() { + val = newWalue; + ref.read(audioStateProvider.notifier).setVolume(val); + }); + } + + void pause() {} + + @override + Widget build(BuildContext context) { + final volumeValue = + ref.watch(audioStateProvider.select((audio) => audio.volume)); + val = volumeValue; + return Column( + // mainAxisAlignment: MainAxisAlignment.center, + // crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: 80, + height: 418, + //padding: const EdgeInsets.all(16), + decoration: const ShapeDecoration( + // gradient: RadialGradient( + // colors: [Color.fromARGB(255, 19, 24, 75), Colors.black], + // stops: [0, 0.9], + // radius: 1, + // ), + color: AGLDemoColors.buttonFillEnabledColor, + shape: StadiumBorder( + side: BorderSide( + color: Color(0xFF5477D4), + width: 1, + )), + ), + //alignment: Alignment.topLeft, + child: Column( + children: [ + SizedBox( + height: 68.0, + width: 56.0, + child: IconButton( + padding: EdgeInsets.zero, + color: AGLDemoColors.periwinkleColor, + onPressed: () { + increaseVolume(); + }, + icon: const Icon( + CustomIcons.vol_max, + size: 56, + ), + ), + ), + Padding( + padding: const EdgeInsets.only( + top: 4, bottom: 4), // Top and bottom padding + child: SizedBox( + height: 274.0, + child: RotatedBox( + quarterTurns: 3, + child: SliderTheme( + data: SliderTheme.of(context).copyWith( + activeTrackColor: AGLDemoColors.periwinkleColor, + inactiveTrackColor: const Color(0xFF0D113F), + trackShape: const GradinetRectangularSliderTrackShape(), + //trackShape: CustomTrackShape(), + trackHeight: 56.0, + //thumbColor: Colors.blueAccent, + thumbShape: const RectSliderThumbShape( + enabledThumbRadius: 0, disabledThumbRadius: 0), + //overlayColor: Colors.red.withAlpha(32), + overlayShape: + //RoundSliderOverlayShape(overlayRadius: 33.0), + //RoundSliderOverlayShape(overlayRadius: 0.0), + SliderComponentShape.noOverlay, + ), + child: Slider( + min: 0, + max: 20, + value: volumeValue, + divisions: 20, + onChanged: (newValue) { + setVolume(newValue); + }, + ), + ), + ), + ), + ), + SizedBox( + height: 56.0, + width: 56.0, + child: IconButton( + padding: EdgeInsets.zero, + color: AGLDemoColors.periwinkleColor, + onPressed: () { + decreaseVolume(); + }, + icon: const Icon( + CustomIcons.vol_min, + size: 56, + ), + ), + ), + ], + ), + ), + const SizedBox(height: 10), + Container( + width: 80, + height: 80, + //padding: const EdgeInsets.all(16), + decoration: const ShapeDecoration( + // gradient: RadialGradient( + // colors: [Color.fromARGB(255, 19, 24, 75), Colors.black], + // stops: [0, 0.9], + // radius: 1, + // ), + color: AGLDemoColors.buttonFillEnabledColor, + shape: StadiumBorder( + side: BorderSide( + color: Color(0xFF5477D4), + width: 1, + )), + ), + //alignment: Alignment.topLeft, + child: IconButton( + padding: EdgeInsets.zero, + color: AGLDemoColors.periwinkleColor, + onPressed: () { + pause(); + }, + icon: const Icon( + Icons.pause, + size: 30, + )), + ), + ], + ); + } +} + +class RectSliderThumbShape extends SliderComponentShape { + /// Create a slider thumb that draws a Rect. + const RectSliderThumbShape({ + this.enabledThumbRadius = 10.0, + this.disabledThumbRadius, + this.elevation = 1.0, + this.pressedElevation = 6.0, + }); + + final double enabledThumbRadius; + + final double? disabledThumbRadius; + double get _disabledThumbRadius => disabledThumbRadius ?? enabledThumbRadius; + + final double elevation; + + final double pressedElevation; + + @override + Size getPreferredSize(bool isEnabled, bool isDiscrete) { + return Size.fromRadius( + isEnabled == true ? enabledThumbRadius : _disabledThumbRadius); + } + + @override + void paint( + PaintingContext context, + Offset center, { + required Animation<double> activationAnimation, + required Animation<double> enableAnimation, + required bool isDiscrete, + required TextPainter labelPainter, + required RenderBox parentBox, + required SliderThemeData sliderTheme, + required TextDirection textDirection, + required double value, + required double textScaleFactor, + required Size sizeWithOverflow, + }) { + final Canvas canvas = context.canvas; + + const Color color = Colors.white; + //const double radius = 0; + + final Tween<double> elevationTween = Tween<double>( + begin: elevation, + end: pressedElevation, + ); + + final double evaluatedElevation = + elevationTween.evaluate(activationAnimation); + + final Path path = Path() + ..addRect( + Rect.fromCenter( + center: Offset(center.dx - 3, center.dy + 2), width: 3, height: 54), + ); + + // canvas.drawRect( + // Rect.fromCenter(center: center, width: 4, height: 25), + // Paint()..color = color, + // ); + canvas.drawRect( + Rect.fromCenter(center: center, width: 4, height: 54), + Paint()..color = color, + ); + canvas.drawShadow(path, Colors.black, evaluatedElevation, true); + } +} + +class GradinetRectangularSliderTrackShape extends SliderTrackShape + with BaseSliderTrackShape { + /// Creates a slider track that draws 2 rectangles. + const GradinetRectangularSliderTrackShape(); + + @override + void paint( + PaintingContext context, + Offset offset, { + required RenderBox parentBox, + required SliderThemeData sliderTheme, + required Animation<double> enableAnimation, + required TextDirection textDirection, + required Offset thumbCenter, + Offset? secondaryOffset, + bool isDiscrete = false, + bool isEnabled = false, + }) { + assert(sliderTheme.disabledActiveTrackColor != null); + assert(sliderTheme.disabledInactiveTrackColor != null); + assert(sliderTheme.activeTrackColor != null); + assert(sliderTheme.inactiveTrackColor != null); + assert(sliderTheme.thumbShape != null); + // If the slider [SliderThemeData.trackHeight] is less than or equal to 0, + // then it makes no difference whether the track is painted or not, + // therefore the painting can be a no-op. + if (sliderTheme.trackHeight! <= 0) { + return; + } + + LinearGradient gradient = const LinearGradient( + colors: <Color>[ + //AGLDemoColors.periwinkleColor, + Color(0xff81A9ED), + Colors.white, + ], + ); + final Rect trackRect = getPreferredRect( + parentBox: parentBox, + offset: offset, + sliderTheme: sliderTheme, + isEnabled: isEnabled, + isDiscrete: isDiscrete, + ); + // Assign the track segment paints, which are left: active, right: inactive, + // but reversed for right to left text. + final ColorTween activeTrackColorTween = ColorTween( + begin: sliderTheme.disabledActiveTrackColor, + end: sliderTheme.activeTrackColor); + final ColorTween inactiveTrackColorTween = ColorTween( + begin: sliderTheme.disabledInactiveTrackColor, + end: sliderTheme.inactiveTrackColor); + final Paint activePaint = Paint() + ..shader = gradient.createShader(trackRect) + ..color = activeTrackColorTween.evaluate(enableAnimation)!; + final Paint inactivePaint = Paint() + ..color = inactiveTrackColorTween.evaluate(enableAnimation)!; + final Paint leftTrackPaint; + final Paint rightTrackPaint; + switch (textDirection) { + case TextDirection.ltr: + leftTrackPaint = activePaint; + rightTrackPaint = inactivePaint; + case TextDirection.rtl: + leftTrackPaint = inactivePaint; + rightTrackPaint = activePaint; + } + + final Rect leftTrackSegment = Rect.fromLTRB( + trackRect.left, trackRect.top, thumbCenter.dx, trackRect.bottom); + if (!leftTrackSegment.isEmpty) { + context.canvas.drawRect(leftTrackSegment, leftTrackPaint); + } + final Rect rightTrackSegment = Rect.fromLTRB( + thumbCenter.dx, trackRect.top, trackRect.right, trackRect.bottom); + if (!rightTrackSegment.isEmpty) { + context.canvas.drawRect(rightTrackSegment, rightTrackPaint); + } + + final bool showSecondaryTrack = (secondaryOffset != null) && + ((textDirection == TextDirection.ltr) + ? (secondaryOffset.dx > thumbCenter.dx) + : (secondaryOffset.dx < thumbCenter.dx)); + + if (showSecondaryTrack) { + final ColorTween secondaryTrackColorTween = ColorTween( + begin: sliderTheme.disabledSecondaryActiveTrackColor, + end: sliderTheme.secondaryActiveTrackColor); + final Paint secondaryTrackPaint = Paint() + ..color = secondaryTrackColorTween.evaluate(enableAnimation)!; + final Rect secondaryTrackSegment = Rect.fromLTRB( + (textDirection == TextDirection.ltr) + ? thumbCenter.dx + : secondaryOffset.dx, + trackRect.top, + (textDirection == TextDirection.ltr) + ? secondaryOffset.dx + : thumbCenter.dx, + trackRect.bottom, + ); + if (!secondaryTrackSegment.isEmpty) { + context.canvas.drawRect(secondaryTrackSegment, secondaryTrackPaint); + } + } + } +} diff --git a/lib/presentation/custom_icons/custom_icons.dart b/lib/presentation/custom_icons/custom_icons.dart new file mode 100644 index 0000000..2a42c95 --- /dev/null +++ b/lib/presentation/custom_icons/custom_icons.dart @@ -0,0 +1,76 @@ +/// Flutter icons CustomIcons +/// Copyright (C) 2023 by original authors @ fluttericon.com, fontello.com +/// This font was generated by FlutterIcon.com, which is derived from Fontello. +/// +/// To use this font, place it in your fonts/ directory and include the +/// following in your pubspec.yaml +/// +/// flutter: +/// fonts: +/// - family: CustomIcons +/// fonts: +/// - asset: fonts/CustomIcons.ttf +/// +/// +/// * Font Awesome 5, Copyright (C) 2016 by Dave Gandy +/// Author: Dave Gandy +/// License: SIL (https://github.com/FortAwesome/Font-Awesome/blob/master/LICENSE.txt) +/// Homepage: http://fortawesome.github.com/Font-Awesome/ +/// +// ignore_for_file: constant_identifier_names + +import 'package:flutter/widgets.dart'; + +class CustomIcons { + CustomIcons._(); + + static const _kFontFam = 'CustomIcons'; + static const String? _kFontPkg = null; + + static const IconData fan_off_enabled = + IconData(0xe800, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData fan_off_pressed = + IconData(0xe801, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData fan_on_enabled = + IconData(0xe802, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData fan_on_pressed = + IconData(0xe803, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData vol_max = + IconData(0xe804, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData vol_min = + IconData(0xe805, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData wifi_0_bar_locked = + IconData(0xe806, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData wifi_0_bar_unlocked = + IconData(0xe807, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData wifi_1_bar_locked = + IconData(0xe808, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData wifi_1_bar_unlocked = + IconData(0xe809, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData wifi_2_bar_locked = + IconData(0xe80a, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData wifi_2_bar_unlocked = + IconData(0xe80b, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData wifi_3_bar_locked = + IconData(0xe80c, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData wifi_3_bar_unlocked = + IconData(0xe80d, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData wifi_4_bar_locked = + IconData(0xe80e, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData wifi_4_bar_unlocked = + IconData(0xe80f, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData wifi_5_bar_locked = + IconData(0xe810, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData wifi_5_bar_unlocked = + IconData(0xe811, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData wiredicon = + IconData(0xe812, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData range = + IconData(0xe813, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData slider_front = + IconData(0xe814, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData slider_rear = + IconData(0xe815, fontFamily: _kFontFam, fontPackage: _kFontPkg); + static const IconData fan = + IconData(0xf863, fontFamily: _kFontFam, fontPackage: _kFontPkg); +} diff --git a/lib/presentation/router/routes/routes.dart b/lib/presentation/router/routes/routes.dart new file mode 100644 index 0000000..d6d74c4 --- /dev/null +++ b/lib/presentation/router/routes/routes.dart @@ -0,0 +1,56 @@ +import 'package:flutter_ics_homescreen/presentation/screens/settings/settings_screens/date_time/date/date_screen.dart'; +import 'package:flutter_ics_homescreen/presentation/screens/settings/settings_screens/date_time/time/time_screen.dart'; + +import '../../../../export.dart'; + +List<Page<dynamic>> onGenerateAppViewPages( + AppState state, + List<Page<dynamic>> pages, +) { + switch (state) { + case AppState.home: + return [DasboardPage.page()]; + case AppState.dashboard: + return [DasboardPage.page()]; + case AppState.hvac: + return [HvacPage.page()]; + case AppState.apps: + return [AppsPage.page()]; + case AppState.mediaPlayer: + return [MediaPlayerPage.page()]; + case AppState.settings: + return [SettingsPage.page()]; + case AppState.splash: + return [SplashPage.page()]; + case AppState.dateTime: + return [DateTimePage.page()]; + case AppState.bluetooth: + return [BluetoothPage.page()]; + case AppState.wifi: + return [WifiPage.page()]; + case AppState.wired: + return [WiredPage.page()]; + case AppState.audioSettings: + return [AudioSettingsPage.page()]; + case AppState.profiles: + return [ProfilesPage.page()]; + case AppState.units: + return [UnitsPage.page()]; + case AppState.versionInfo: + return [VersionInfoPage.page()]; + case AppState.weather: + return [WeatherPage.page()]; + case AppState.distanceUnit: + return [DistanceUnitPage.page()]; + case AppState.tempUnit: + return [TemperatureUnitPage.page()]; + case AppState.clock: + return [ClockPage.page()]; + case AppState.newProfile: + return [NewProfilePage.page()]; + case AppState.date: + return [DatePage.page()]; + case AppState.time: + return [TimePage.page()]; + } +} diff --git a/lib/presentation/routes/widget/page_widget.dart b/lib/presentation/routes/widget/page_widget.dart new file mode 100644 index 0000000..8a08377 --- /dev/null +++ b/lib/presentation/routes/widget/page_widget.dart @@ -0,0 +1,21 @@ +import '../../../export.dart'; + +class MyPage<T> extends Page<T> { + const MyPage({required this.child, super.key}); + + final Widget child; + + @override + Route<T> createRoute(BuildContext context) { + return PageRouteBuilder( + settings: this, + pageBuilder: ( + BuildContext context, + Animation<double> animation, + Animation<double> secondaryAnimation, + ) { + return FadeTransition(opacity: animation, child: child); + }, + ); + } +} diff --git a/lib/presentation/screens/apps/apps.dart b/lib/presentation/screens/apps/apps.dart new file mode 100644 index 0000000..5a789fa --- /dev/null +++ b/lib/presentation/screens/apps/apps.dart @@ -0,0 +1,30 @@ +import '/export.dart'; +import 'apps_content.dart'; + +class AppsPage extends StatelessWidget { + const AppsPage({super.key}); + + static Page<void> page() => const MaterialPage<void>(child: AppsPage()); + @override + Widget build(BuildContext context) { + return const Stack( + children: [ + // SizedBox( + // width: double.infinity, + // height: double.infinity, + // //color: Colors.black, + // // decoration: + // // BoxDecoration(gradient: AGLDemoColors.gradientBackgroundColor), + // child: SvgPicture.asset( + // 'assets/HVACBackground.svg', + // alignment: Alignment.center, + // fit: BoxFit.cover, + // //width: 200, + // //height: 200, + // ), + // ), + Apps(), + ], + ); + } +} diff --git a/lib/presentation/screens/apps/apps_content.dart b/lib/presentation/screens/apps/apps_content.dart new file mode 100644 index 0000000..52da10c --- /dev/null +++ b/lib/presentation/screens/apps/apps_content.dart @@ -0,0 +1,60 @@ +import 'package:flutter_ics_homescreen/data/models/hybrid.dart'; +import 'package:flutter_ics_homescreen/export.dart'; +import 'package:flutter_ics_homescreen/presentation/screens/apps/widgets/app_button.dart'; + +class Apps extends StatefulWidget { + const Apps({super.key}); + + @override + State<Apps> createState() => _AppsState(); +} + +class _AppsState extends State<Apps> { + onPressed({required String type}) { + if (type == "weather") { + context.flow<AppState>().update((next) => AppState.weather); + } else if (type == "clock") { + context.flow<AppState>().update((next) => AppState.clock); + } + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const CommonTitle(title: "Applications"), + Padding( + padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 148), + child: Wrap( + children: [ + AppButton( + image: "weather.svg", + title: "Weather", + onPressed: () { + onPressed(type: "weather"); + }, + ), + AppButton( + image: "clock.svg", + title: "Clock", + onPressed: () { + onPressed(type: "clock"); + }, + ) + ], + ), + ), + // Center( + // child: SizedBox( + // width: 500, + // height: 500, + // child: Center( + // child: Lottie.asset(''), + // )), + // ), + ], + ); + } +} + diff --git a/lib/presentation/screens/apps/widgets/app_button.dart b/lib/presentation/screens/apps/widgets/app_button.dart new file mode 100644 index 0000000..a890786 --- /dev/null +++ b/lib/presentation/screens/apps/widgets/app_button.dart @@ -0,0 +1,61 @@ +import 'package:flutter_ics_homescreen/core/utils/helpers.dart'; +import 'package:flutter_ics_homescreen/export.dart'; + +class AppButton extends StatefulWidget { + const AppButton( + {super.key, + required this.image, + required this.title, + required this.onPressed}); + final String image; + final String title; + final VoidCallback onPressed; + + @override + State<AppButton> createState() => _AppButtonState(); +} + +class _AppButtonState extends State<AppButton> { + @override + Widget build(BuildContext context) { + Size size = MediaQuery.sizeOf(context); + return Container( + width: 250, + height: 250, + margin: const EdgeInsets.all(8), + decoration: BoxDecoration( + boxShadow: [Helpers.boxDropShadowRegular], + border: Border.all(color: AGLDemoColors.neonBlueColor), + color: AGLDemoColors.buttonFillEnabledColor, + borderRadius: BorderRadius.circular(4)), + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(4), + onTap: widget.onPressed, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 10, right: 10, top: 6, bottom: 6), + child: SvgPicture.asset( + "assets/${widget.image}", + ), + ), + Text( + widget.title, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 40, + shadows: [Helpers.dropShadowRegular], + color: AGLDemoColors.periwinkleColor, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/presentation/screens/clock/clock.dart b/lib/presentation/screens/clock/clock.dart new file mode 100644 index 0000000..f0858e7 --- /dev/null +++ b/lib/presentation/screens/clock/clock.dart @@ -0,0 +1,145 @@ +import 'dart:async'; + +import 'package:flutter_ics_homescreen/export.dart'; + +class ClockPage extends ConsumerWidget { + const ClockPage({super.key}); + + static Page<void> page() => const MaterialPage<void>(child: ClockPage()); + + @override + Widget build(BuildContext context, WidgetRef ref) { + double clockSize = MediaQuery.sizeOf(context).width * 0.51; + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + CommonTitle( + title: "Clock", + hasBackButton: true, + onPressed: () { + context.flow<AppState>().update((state) => AppState.apps); + }, + ), + const SizedBox( + height: 25, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: SingleChildScrollView( + child: Column( + children: [ + const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.location_on_outlined, + color: Colors.white, + size: 48, + ), + SizedBox( + width: 7, + ), + Text( + "Fortaleza", + style: TextStyle( + color: Colors.white, + fontSize: 40, + fontWeight: FontWeight.w500), + ), + ], + ), + const SizedBox( + height: 80, + ), + const SizedBox( + height: 140, + ), + Container( + width: clockSize, + height: clockSize, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + "assets/clockBackground.png", + ), + ), + ), + child: const AnalogClock( + dialColor: null, + markingColor: null, + hourNumberColor: null, + secondHandColor: AGLDemoColors.jordyBlueColor, + hourHandColor: AGLDemoColors.jordyBlueColor, + minuteHandColor: AGLDemoColors.jordyBlueColor, + centerPointColor: null, + hourHandLengthFactor: 0.6, + secondHandLengthFactor: 0.6, + secondHandWidthFactor: 1.5, + minuteHandLengthFactor: 0.7, + minuteHandWidthFactor: 2.5, + hourHandWidthFactor: 1.2, + ), + ), + const SizedBox( + height: 120, + ), + const RealTimeClock(), + ], + ), + ), + ), + ) + ], + ); + } +} + +class RealTimeClock extends StatefulWidget { + const RealTimeClock({super.key}); + + @override + State<RealTimeClock> createState() => _RealTimeClockState(); +} + +class _RealTimeClockState extends State<RealTimeClock> { + late String _timeString; + late Timer _timer; + + @override + void initState() { + _timeString = _formatDateTime(DateTime.now()); + _timer = + Timer.periodic(const Duration(seconds: 1), (Timer t) => _getTime()); + super.initState(); + } + + @override + void dispose() { + _timer.cancel(); + super.dispose(); + } + + void _getTime() { + final DateTime now = DateTime.now(); + final String formattedDateTime = _formatDateTime(now); + if (mounted) { + setState(() { + _timeString = formattedDateTime; + }); + } + } + + String _formatDateTime(DateTime dateTime) { + return "${dateTime.hour}:${dateTime.minute.toString().padLeft(2, '0')}"; + } + + @override + Widget build(BuildContext context) { + return Text( + _timeString, + style: GoogleFonts.brunoAce(color: Colors.white, fontSize: 128), + ); + } +} diff --git a/lib/presentation/screens/dashboard/dashboard.dart b/lib/presentation/screens/dashboard/dashboard.dart new file mode 100644 index 0000000..977bb31 --- /dev/null +++ b/lib/presentation/screens/dashboard/dashboard.dart @@ -0,0 +1,33 @@ + +import '/export.dart'; +import 'widgets/dashboard_content.dart'; + +class DasboardPage extends ConsumerWidget { + const DasboardPage({super.key}); + + static Page<void> page() => const MaterialPage<void>(child: DasboardPage()); + @override + Widget build(BuildContext context, WidgetRef ref) { + return Stack( + children: [ + + Padding( + padding: const EdgeInsets.only(top: 150.0), + child: SizedBox( + width: double.infinity, + height: double.infinity, + child: SvgPicture.asset( + 'assets/dashboardTextures.svg', + alignment: Alignment.center, + ), + ), + ), + const Padding( + padding: EdgeInsets.symmetric(vertical: 50, horizontal: 140), + child: DashBoard(), + ), + ], + + ); + } +} diff --git a/lib/presentation/screens/dashboard/widgets/car_status.dart b/lib/presentation/screens/dashboard/widgets/car_status.dart new file mode 100644 index 0000000..b824871 --- /dev/null +++ b/lib/presentation/screens/dashboard/widgets/car_status.dart @@ -0,0 +1,251 @@ +// ignore_for_file: prefer_const_constructors, prefer_const_literals_to_create_immutables + +import 'package:gradient_borders/gradient_borders.dart'; + +import '../../../../export.dart'; + +class CarStatus extends ConsumerStatefulWidget { + const CarStatus({super.key}); + + @override + CarStatusState createState() => CarStatusState(); +} + +class CarStatusState extends ConsumerState<CarStatus> { + @override + void initState() { + super.initState(); + // "ref" can be used in all life-cycles of a StatefulWidget. + //ref.read(counterProvider); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.fromLTRB(0,0,0,84), + child: SizedBox( + height: 440, + width: 652, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const LeftCarStatus(), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 47.0), // Adding horizontal padding + child: SvgPicture.asset( + 'assets/Car Illustration.svg', + width: 625, + height: 440, + fit: BoxFit.fitHeight, + ), + ), + const RightCarStatus(), + ], + ), + ), + ); + } +} + +class LeftCarStatus extends ConsumerWidget { + const LeftCarStatus({ + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final frontLeftTire = + ref.watch(vehicleProvider.select((vehicle) => vehicle.frontLeftTire)); + final rearLeftTire = + ref.watch(vehicleProvider.select((vehicle) => vehicle.rearLeftTire)); + + return Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + PSIProgressIndicator(value: frontLeftTire.toDouble()), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + frontLeftTire.toStringAsFixed(1), + style: GoogleFonts.brunoAce( + textStyle: TextStyle( + color: Colors.white, fontSize: 44), + ), + ), + SizedBox( + width: 5, + ), + PSIWidget(), + ], + ), + ], + ), + ChildLockLeft(), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + PSIProgressIndicator(value: rearLeftTire.toDouble()), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + rearLeftTire.toStringAsFixed(1), + style: GoogleFonts.brunoAce( + textStyle: TextStyle( + color: Colors.white, fontSize: 44), + ), + ), + SizedBox( + width: 5, + ), + PSIWidget(), + ], + ), + ], + ), + ], + ); + } +} + +class RightCarStatus extends ConsumerWidget { + const RightCarStatus({ + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final frontRightTire = + ref.watch(vehicleProvider.select((vehicle) => vehicle.frontRightTire)); + final rearRightTire = + ref.watch(vehicleProvider.select((vehicle) => vehicle.rearRightTire)); + + return Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + + children: [ + PSIProgressIndicator(value: frontRightTire.toDouble()), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + frontRightTire.toStringAsFixed(1), + style: GoogleFonts.brunoAce( + textStyle: TextStyle( + color: Colors.white, fontSize: 44), + ), + ), + SizedBox( + width: 5, + ), + PSIWidget(), + ], + ), + ], + ), + const ChildLockRight(), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + PSIProgressIndicator(value: rearRightTire.toDouble()), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + rearRightTire.toStringAsFixed(1), + style: GoogleFonts.brunoAce( + textStyle: TextStyle( + color: Colors.white, fontSize: 44), + ), + ), + SizedBox( + width: 5, + ), + PSIWidget(), + ], + ), + ], + ), + ], + ); + } +} + +class PSIProgressIndicator extends StatelessWidget { + final double value; + const PSIProgressIndicator({ + Key? key, + required this.value, // Require the value to be passed + }) : super(key: key); + + @override + Widget build(BuildContext context) { + // Calculate the width as a percentage of the full width (74 in this case) + final double fillWidth = (value / 35) * 74; + + return Stack( + alignment: AlignmentDirectional.centerStart, + children: [ + Container( + width: 100, + height: 24, + decoration: BoxDecoration( + border: GradientBoxBorder( + gradient: + LinearGradient(colors: const [Colors.white30, Colors.white]), + ), + ), + + ), + Positioned( + left: 3, + child: Container( + width: fillWidth, // Use the calculated width here + height: 18, // Match the height of the progress bar + decoration: BoxDecoration( + gradient: LinearGradient( + colors: const [AGLDemoColors.periwinkleColor, Colors.white], + stops: [ + 0.8, + 1, + ], + ), + ), + ), + ), + ], + ); + } +} + +class PSIWidget extends StatelessWidget { + const PSIWidget({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(left: 4.0, right: 1.0, bottom: 2.0), + child: Text( + 'PSI', + style: TextStyle( + fontSize: 26, + ), + ), + ); + } +} diff --git a/lib/presentation/screens/dashboard/widgets/child_lock.dart b/lib/presentation/screens/dashboard/widgets/child_lock.dart new file mode 100644 index 0000000..b8701d7 --- /dev/null +++ b/lib/presentation/screens/dashboard/widgets/child_lock.dart @@ -0,0 +1,96 @@ + +import 'package:flutter_ics_homescreen/export.dart'; + +class ChildLockLeft extends ConsumerWidget { + const ChildLockLeft({ + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isChildLockActiveLeft = ref.watch( + vehicleProvider.select((vehicle) => vehicle.isChildLockActiveLeft)); + + return GestureDetector( + onTap: () { + debugPrint('Tapped child lock left'); + ref.read(vehicleProvider.notifier).setChildLock(side: 'left'); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + const Text( + 'Child Lock', + style: TextStyle( + fontSize: 26, // Set the font size to 26 + ), + ), + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + Icon( + isChildLockActiveLeft ? Icons.lock : Icons.lock_open, + color: isChildLockActiveLeft ? Colors.white : Colors.redAccent, + size: 16, + ), + Text( + isChildLockActiveLeft ? 'Activated' : 'Unlocked', + style: TextStyle( + color: isChildLockActiveLeft ? Colors.white : Colors.redAccent, + fontSize: 26, // Set the font size to 26 + ), + ), + ], + ), + ], + ), + ); + } +} + +class ChildLockRight extends ConsumerWidget { + const ChildLockRight({ + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isChildLockActiveRight = ref.watch( + vehicleProvider.select((vehicle) => vehicle.isChildLockActiveRight)); + + return GestureDetector( + onTap: () { + debugPrint('Tapped child lock right'); + ref.read(vehicleProvider.notifier).setChildLock(side: 'right'); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Child Lock', + style: TextStyle( + fontSize: 26, // Set the font size to 26 + ), + ), + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + Text( + isChildLockActiveRight ? 'Activated' : 'Unlocked', + style: TextStyle( + color: isChildLockActiveRight ? Colors.white : Colors.redAccent, + fontSize: 26, // Set the font size to 26 + ), + ), + Icon( + isChildLockActiveRight ? Icons.lock : Icons.lock_open, + color: isChildLockActiveRight ? Colors.white : Colors.redAccent, + size: 16, + ), + ], + ), + ], + ), + ); + } +}
\ No newline at end of file diff --git a/lib/presentation/screens/dashboard/widgets/circle_indicator.dart b/lib/presentation/screens/dashboard/widgets/circle_indicator.dart new file mode 100644 index 0000000..7a4e724 --- /dev/null +++ b/lib/presentation/screens/dashboard/widgets/circle_indicator.dart @@ -0,0 +1,305 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +import 'custom_circle.dart'; + +class RPMProgressIndicator extends ConsumerStatefulWidget { + const RPMProgressIndicator({super.key}); + + @override + RPMProgressIndicatorState createState() => RPMProgressIndicatorState(); +} + +class RPMProgressIndicatorState extends ConsumerState<RPMProgressIndicator> + with TickerProviderStateMixin { + late AnimationController controller; + + @override + void initState() { + controller = AnimationController( + /// [AnimationController]s can be created with `vsync: this` because of + /// [TickerProviderStateMixin]. + vsync: this, + duration: const Duration(seconds: 5), + )..addListener(() { + //setState(() {}); + }); + controller.repeat(reverse: true); + super.initState(); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final rpm = + ref.watch(vehicleProvider.select((vehicle) => vehicle.engineSpeed)); + return Column( + children: [ + SizedBox( + height: 252, + child: Stack( + alignment: Alignment.center, + children: [ + + Text( + rpm.toString(), + style: GoogleFonts.brunoAce( + textStyle: const TextStyle(color: Colors.white, fontSize: 44), + ), + ), + Stack( + children: [ + if (rpm > 6500) + SizedBox( + height: 200, + width: 200, + child: CircularProgressIndicator( + strokeWidth: 12, + backgroundColor: Colors.transparent, + //value: controller.value, + valueColor: const AlwaysStoppedAnimation<Color>( + AGLDemoColors.redProgressStrokeColor), + value: rpm * (1 / maxRpm), + ), + ), + SizedBox( + height: 200, + width: 200, + child: CircularProgressIndicator( + strokeWidth: 12, + backgroundColor: Colors.transparent, + //value: controller.value, + valueColor: const AlwaysStoppedAnimation<Color>( + AGLDemoColors.jordyBlueColor), + value: rpm >= 6500 + ? 6500 * (1 / maxRpm) + : rpm * (1 / maxRpm), + ), + ), + ], + ), + SizedBox( + height: 220, + width: 220, + child: CustomPaint( + foregroundPainter: CirclePainter( + value: rpm.toDouble(), + maxValue: maxRpm.toDouble(), + isRPM: true, + ), + ), + ), + ], + ), + ), + const Text( + 'RPM', + style: TextStyle(color: Colors.white, fontSize: 40), + ), + ], + ); + } +} + + + +class SpeedProgressIndicator extends ConsumerStatefulWidget { + const SpeedProgressIndicator({super.key}); + + @override + SpeedProgressIndicatorState createState() => SpeedProgressIndicatorState(); +} + +class SpeedProgressIndicatorState extends ConsumerState<SpeedProgressIndicator> + with TickerProviderStateMixin { + late AnimationController controller; + + @override + void initState() { + controller = AnimationController( + /// [AnimationController]s can be created with `vsync: this` because of + /// [TickerProviderStateMixin]. + vsync: this, + duration: const Duration(seconds: 5), + )..addListener(() { + //setState(() {}); + }); + controller.repeat(reverse: true); + super.initState(); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final speed = ref.watch(vehicleProvider.select((vehicle) => vehicle.speed)); + final unit = + ref.watch(unitStateProvider.select((unit) => unit.distanceUnit)); + return Column( + children: [ + + SizedBox( + height: 252, + child: Stack( + alignment: Alignment.center, + children: [ + + Text( + unit == DistanceUnit.kilometers + ? speed.toStringAsFixed(0) + : (speed * 1.609).toStringAsFixed(0), + style: GoogleFonts.brunoAce( + textStyle: const TextStyle( + color: Colors.white, + fontSize: 44, + ), + ), + ), + SizedBox( + height: 200, + width: 200, + child: CircularProgressIndicator( + strokeWidth: 12, + //backgroundColor: const Color(0xFF2962FF), + //value: controller.value, + value: unit == DistanceUnit.kilometers + ? speed * (1 / maxSpeed) + : (speed * (1 / maxSpeed) * 1.609), + semanticsLabel: 'Speed progress indicator', + ), + ), + SizedBox( + height: 220, + width: 220, + child: CustomPaint( + foregroundPainter: + CirclePainter(value: speed, maxValue: maxSpeed), + ), + ), + ], + ), + + ), + Text( + unit == DistanceUnit.kilometers ? 'Km/h' : 'Mph', + style: const TextStyle(color: Colors.white, fontSize: 40), + ), + ], + ); + } +} + +class FuelProgressIndicator extends ConsumerStatefulWidget { + const FuelProgressIndicator({super.key}); + + @override + FuelProgressIndicatorState createState() => FuelProgressIndicatorState(); +} + +class FuelProgressIndicatorState extends ConsumerState<FuelProgressIndicator> + with TickerProviderStateMixin { + late AnimationController controller; + + @override + void initState() { + controller = AnimationController( + /// [AnimationController]s can be created with `vsync: this` because of + /// [TickerProviderStateMixin]. + vsync: this, + duration: const Duration(seconds: 5), + )..addListener(() { + //setState(() {}); + }); + controller.repeat(reverse: true); + super.initState(); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final fuelLevel = + ref.watch(vehicleProvider.select((vehicle) => vehicle.fuelLevel)); + return Column( + children: [ + SizedBox( + height: 252, + child: Stack( + alignment: Alignment.center, + children: [ + + Text( + '${(fuelLevel * (1 / maxFuelLevel) * 100).toStringAsFixed(0)}%', + style: GoogleFonts.brunoAce( + textStyle: const TextStyle( + color: Colors.white, + fontSize: 44, + ), + ), + ), + Stack( + children: [ + SizedBox( + height: 200, + width: 200, + child: CircularProgressIndicator( + strokeWidth: 12, + backgroundColor: Colors.transparent, + value: fuelLevel >= 12 + ? 12 * (1 / maxFuelLevel) + : fuelLevel * (1 / maxFuelLevel), + valueColor: const AlwaysStoppedAnimation<Color>( + AGLDemoColors.redProgressStrokeColor), + ), + ), + if (fuelLevel > 12) + SizedBox( + height: 200, + width: 200, + child: CircularProgressIndicator( + strokeWidth: 12, + backgroundColor: Colors.transparent, + //value: controller.value, + valueColor: const AlwaysStoppedAnimation<Color>( + AGLDemoColors.jordyBlueColor), + value: fuelLevel * (1 / maxFuelLevel), + ), + ), + + ], + ), + SizedBox( + height: 220, + width: 220, + child: CustomPaint( + foregroundPainter: CirclePainter( + value: fuelLevel, + maxValue: maxFuelLevel, + isFuel: true, + isRPM: false), + ), + ), + ], + ), + ), + const Text( + 'Fuel', + style: TextStyle(color: Colors.white, fontSize: 40), + ), + ], + ); + } +} + diff --git a/lib/presentation/screens/dashboard/widgets/custom_circle.dart b/lib/presentation/screens/dashboard/widgets/custom_circle.dart new file mode 100644 index 0000000..4e26f0b --- /dev/null +++ b/lib/presentation/screens/dashboard/widgets/custom_circle.dart @@ -0,0 +1,107 @@ +import 'dart:math' as math; + +import 'package:flutter_ics_homescreen/export.dart'; + +class CirclePainter extends CustomPainter { + final double value; + final double maxValue; + final bool? isRPM; + final bool? isFuel; + + CirclePainter({ + required this.value, + required this.maxValue, + this.isRPM = false, + this.isFuel = false, + }); + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = AGLDemoColors.neonBlueColor + ..strokeWidth = 2 + ..style = PaintingStyle.stroke; + final paintRed = Paint() + ..color = const Color(0xFFBF360C) + ..strokeWidth = 2 + ..style = PaintingStyle.stroke; + + final smallCirclePaint = Paint() + ..color = AGLDemoColors.resolutionBlueColor + ..strokeWidth = 10 + // Use [PaintingStyle.fill] if you want the circle to be filled. + ..style = PaintingStyle.fill; + + final center = Offset(size.width / 2, size.height / 2); + + final double radius = (size.width / 2) - 10; + + const totalDegree = 360; + + // Total ticks to display + var totalTicks = isFuel! ? 4 : 8; + + var values = []; + for (int i = 0; i < totalTicks; i++) { + values.add(i * (maxValue / totalTicks)); + } + + /// The angle between each tick + var unitAngle = totalDegree / totalTicks; + for (int i = 0; i < totalTicks; i++) { + final angle = -90.0.radians + (i * unitAngle).radians; + final xOffset = radius * math.cos(angle); + final yOffset = radius * math.sin(angle); + final offset = Offset(center.dx + xOffset, center.dy + yOffset); + if (value > values[i]) { + canvas.drawCircle(offset, 3, smallCirclePaint); + } else { + canvas.drawCircle(offset, 3, smallCirclePaint..color = Colors.white); + } + } + + final rect = Rect.fromCenter( + center: center, + width: ((size.width / 2.4) * 2) + 2, + height: (size.width / 2.4) * 2 + 2, + ); + canvas.drawArc( + rect, + _deg2Rads(-90), + _deg2Rads(360), + false, + paint, + ); + if (isRPM == true) { + canvas.drawArc( + rect, + _deg2Rads(202), + _deg2Rads(68), + false, + paintRed, + ); + } + if (isFuel == true) { + canvas.drawArc( + rect, + _deg2Rads(-90), + _deg2Rads(80), + false, + paintRed, + ); + } + + //canvas.drawArc(rect, pi / 4, pi * 3 / 4, false, paint); + } + + double _deg2Rads(num deg) { + return (deg * math.pi) / 180.0; + } + + @override + bool shouldRepaint(oldDelegate) => false; +} + +extension on num { + /// This is an extension we created so we can easily convert a value /// to a radian value + double get radians => (this * math.pi) / 180.0; +} diff --git a/lib/presentation/screens/dashboard/widgets/dashboard_content.dart b/lib/presentation/screens/dashboard/widgets/dashboard_content.dart new file mode 100644 index 0000000..74f0d2a --- /dev/null +++ b/lib/presentation/screens/dashboard/widgets/dashboard_content.dart @@ -0,0 +1,108 @@ +import 'dart:math'; + +import 'package:flutter_ics_homescreen/export.dart'; + +class DashBoard extends ConsumerStatefulWidget { + const DashBoard({super.key}); + + @override + DashBoardState createState() => DashBoardState(); +} + +class DashBoardState extends ConsumerState<DashBoard> + with SingleTickerProviderStateMixin { + late AnimationController _animationController; + late Animation<double> _animation; + static bool _isAnimationPlayed = false; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + duration: const Duration(milliseconds: 1800), + vsync: this, + value: _isAnimationPlayed ? 1.0 : 0.0, + ); + + _animation = CurvedAnimation( + parent: _animationController, + curve: Curves.easeIn, + ); + + // Start the animation on first build. + if (!_isAnimationPlayed) { + Future.delayed(const Duration(milliseconds: 500), () { + _animationController.forward(); + _isAnimationPlayed = true; + }); + } + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + Widget svgImage = Align( + alignment: Alignment.bottomCenter, + child: SvgPicture.asset( + 'assets/Car Illustration.svg', + width: 625, + height: 440, + fit: BoxFit.fitHeight, + ), + ); + + Widget fadeContent = FadeTransition( + opacity: _animation, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.center, + children: <Widget>[ + const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + //mainAxisSize: MainAxisSize.max, + children: [ + RPMProgressIndicator(), + SpeedProgressIndicator(), + FuelProgressIndicator(), + ], + ), + GestureDetector( + onTap: () { + Random random = Random(); + int randomState = random.nextInt(4); + var hybridState = HybridState.values[randomState]; + ref + .read(hybridtateProvider.notifier) + .setHybridState(hybridState); + }, + child: const HybridModel()), + const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TemperatureWidget(), + RangeWidget(), + ], + ), + const CarStatus(), + ], + )); + + return Stack( + alignment: Alignment.center, + children: [ + Positioned.fill( + child: fadeContent, + ), + Positioned( + bottom: 138, + child: svgImage, + ), + ], + ); + } +} diff --git a/lib/presentation/screens/dashboard/widgets/hybrid/hybrid.dart b/lib/presentation/screens/dashboard/widgets/hybrid/hybrid.dart new file mode 100644 index 0000000..b6844de --- /dev/null +++ b/lib/presentation/screens/dashboard/widgets/hybrid/hybrid.dart @@ -0,0 +1,142 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class HybridBackround extends StatelessWidget { + const HybridBackround({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return SvgPicture.asset('animations/hybrid_model/hybrid_bg.svg'); + } +} + +class TopArrow extends StatelessWidget { + const TopArrow({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Align( + alignment: const Alignment(0, -0.75), + child: Consumer(builder: (context, ref, child) { + final state = ref.watch(hybridtateProvider.select((hybrid) => hybrid)); + Widget? widget; + switch (state.topArrowState) { + case ArrowState.blue: + widget = SvgPicture.asset( + 'animations/hybrid_model/top_blue.svg', + ); + break; + case ArrowState.red: + widget = Lottie.asset('animations/hybrid_model/top_arrow_red.json'); + + break; + + default: + } + + return widget ?? + SvgPicture.asset( + 'animations/hybrid_model/left_blue.svg', + ); + }), + ); + } +} + +class LeftArrow extends StatelessWidget { + const LeftArrow({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Align( + alignment: const Alignment(-0.7, 0.5), + child: Consumer(builder: (context, ref, child) { + final state = ref.watch(hybridtateProvider.select((hybrid) => hybrid)); + Widget? widget; + switch (state.leftArrowState) { + case ArrowState.blue: + widget = SvgPicture.asset( + 'animations/hybrid_model/left_blue.svg', + ); + break; + case ArrowState.red: + widget = + Lottie.asset('animations/hybrid_model/left_arrow_red.json'); + + break; + + default: + } + + return widget ?? + SvgPicture.asset( + 'animations/hybrid_model/left_blue.svg', + ); + }), + ); + } +} + +class RightArrow extends StatelessWidget { + const RightArrow({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Align( + alignment: const Alignment(0.70, 0.5), + child: Consumer(builder: (context, ref, child) { + final state = ref.watch(hybridtateProvider.select((hybrid) => hybrid)); + + Widget? widget; + switch (state.rightArrowState) { + case ArrowState.blue: + widget = SvgPicture.asset( + 'animations/hybrid_model/right_blue.svg', + ); + break; + case ArrowState.yellow: + widget = + Lottie.asset('animations/hybrid_model/right_arrow_yellow.json'); + + break; + case ArrowState.green: + widget = + Lottie.asset('animations/hybrid_model/right_arrow_green.json'); + + break; + default: + } + + return widget ?? + SvgPicture.asset( + 'animations/hybrid_model/right_blue.svg', + ); + }), + ); + } +} + +class BatteryHybrid extends ConsumerWidget { + const BatteryHybrid({ + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final batteryState = + ref.watch(hybridtateProvider.select((hybrid) => hybrid.batteryState)); + return Align( + alignment: const Alignment(0, 0.8), + child: SvgPicture.asset( + 'animations/hybrid_model/battery_${batteryState.name}.svg', + ), + ); + } +} diff --git a/lib/presentation/screens/dashboard/widgets/hybrid_mode.dart b/lib/presentation/screens/dashboard/widgets/hybrid_mode.dart new file mode 100644 index 0000000..9a657b8 --- /dev/null +++ b/lib/presentation/screens/dashboard/widgets/hybrid_mode.dart @@ -0,0 +1,30 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class HybridModel extends StatefulWidget { + const HybridModel({super.key}); + + @override + State<HybridModel> createState() => _HybridModelState(); +} + +class _HybridModelState extends State<HybridModel> { + @override + Widget build(BuildContext context) { + + return GestureDetector( + child: const SizedBox( + width: 500, + height: 500, + child: Stack( + children: [ + HybridBackround(), + TopArrow(), + LeftArrow(), + RightArrow(), + BatteryHybrid(), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/screens/dashboard/widgets/range.dart b/lib/presentation/screens/dashboard/widgets/range.dart new file mode 100644 index 0000000..aea92af --- /dev/null +++ b/lib/presentation/screens/dashboard/widgets/range.dart @@ -0,0 +1,85 @@ +import 'package:flutter_ics_homescreen/presentation/custom_icons/custom_icons.dart'; + +import '../../../../export.dart'; + +class RangeWidget extends ConsumerWidget { + const RangeWidget({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final range = ref.watch(vehicleProvider.select((vehicle) => vehicle.range)); + final unit = + ref.watch(unitStateProvider.select((unit) => unit.distanceUnit)); + return Container( + height:130, + width: 306, + // padding: const EdgeInsets.all(10), + decoration: const ShapeDecoration( + gradient: RadialGradient( + colors: [ + Color.fromARGB(255, 19, 24, 75), + Color.fromARGB(127, 0, 0, 0) + ], + stops: [0, 0.7], + radius: 1, + ), + //color: Colors.grey, + shape: StadiumBorder( + side: BorderSide( + color: Color.fromARGB(156, 0, 0, 0), + width: 2, + )), + ), + alignment: Alignment.topLeft, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + CustomIcons.range, + color: Color(0xFF2962FF), + size: 48, + ), + const SizedBox(width: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Range', + textAlign: TextAlign.start, + style: TextStyle( + color: Color(0xFFC1D8FF), + fontSize: 26, + ), + ), + RichText( + text: TextSpan( + + text: '$range', + style: GoogleFonts.brunoAce( + + textStyle: + const TextStyle( + color: Colors.white, + fontSize: 44, + ), + ), + children: <TextSpan>[ + TextSpan( + text: + unit == DistanceUnit.kilometers ? ' Km' : ' Mi', + style: GoogleFonts.brunoAce( + textStyle: const TextStyle( + color: Color(0xFFC1D8FF), + fontSize: 38), + ), + ), + ]), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/presentation/screens/dashboard/widgets/temperature.dart b/lib/presentation/screens/dashboard/widgets/temperature.dart new file mode 100644 index 0000000..0817b53 --- /dev/null +++ b/lib/presentation/screens/dashboard/widgets/temperature.dart @@ -0,0 +1,143 @@ +import '../../../../export.dart'; + +class TemperatureWidget extends ConsumerWidget { + const TemperatureWidget({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final temperature = ref.watch(vehicleProvider.select((vehicle) => vehicle)); + // final outsideTemperature = ref + // .watch(vehicleProvider.select((vehicle) => vehicle.outsideTemperature)); + final tempUnit = + ref.watch(unitStateProvider.select((unit) => unit.temperatureUnit)); + + TextStyle temperatureTextStyle = const TextStyle( + fontFamily: 'BrunoAce', + color: Colors.white, + fontSize: 44, + ); + + TextStyle unitTextStyle = const TextStyle( + fontFamily: 'BrunoAce', + color: Color(0xFFC1D8FF), + fontSize: 38, + ); + + return Container( + width: + 442, // needs to be adjusted after the celsius and farenheight symbols are fixed + height: 130, // Height of the oval + //padding: const EdgeInsets.all(10), + decoration: ShapeDecoration( + gradient: const RadialGradient( + colors: [ + Color.fromARGB(255, 19, 24, 75), + Color.fromARGB(127, 0, 0, 0) + ], + stops: [0.0, 0.7], + radius: 1, + ), + //color: Colors.grey, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(65), // Half of the height for an oval effect + side: const BorderSide( + color: Color.fromARGB(156, 0, 0, 0), + width: 2, + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Inside temperature + buildTemperatureRow( + context, + Icons.thermostat_outlined, + "Inside", + temperature.insideTemperature, + tempUnit, + temperatureTextStyle, + unitTextStyle, + false, + ), + const SizedBox(width: 10), + // Outside temperature + buildTemperatureRow( + context, + Icons.thermostat_outlined, + "Outside", + temperature.outsideTemperature, + tempUnit, + temperatureTextStyle, + unitTextStyle, + true, + ), + ], + ), + ); + } + + Widget buildTemperatureRow( + BuildContext context, + IconData icon, + String label, + double temperatureValue, + TemperatureUnit tempUnit, + TextStyle tempTextStyle, + TextStyle unitTextStyle, + bool isOutside, + + ) { + int temperatureAsInt = temperatureValue.toInt(); + double convertedTemperature = tempUnit == TemperatureUnit.celsius + ? temperatureAsInt.toDouble() + : (temperatureAsInt * 9 / 5) + 32; + + // Format the temperature for display. + String temperatureDisplay = tempUnit == TemperatureUnit.celsius + ? '$temperatureAsInt' + : '${convertedTemperature.toStringAsFixed(0)}'; + + return Padding( + padding: isOutside + ? const EdgeInsets.only(right: 22) // Padding for the outside temperature + : const EdgeInsets.only(left: 12), // Padding for the inside temperature + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + icon, + color: const Color(0xFF2962FF), + size: 48, + ), + const SizedBox(width: 4), // Space between icon and text + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + label, + style: const TextStyle( + color: Color(0xFFC1D8FF), + fontSize: 26, + ), + ), + RichText( + text: TextSpan( + text: temperatureDisplay, + style: tempTextStyle, + children: <TextSpan>[ + TextSpan( + text: tempUnit == TemperatureUnit.celsius ? '°C' : '°F', + style: unitTextStyle, + ), + ], + ), + ), + ], + ), + ], + ), + ); + } +}
\ No newline at end of file diff --git a/lib/presentation/screens/home/home.dart b/lib/presentation/screens/home/home.dart new file mode 100644 index 0000000..c132c98 --- /dev/null +++ b/lib/presentation/screens/home/home.dart @@ -0,0 +1,71 @@ +import 'package:flutter_ics_homescreen/export.dart'; +// import 'package:media_kit_video/media_kit_video.dart'; + +class HomeScreen extends ConsumerStatefulWidget { + const HomeScreen({ + super.key, + }); + + @override + HomeScreenState createState() => HomeScreenState(); +} + +class HomeScreenState extends ConsumerState<HomeScreen> { + + + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + // player.dispose(); + super.dispose(); + } + + @override + Widget build( + BuildContext context, + ) { + return Consumer(builder: (context, ref, child) { + final state = ref.read(appProvider); + return Scaffold( + key: homeScaffoldKey, + extendBody: true, + extendBodyBehindAppBar: true, + appBar: const CustomTopBar(), + body: Stack( + children: [ + /* + Lottie.asset( + 'animations/BG-dotwaveform.json', + fit: BoxFit.cover, + repeat: true, + ), + */ + FlowBuilder<AppState>( + state: ref.watch(appProvider), + onGeneratePages: onGenerateAppViewPages, + observers: [ + HeroController(), + ], + ), + if (state != AppState.splash) + Positioned( + top: 0, + bottom: 0, + child: Container( + padding: const EdgeInsets.only(left: 8), + height: 500, + child: const VolumeFanControl()), + ), + ], + ), + bottomNavigationBar: + state == AppState.splash ? null : const CustomBottomBar(), + ); + }); + } +} diff --git a/lib/presentation/screens/home/widgets/custom_tile.dart b/lib/presentation/screens/home/widgets/custom_tile.dart new file mode 100644 index 0000000..389a75d --- /dev/null +++ b/lib/presentation/screens/home/widgets/custom_tile.dart @@ -0,0 +1,50 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class CustomTile extends StatelessWidget { + final String name; + final Color color; + final VoidCallback callback; + const CustomTile({ + Key? key, + required this.name, + required this.color, + required this.callback, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + // Calculate the container size based on the app size + //final size = MediaQuery.of(context).size; + // final width = size.width * 0.15; + // final height = size.height * 0.15; + return Expanded( + child: GestureDetector( + onTap: callback, + child: Padding( + padding: const EdgeInsets.fromLTRB(0.0, 8.0, 8.0, 8.0), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 10, + ), + height: 150, //height, + width: 150, //width, + color: color, + child: Center( + child: Text( + name, + textAlign: TextAlign.center, + overflow: TextOverflow.fade, + style: const TextStyle( + color: Colors.white, + //fontSize: width * 0.15, + fontSize: 18, + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/presentation/screens/hvac/hvac.dart b/lib/presentation/screens/hvac/hvac.dart new file mode 100644 index 0000000..ebdaea4 --- /dev/null +++ b/lib/presentation/screens/hvac/hvac.dart @@ -0,0 +1,45 @@ +import '/export.dart'; + +class HvacPage extends StatelessWidget { + const HvacPage({super.key}); + + static Page<void> page() => const MaterialPage<void>(child: HvacPage()); + @override + Widget build(BuildContext context) { + // print(MediaQuery.of(context).size); + // print(MediaQuery.of(context).size.width * + // MediaQuery.of(context).devicePixelRatio); + // print(MediaQuery.of(context).size.height * + // MediaQuery.of(context).devicePixelRatio); + return Stack( + children: [ + // SizedBox( + // width: double.infinity, + // height: double.infinity, + // //color: Colors.black, + // // decoration: + // // BoxDecoration(gradient: AGLDemoColors.gradientBackgroundColor), + // child: SvgPicture.asset( + // 'assets/HVACBackground.svg', + // alignment: Alignment.center, + // fit: BoxFit.cover, + // //width: 200, + // //height: 200, + // ), + // ), + SizedBox( + width: double.infinity, + height: double.infinity, + // color: Colors.black, + child: SvgPicture.asset( + 'assets/backgroundTextures.svg', + alignment: Alignment.center, + //width: 200, + //height: 200, + ), + ), + const SingleChildScrollView(child: HVAC()), + ], + ); + } +} diff --git a/lib/presentation/screens/hvac/hvac_content.dart b/lib/presentation/screens/hvac/hvac_content.dart new file mode 100644 index 0000000..f79ec14 --- /dev/null +++ b/lib/presentation/screens/hvac/hvac_content.dart @@ -0,0 +1,249 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class HVAC extends ConsumerStatefulWidget { + const HVAC({super.key}); + + @override + HVACState createState() => HVACState(); +} + +class HVACState extends ConsumerState<HVAC> { + bool isFanFocusLeftTopSelected = false; + bool isFanFocusRightTopSelected = true; + bool isFanFocusLeftBottomSelected = true; + bool isFanFocusRightBottomSelected = false; + + late bool isACSelected; + bool isSYNCSelected = true; + late bool isFrontDefrostSelected; + bool isAutoSelected = true; + late bool isRecirculationSelected; + late bool isRearDefrostSelected; + + int temperatureLeft = 26; + int temperatureRight = 26; + @override + void initState() { + super.initState(); + } + + TextStyle climateControlTextStyle = GoogleFonts.raleway( + color: AGLDemoColors.periwinkleColor, + fontSize: 44, + height: 1.25, + fontWeight: FontWeight.w500, + shadows: [ + Shadow( + offset: const Offset(1, 2), + blurRadius: 3, + color: Colors.black.withOpacity(0.7)) + ]); + TextStyle climateControlSelectedTextStyle = GoogleFonts.raleway( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 44, + height: 1.25, + shadows: [ + Shadow( + offset: const Offset(1, 2), + blurRadius: 3, + color: Colors.black.withOpacity(0.7)) + ]); + + @override + Widget build(BuildContext context) { + final vehicle = ref.watch(vehicleProvider.select((vehicle) => vehicle)); + isACSelected = vehicle.isAirConditioningActive; + isFrontDefrostSelected = vehicle.isFrontDefrosterActive; + isRearDefrostSelected = vehicle.isRearDefrosterActive; + isRecirculationSelected = vehicle.isRecirculationActive; + Size size = MediaQuery.sizeOf(context); + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox( + height: 83, + ), + Row( + children: [ + SizedBox( + width: size.width * 0.125, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Padding( + padding: EdgeInsets.symmetric(vertical: 20), + child: Text( + "Left", + style: TextStyle(color: Colors.white, fontSize: 40), + textAlign: TextAlign.center, + ), + ), + FanFocus( + onPressed: () { + setState(() { + isFanFocusLeftTopSelected = !isFanFocusLeftTopSelected; + }); + }, + isSelected: isFanFocusLeftTopSelected, + focusType: "top_half"), + const SizedBox( + height: 12, + ), + FanFocus( + onPressed: () { + setState(() { + isFanFocusLeftBottomSelected = + !isFanFocusLeftBottomSelected; + }); + }, + isSelected: isFanFocusLeftBottomSelected, + focusType: "bottom_half") + ], + )), + SizedBox( + width: size.width * 0.05, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Padding( + padding: EdgeInsets.symmetric(vertical: 20), + child: Text( + "Right", + style: TextStyle(color: Colors.white, fontSize: 40), + textAlign: TextAlign.center, + ), + ), + FanFocus( + onPressed: () { + setState(() { + isFanFocusRightTopSelected = + !isFanFocusRightTopSelected; + }); + }, + isSelected: isFanFocusRightTopSelected, + focusType: "top_half"), + const SizedBox( + height: 12, + ), + FanFocus( + onPressed: () { + setState(() { + isFanFocusRightBottomSelected = + !isFanFocusRightBottomSelected; + }); + }, + isSelected: isFanFocusRightBottomSelected, + focusType: "bottom_half") + ], + )), + SizedBox( + width: size.width * 0.1, + ), + ], + ), + const SizedBox( + height: 80, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + TemperatureControl(temperature: temperatureLeft), + TemperatureControl(temperature: temperatureRight) + ], + ), + const SizedBox( + height: 170, + ), + const FanSpeedControls(), + const SizedBox( + height: 70, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ClimateControls( + isSelected: isACSelected, + onPressed: () { + ref + .read(vehicleProvider.notifier) + .setHVACMode(mode: 'airCondition'); + }, + child: Text( + "A/C", + style: isACSelected + ? climateControlSelectedTextStyle + : climateControlTextStyle, + )), + ClimateControls( + onPressed: () { + setState(() { + isSYNCSelected = !isSYNCSelected; + }); + }, + isSelected: isSYNCSelected, + child: Text( + "SYNC", + style: isSYNCSelected + ? climateControlSelectedTextStyle + : climateControlTextStyle, + )), + ClimateControls( + onPressed: () { + ref + .read(vehicleProvider.notifier) + .setHVACMode(mode: 'frontDefrost'); + }, + isSelected: isFrontDefrostSelected, + child: SvgPicture.asset( + "assets/${isFrontDefrostSelected ? "FrontDefrostFilled.svg" : "FrontDefrost.svg"}", + )) + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ClimateControls( + isSelected: isAutoSelected, + onPressed: () { + setState(() { + isAutoSelected = !isAutoSelected; + }); + }, + child: Text( + "AUTO", + style: isAutoSelected + ? climateControlSelectedTextStyle + : climateControlTextStyle, + )), + ClimateControls( + onPressed: () { + ref + .read(vehicleProvider.notifier) + .setHVACMode(mode: 'recirculation'); + }, + isSelected: isRecirculationSelected, + child: SvgPicture.asset( + "assets/${isRecirculationSelected ? "RecirculationFilled.svg" : "Recirculation.svg"}", + )), + ClimateControls( + onPressed: () { + ref + .read(vehicleProvider.notifier) + .setHVACMode(mode: 'rearDefrost'); + }, + isSelected: isRearDefrostSelected, + child: SvgPicture.asset( + "assets/${isRearDefrostSelected ? "BackDefrostFilled.svg" : "BackDefrost.svg"}", + )) + ], + ) + ], + ); + } +} diff --git a/lib/presentation/screens/hvac/widgets/climate_controls.dart b/lib/presentation/screens/hvac/widgets/climate_controls.dart new file mode 100644 index 0000000..c7dcd52 --- /dev/null +++ b/lib/presentation/screens/hvac/widgets/climate_controls.dart @@ -0,0 +1,80 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class ClimateControls extends StatefulWidget { + const ClimateControls( + {super.key, + required this.child, + required this.isSelected, + required this.onPressed}); + final Widget child; + final bool isSelected; + final VoidCallback onPressed; + + @override + State<ClimateControls> createState() => _ClimateControlsState(); +} + +class _ClimateControlsState extends State<ClimateControls> { + @override + Widget build(BuildContext context) { + Size size = MediaQuery.sizeOf(context); + + return Container( + margin: const EdgeInsets.all(8), + width: size.width * 0.23, + height: size.height * 0.07, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2), + boxShadow: [ + BoxShadow( + offset: Offset( + widget.isSelected ? 0 : 1, widget.isSelected ? 4 : 2), + blurRadius: widget.isSelected ? 4 : 3, + spreadRadius: 0, + color: Colors.black.withOpacity(widget.isSelected ? 0.25 : 0.7)) + ], + gradient: LinearGradient( + colors: widget.isSelected + ? [ + AGLDemoColors.periwinkleColor, + AGLDemoColors.periwinkleColor.withOpacity(0.25) + ] + : [ + AGLDemoColors.neonBlueColor, + AGLDemoColors.neonBlueColor.withOpacity(0.2) + ], + begin: Alignment.centerLeft, + end: Alignment.centerRight), + border: Border.all(color: Colors.white12)), + child: Container( + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2), + boxShadow: [ + BoxShadow( + offset: Offset( + widget.isSelected ? 0 : 1, widget.isSelected ? 4 : 2), + blurRadius: widget.isSelected ? 4 : 3, + spreadRadius: 0, + color: + Colors.black.withOpacity(widget.isSelected ? 0.25 : 0.7)) + ], + color: widget.isSelected + ? AGLDemoColors.neonBlueColor + : AGLDemoColors.buttonFillEnabledColor, + border: Border.all(color: Colors.white12)), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: widget.onPressed, + child: Center( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: widget.child, + )), + ), + ), + ), + ); + } +} diff --git a/lib/presentation/screens/hvac/widgets/fan_focus.dart b/lib/presentation/screens/hvac/widgets/fan_focus.dart new file mode 100644 index 0000000..556c2c7 --- /dev/null +++ b/lib/presentation/screens/hvac/widgets/fan_focus.dart @@ -0,0 +1,116 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class FanFocus extends StatefulWidget { + const FanFocus( + {super.key, + required this.isSelected, + required this.focusType, + required this.onPressed}); + final bool isSelected; + final String focusType; + final VoidCallback onPressed; + @override + State<FanFocus> createState() => _FanFocusState(); +} + +class _FanFocusState extends State<FanFocus> { + @override + Widget build(BuildContext context) { + double height = MediaQuery.sizeOf(context).height * 0.10; + double iconSize = 32; + + return Container( + height: height, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: widget.isSelected + ? [ + AGLDemoColors.periwinkleColor, + AGLDemoColors.periwinkleColor.withOpacity(0.25) + ] + : [ + AGLDemoColors.jordyBlueColor, + AGLDemoColors.jordyBlueColor.withOpacity(0.2) + ]), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(widget.focusType == "top_half" ? 16 : 0), + topRight: Radius.circular(widget.focusType == "top_half" ? 16 : 0), + bottomLeft: + Radius.circular(widget.focusType == "bottom_half" ? 16 : 0), + bottomRight: + Radius.circular(widget.focusType == "bottom_half" ? 16 : 0)), + ), + child: Container( + margin: const EdgeInsets.all(1), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(widget.focusType == "top_half" ? 16 : 0), + topRight: + Radius.circular(widget.focusType == "top_half" ? 16 : 0), + bottomLeft: + Radius.circular(widget.focusType == "bottom_half" ? 16 : 0), + bottomRight: + Radius.circular(widget.focusType == "bottom_half" ? 16 : 0)), + color: widget.isSelected + ? AGLDemoColors.neonBlueColor + : AGLDemoColors.buttonFillEnabledColor, + image: const DecorationImage( + image: AssetImage("assets/PlusVector.png"), + opacity: 0.5, + fit: BoxFit.cover), + ), + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.only( + topLeft: + Radius.circular(widget.focusType == "top_half" ? 16 : 0), + topRight: + Radius.circular(widget.focusType == "top_half" ? 16 : 0), + bottomLeft: + Radius.circular(widget.focusType == "bottom_half" ? 16 : 0), + bottomRight: Radius.circular( + widget.focusType == "bottom_half" ? 16 : 0)), + onTap: widget.onPressed, + child: Row( + crossAxisAlignment: widget.focusType == "top_half" + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(top: 38), + child: Icon( + Icons.arrow_forward, + color: Colors.white, + size: iconSize, + shadows: [ + Shadow( + offset: Offset(1, widget.isSelected ? 2 : 4), + blurRadius: widget.isSelected ? 3 : 4, + color: Colors.black.withOpacity(0.7)) + ], + ), + ), + Image.asset( + "assets/${widget.focusType == "top_half" ? widget.isSelected ? "head_selected" : "head" : widget.isSelected ? "legs_selected" : "legs"}.png", + //fit: BoxFit.contain, + // alignment: Alignment.bottomRight, + // width: widget.focusType == "top_half" ? 108 : 250, + // height: 180, + ), + ], + ), + SizedBox( + width: widget.focusType == "top_half" ? 5 : 40, + ) + ]), + ), + ), + ), + ); + } +} diff --git a/lib/presentation/screens/hvac/widgets/fan_speed_controls.dart b/lib/presentation/screens/hvac/widgets/fan_speed_controls.dart new file mode 100644 index 0000000..00f1181 --- /dev/null +++ b/lib/presentation/screens/hvac/widgets/fan_speed_controls.dart @@ -0,0 +1,251 @@ +import 'package:flutter_ics_homescreen/export.dart'; +import 'package:gradient_borders/gradient_borders.dart'; +import 'package:rive/rive.dart' as rive; + +class FanSpeedControls extends ConsumerStatefulWidget { + const FanSpeedControls({super.key}); + + @override + FanSpeedControlsState createState() => FanSpeedControlsState(); +} + +class FanSpeedControlsState extends ConsumerState<FanSpeedControls> + with TickerProviderStateMixin { + bool isPressed = false; + LinearGradient gradientEnable1 = const LinearGradient(colors: <Color>[ + Color(0xFF2962FF), + Color(0x802962FF), + ]); + LinearGradient gradientEnable2 = const LinearGradient(colors: <Color>[ + Color(0xFF1A237E), + Color(0xFF141F64), + ]); + bool isMainACSelected = false; + late AnimationController animationController; + double controlProgress = 0.0; + int selectedFanSpeed = 0; + late rive.RiveAnimationController _controller; + + bool _isPlaying = false; + + /// Tracks if the animation is playing by whether controller is running + bool get isPlaying => _controller.isActive; + + @override + void initState() { + super.initState(); + _controller = rive.OneShotAnimation( + 'Fan Spin', + autoplay: false, + onStop: () => setState(() => _isPlaying = false), + onStart: () => setState(() => _isPlaying = true), + ); + animationController = AnimationController( + vsync: this, + duration: const Duration(seconds: 1), + ); + + animationController.addListener(() { + setState(() { + // _currentColorIndex = (_currentColorIndex + 1) % colorsList.length; + }); // Trigger a rebuild to repaint the CustomPaint + }); + animationController.forward(); + } + + @override + void dispose() { + animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + double size = MediaQuery.sizeOf(context).height * 0.13021; + double fanSpeedWidth = MediaQuery.sizeOf(context).width * 0.35; + double fanSpeedHeight = MediaQuery.sizeOf(context).height * 0.15; + double strokeWidth = MediaQuery.sizeOf(context).height * 0.03; + + double iconSize = 80; + + final vehicle = ref.watch(vehicleProvider.select((vehicle) => vehicle)); + selectedFanSpeed = vehicle.fanSpeed; + controlProgress = selectedFanSpeed * 0.3; + + return Stack( + children: [ + Center( + child: CustomPaint( + size: Size( + fanSpeedWidth, fanSpeedHeight), // Set the desired size here + painter: AnimatedColorPainter( + animationController, + controlProgress, + AGLDemoColors.blueGlowFillColor, + AGLDemoColors.backgroundInsetColor, + strokeWidth, + ), + ), + ), + Center( + child: Container( + margin: const EdgeInsets.only(top: 3), + // decoration: BoxDecoration( + // shape: BoxShape.circle, + // gradient: LinearGradient( + // colors: !isMainACSelected + // ? [ + // AGLDemoColors.neonBlueColor, + // AGLDemoColors.neonBlueColor.withOpacity(0.2) + // ] + // : [ + // const Color.fromARGB(255, 255, 193, 193) + // .withOpacity(0.2), + // const Color.fromARGB(255, 255, 193, 193) + // ]), + // boxShadow: isMainACSelected + // ? [ + // BoxShadow( + // offset: Offset( + // isMainACSelected ? 1 : 1, isMainACSelected ? 2 : 2), + // blurRadius: isMainACSelected ? 16 : 16, + // spreadRadius: 0, + // color: isMainACSelected + // ? Colors.black.withOpacity(0.5) + // : Colors.black) + // ] + // : [], + // ), + //border: Border.all(color: Colors.white12, width: 1)), + //width: 90, + //height: 90, + child: Container( + margin: const EdgeInsets.all(1), + decoration: BoxDecoration( + shape: BoxShape.circle, + image: const DecorationImage( + image: AssetImage("assets/PlusVector.png"), + ), + gradient: Gradient.lerp(gradientEnable1, gradientEnable2, 0.5), + // border: Border.all( + // color: isMainACSelected + // ? AGLDemoColors.buttonFillEnabledColor + // : Colors.white12, + // width: isMainACSelected ? 3 : 1), + border: const GradientBoxBorder( + width: 2, + gradient: LinearGradient( + colors: [ + Color(0x30C1D8FF), + Color(0xFFC1D8FF), + ], + ), + ), + ), + alignment: Alignment.center, + child: Material( + color: Colors.transparent, + child: InkWell( + splashColor: Colors.transparent, + hoverColor: Colors.transparent, + highlightColor: Colors.transparent, + customBorder: const CircleBorder(), + onTap: () { + _isPlaying ? null : _controller.isActive = true; + setState(() { + if (controlProgress >= 0.80) { + controlProgress = 0.0; + isMainACSelected = false; + animationController.reverse(); + } else { + isMainACSelected = true; + _controller.isActive = true; + _isPlaying = true; + controlProgress += 0.30; + animationController.forward(); + } + ref + .read(vehicleProvider.notifier) + .updateFanSpeed(controlProgress ~/ 0.3); + + // isMainACSelected = !isMainACSelected; + // if (controlProgress != 0.0) { + // previousProgress = controlProgress; + // } + // if (isMainACSelected) { + // controlProgress = previousProgress; + // animationController.forward(); + // } else { + // controlProgress = 0.0; + // animationController.reverse(); + // } + }); + }, + onTapDown: (details) { + setState(() { + gradientEnable1 = LinearGradient(colors: <Color>[ + const Color(0xFF2962FF).withOpacity(0.15), + const Color(0x802962FF).withOpacity(0.15), + ]); + gradientEnable2 = const LinearGradient(colors: <Color>[ + Color(0xFF1A237E), + Color(0xFF1C2D92), + ]); + }); + //change style + }, + onTapUp: (details) { + setState(() { + gradientEnable1 = const LinearGradient(colors: <Color>[ + Color(0xFF2962FF), + Color(0x802962FF), + ]); + gradientEnable2 = const LinearGradient(colors: <Color>[ + Color(0xFF1A237E), + Color(0xFF141F64), + ]); + }); + }, + child: Container( + width: size, + height: size, + alignment: Alignment.center, + child: !_isPlaying && controlProgress == 0.0 + ? SvgPicture.asset( + "assets/ACMainButtonOff.svg", + width: iconSize, + height: iconSize, + ) + // : !_isPlaying && controlProgress > 0.8 + // ? SvgPicture.asset( + // "assets/ACMainButton.svg", + // width: iconSize, + // height: iconSize, + // ) + : SizedBox( + width: iconSize, + height: iconSize, + child: rive.RiveAnimation.asset( + 'assets/new_file.riv', + controllers: [_controller], + onInit: (_) => setState(() { + _controller.isActive = true; + })))) + // Container( + // width: size, + // height: size, + // alignment: Alignment.center, + // child: SvgPicture.asset( + // "assets/ACMainButton.svg", + // width: iconSize, + // height: iconSize, + // ), + // ), + ), + ), + ), + )) + ], + ); + } +} diff --git a/lib/presentation/screens/hvac/widgets/semi_circle_painter.dart b/lib/presentation/screens/hvac/widgets/semi_circle_painter.dart new file mode 100644 index 0000000..e2003c5 --- /dev/null +++ b/lib/presentation/screens/hvac/widgets/semi_circle_painter.dart @@ -0,0 +1,109 @@ +import 'package:flutter_ics_homescreen/export.dart'; +import 'dart:math' as math; + +class AnimatedColorPainter extends CustomPainter { + final AnimationController animationController; + final double progress; + final Color progressColor; // New parameter for progress color + final Color backgroundColor; + final double strokeWidth; + + AnimatedColorPainter(this.animationController, this.progress, + this.progressColor, this.backgroundColor, this.strokeWidth); + + @override + void paint(Canvas canvas, Size size) { + // const strokeWidth = 25.0; + const borderWidth = 2.0; + + // Divide the arc into equal parts based on the number of colors + const arcAngle = math.pi; + const arcPart = arcAngle / 3; + const gapAngle = arcAngle / 150; + + // Calculate the current color index based on animation progress and progress value + final double normalizedProgress = progress * 3; + int currentColorIndex = + (animationController.value * normalizedProgress).floor(); + if (progress == 0.0) { + currentColorIndex = -1; // Force background color when progress is 0 + } + // Draw each part with a border and inner color + double startAngle = -math.pi; // Start from left + for (int i = 0; i < 3; i++) { + Color? currentColor = backgroundColor; + if (i <= currentColorIndex) { + // Use progress color if within progress range + currentColor = progressColor; + } else { + // Use background color if outside progress range + currentColor = backgroundColor; + } + + // Draw border + final borderPaint = Paint() + ..strokeWidth = strokeWidth + borderWidth + ..style = PaintingStyle.stroke + ..color = Colors.white12; + canvas.drawArc( + Rect.fromCircle( + center: Offset(size.width / 2, size.height / 2), + radius: size.width / 2, + ), + startAngle, + arcPart - 2 * gapAngle, + false, // Draw clockwise + borderPaint, + ); + + // Draw inner color + final colorPaint = Paint() + ..strokeWidth = strokeWidth + ..style = PaintingStyle.stroke + ..shader = _createColorShader(currentColor, size); + canvas.drawArc( + Rect.fromCircle( + center: Offset(size.width / 2, size.height / 2), + radius: size.width / 2, + ), + startAngle, + arcPart - 2 * gapAngle, + false, // Draw clockwise + colorPaint, + ); + + startAngle += arcPart + gapAngle; + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => true; + + Shader _createColorShader(Color color, Size size) { + if (color == progressColor) { + return const RadialGradient( + center: Alignment.center, + radius: 2, + tileMode: TileMode.repeated, + focal: Alignment.center, + focalRadius: 8, + colors: [ + AGLDemoColors.blueGlowFillColor, + AGLDemoColors.jordyBlueColor, + AGLDemoColors.neonBlueColor + ], + ).createShader( + Rect.fromCircle( + center: Offset(size.width / 2, size.height / 2), + radius: size.width / 2, + ), + ); + } + return LinearGradient(colors: [color, color]).createShader( + Rect.fromCircle( + center: Offset(size.width / 2, size.height / 2), + radius: size.width / 2, + ), + ); + } +} diff --git a/lib/presentation/screens/hvac/widgets/temperature_control.dart b/lib/presentation/screens/hvac/widgets/temperature_control.dart new file mode 100644 index 0000000..df83840 --- /dev/null +++ b/lib/presentation/screens/hvac/widgets/temperature_control.dart @@ -0,0 +1,255 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class TemperatureControl extends StatefulWidget { + const TemperatureControl({super.key, required this.temperature}); + final int temperature; + + @override + State<TemperatureControl> createState() => _TemperatureControlState(); +} + +class _TemperatureControlState extends State<TemperatureControl> { + int temperature = 0; + bool isUpButtonHighlighted = false; + bool isDownButtonHighlighted = false; + + @override + void initState() { + super.initState(); + setState(() { + temperature = widget.temperature; + }); + } + + onPressed({required String type}) { + setState(() { + if (type == "add") { + temperature = temperature + 1; + } else if (type == "subtract") { + temperature = temperature - 1; + } + }); + } + + @override + Widget build(BuildContext context) { + double iconSize = 32; + double height = MediaQuery.sizeOf(context).height * 0.0417; + double width = MediaQuery.sizeOf(context).width * 0.2112; + + return Column( + children: [ + Material( + color: Colors.transparent, + child: InkWell( + onHighlightChanged: (value) { + setState(() { + isUpButtonHighlighted = value; + }); + }, + onTap: () { + onPressed(type: "add"); + }, + child: SizedBox( + height: height, + width: width, + child: Image.asset( + "assets/${isUpButtonHighlighted ? 'UpPressed' : 'Up'}.png")), + ), + ), + // ClipRect( + // clipper: MyCustomClipper(type: "top"), + // child: ClipRRect( + // borderRadius: const BorderRadius.only( + // bottomLeft: Radius.circular(22), + // bottomRight: Radius.circular(22)), + // child: Container( + // height: height, + // width: width, + // decoration: BoxDecoration( + // boxShadow: [ + // BoxShadow( + // offset: const Offset(1, 2), + // blurRadius: 3, + // color: Colors.black.withOpacity(0.7)), + // ], + // gradient: LinearGradient(colors: [ + // AGLDemoColors.neonBlueColor, + // AGLDemoColors.neonBlueColor.withOpacity(0.20) + // ]), + // borderRadius: const BorderRadius.only( + // topLeft: Radius.circular(100), + // topRight: Radius.circular(100), + // bottomLeft: Radius.circular(10), + // bottomRight: Radius.circular(10))), + // child: Container( + // margin: const EdgeInsets.all(1), + // decoration: const BoxDecoration( + // color: AGLDemoColors.buttonFillEnabledColor, + // borderRadius: BorderRadius.only( + // topLeft: Radius.circular(100), + // topRight: Radius.circular(100), + // bottomLeft: Radius.circular(10), + // bottomRight: Radius.circular(10))), + // child: Material( + // color: Colors.transparent, + // child: InkWell( + // onTap: () { + // onPressed(type: "add"); + // }, + // child: Padding( + // padding: const EdgeInsets.only(bottom: 10), + // child: Icon( + // Icons.arrow_upward, + // color: Colors.white, + // size: iconSize, + // shadows: [ + // BoxShadow( + // offset: const Offset(1, 2), + // blurRadius: 3, + // color: Colors.black.withOpacity(0.7)), + // ], + // ), + // ), + // ), + // ), + // ), + // ), + // ), + // ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Text( + "$temperature°C", + style: GoogleFonts.brunoAce(fontSize: 44, height: 1.25), + ), + ), + Material( + color: Colors.transparent, + child: InkWell( + onHighlightChanged: (value) { + setState(() { + isDownButtonHighlighted = value; + }); + }, + onTap: () { + onPressed(type: "subtract"); + }, + child: SizedBox( + height: height, + width: width, + child: Image.asset( + "assets/${isDownButtonHighlighted ? 'DownPressed' : 'Down'}.png")), + ), + ), + // ClipRect( + // clipper: MyCustomClipper(type: "bottom"), + // child: ClipRRect( + // borderRadius: const BorderRadius.only( + // topLeft: Radius.circular(20), topRight: Radius.circular(20)), + // child: Container( + // height: height, + // width: width, + // decoration: BoxDecoration( + // boxShadow: [ + // BoxShadow( + // offset: const Offset(1, 2), + // blurRadius: 3, + // color: Colors.black.withOpacity(0.7)), + // ], + // gradient: LinearGradient(colors: [ + // AGLDemoColors.neonBlueColor, + // AGLDemoColors.neonBlueColor.withOpacity(0.20) + // ]), + // border: Border.all(color: Colors.white12), + // borderRadius: const BorderRadius.only( + // bottomLeft: Radius.circular(100), + // bottomRight: Radius.circular(100), + // topLeft: Radius.circular(10), + // topRight: Radius.circular(10))), + // child: Container( + // margin: const EdgeInsets.all(1), + // decoration: const BoxDecoration( + // color: AGLDemoColors.buttonFillEnabledColor, + // borderRadius: BorderRadius.only( + // bottomLeft: Radius.circular(100), + // bottomRight: Radius.circular(100), + // topLeft: Radius.circular(10), + // topRight: Radius.circular(10))), + // child: Material( + // color: Colors.transparent, + // child: InkWell( + // onTap: () { + // onPressed(type: "subtract"); + // }, + // child: Padding( + // padding: const EdgeInsets.only(top: 10), + // child: Icon( + // Icons.arrow_downward, + // color: Colors.white, + // size: iconSize, + // shadows: [ + // BoxShadow( + // offset: const Offset(1, 2), + // blurRadius: 3, + // color: Colors.black.withOpacity(0.7)), + // ], + // ), + // ), + // ), + // ), + // )), + // )), + ], + ); + } +} + +class MyCustomClipper extends CustomClipper<Rect> { + final String type; + + MyCustomClipper({super.reclip, required this.type}); + @override + Rect getClip(Size size) { + // Clip 10 pixels from the top of the container + return Rect.fromPoints( + Offset(0, type == "top" ? 0 : 10), + Offset(size.width, type == "top" ? size.height - 10 : size.height), + ); + } + + @override + bool shouldReclip(CustomClipper<Rect> oldClipper) { + return false; + } +} + +class CustomShapePainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = Colors.blue + ..strokeWidth = 5.0; + + final path = Path(); + + // Draw the top part of the oval + path.moveTo(0.0, size.height / 2.0); + path.quadraticBezierTo( + size.width / 3.0, size.height / 2.0, size.width / 2.0, size.height); + + // Draw the straight line for the bottom part + path.lineTo(size.width / 2.0, size.height); + + // Draw the left part of the oval + path.quadraticBezierTo(size.width / 3.0, 0.0, 0.0, 0.0); + + // Close the path + path.close(); + + canvas.drawPath(path, paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} diff --git a/lib/presentation/screens/media_player/fm_player.dart b/lib/presentation/screens/media_player/fm_player.dart new file mode 100644 index 0000000..31a22ae --- /dev/null +++ b/lib/presentation/screens/media_player/fm_player.dart @@ -0,0 +1,76 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class FMPlayer extends StatefulWidget { + const FMPlayer({super.key}); + + @override + State<FMPlayer> createState() => _FMPlayerState(); +} + +class _FMPlayerState extends State<FMPlayer> { + String selectedNav = "Standard"; + List<String> navItems = [ + "Standard", + "HD", + ]; + String tableName = "Presets"; + List<PlayListModel> playList = [ + PlayListModel(songName: "93.1 The Mountain", albumName: "93.1"), + PlayListModel(songName: "Mix 94.1", albumName: "94.1 MHz"), + PlayListModel(songName: "96.3 KKLZ", albumName: "96.3 MHz"), + ]; + String selectedPlayListSongName = "93.1 The Mountain"; + @override + Widget build(BuildContext context) { + double fmSignalHeight = 460; + double fmSignalWidth = 460; + + return Container( + padding: const EdgeInsets.only(left: 7, right: 7), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SegmentedButtons( + navItems: navItems, + selectedNav: selectedNav, + ), + const SizedBox( + height: 32, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + "assets/AlbumArtFM.png", + width: fmSignalWidth, + height: fmSignalHeight, + ) + ], + ), + const SizedBox( + height: 40, + ), + Column( + children: [ + const MediaControls( + songName: "87.9", + songLengthStart: "87.9 MHz", + songLengthStop: "87.9 MHz", + type: "fm", + ), + const SizedBox( + height: 70, + ), + PlayListTable( + playList: playList, + selectedPlayListSongName: selectedPlayListSongName, + tableName: tableName, + type: "fm", + ), + ], + ) + ], + ), + ); + } +} diff --git a/lib/presentation/screens/media_player/media_content.dart b/lib/presentation/screens/media_player/media_content.dart new file mode 100644 index 0000000..9a0ce19 --- /dev/null +++ b/lib/presentation/screens/media_player/media_content.dart @@ -0,0 +1,82 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class MediaPlayer extends StatefulWidget { + const MediaPlayer({super.key}); + + @override + State<MediaPlayer> createState() => _MediaPlayerState(); +} + +class _MediaPlayerState extends State<MediaPlayer> { + String selectedNav = "Bluetooth"; + List<String> navItems = ["Bluetooth", "SD", "USB"]; + + late String songName = "Feel Good Inc."; + + String tableName = "2000’s Dance Hits"; + List<PlayListModel> playList = [ + PlayListModel(songName: "Feel Good Inc.", albumName: "Gorillaz"), + PlayListModel( + songName: "Hips Don’t Lie", albumName: "Shakira, Wyclef Jean"), + PlayListModel(songName: "AG1", albumName: "Paid Advertisement"), + PlayListModel(songName: "Hey Ya!", albumName: "Outkast"), + PlayListModel(songName: "One, Two, Step", albumName: "Ciara, Missy Elliot"), + PlayListModel(songName: "Don’t Trust Me", albumName: "3OH!3"), + PlayListModel(songName: "Feel Good Inc.", albumName: "Gorillaz"), + PlayListModel(songName: "Feel Good Inc.", albumName: "Gorillaz"), + PlayListModel(songName: "Feel Good Inc.", albumName: "Gorillaz"), + PlayListModel(songName: "Feel Good Inc.", albumName: "Gorillaz"), + ]; + String selectedPlayListSongName = "Feel Good Inc."; + + @override + Widget build(BuildContext context) { + double albumArtSize = 460; + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // const PlayerNavigation(), + SegmentedButtons( + navItems: navItems, + selectedNav: selectedNav, + ), + const SizedBox( + height: 32, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + "assets/AlbumArtMedia.png", + width: albumArtSize, + height: albumArtSize, + ) + ], + ), + const SizedBox( + height: 40, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + MediaControls( + songName: songName, + songLengthStart: "-1:23", + songLengthStop: "5:03", + type: "media", + ), + const SizedBox( + height: 72, + ), + PlayListTable( + playList: playList, + selectedPlayListSongName: selectedPlayListSongName, + tableName: tableName, + type: "media", + ), + ], + ) + ], + ); + } +} diff --git a/lib/presentation/screens/media_player/media_controls.dart b/lib/presentation/screens/media_player/media_controls.dart new file mode 100644 index 0000000..0686187 --- /dev/null +++ b/lib/presentation/screens/media_player/media_controls.dart @@ -0,0 +1,413 @@ +import 'package:flutter_ics_homescreen/core/utils/helpers.dart'; +import 'package:flutter_ics_homescreen/export.dart'; +import 'package:flutter_ics_homescreen/presentation/screens/media_player/widgets/gradient_progress_indicator.dart'; + +class MediaControls extends StatefulWidget { + const MediaControls( + {super.key, + required this.type, + required this.songName, + required this.songLengthStart, + required this.songLengthStop}); + + final String type; + final String songName; + final String songLengthStart; + final String songLengthStop; + + @override + State<MediaControls> createState() => _MediaControlsState(); +} + +class _MediaControlsState extends State<MediaControls> { + late String songName; + late String songLengthStart; + late String songLengthStop; + final String albumName = "Gorillaz"; + + int songProgress = 20; + + @override + void initState() { + songName = widget.songName; + songLengthStart = widget.songLengthStart; + songLengthStop = widget.songLengthStop; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Material( + color: Colors.transparent, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + songName, + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.w400, + shadows: [Helpers.dropShadowRegular], + fontSize: 44), + ), + if (widget.type == "media") + MediaControlSubDetails( + albumName: albumName, + ) + else if (widget.type == "fm") + const FMPlayerSubDetails(), + if (widget.type == "media") + Column(children: [ + GradientProgressIndicator( + percent: songProgress, + type: "media", + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + AGLDemoColors.jordyBlueColor, + AGLDemoColors.jordyBlueColor.withOpacity(0.8), + ]), + backgroundColor: AGLDemoColors.gradientBackgroundDarkColor, + ), + // const LinearProgressIndicator( + // backgroundColor: AGLDemoColors.gradientBackgroundDarkColor, + // color: Colors.white70, + // minHeight: 8, + // value: 0.7, + // ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 5), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + songLengthStart, + style: TextStyle( + color: Colors.white, + fontSize: 26, + shadows: [Helpers.dropShadowRegular]), + ), + Text( + songLengthStop, + style: TextStyle( + color: Colors.white, + fontSize: 26, + shadows: [Helpers.dropShadowRegular]), + ) + ], + ), + ), + ]) + else if (widget.type == "fm") + FMPlayerSlider( + minHertz: songLengthStart, + maxHertz: songLengthStop, + songProgress: songProgress, + ), + if (widget.type == "media") const MediaPlayerActions() + ], + ), + ); + } +} + +class MediaControlSubDetails extends StatefulWidget { + const MediaControlSubDetails({super.key, required this.albumName}); + final String albumName; + + @override + State<MediaControlSubDetails> createState() => _MediaControlSubDetailsState(); +} + +class _MediaControlSubDetailsState extends State<MediaControlSubDetails> { + bool isShuffleEnabled = false; + bool isRepeatEnabled = false; + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + widget.albumName, + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.w400, + fontSize: 40, + shadows: [Helpers.dropShadowRegular]), + ), + Row( + children: [ + InkWell( + customBorder: const CircleBorder(), + onTap: () { + setState(() { + isShuffleEnabled = !isShuffleEnabled; + }); + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + "assets/${isShuffleEnabled ? "ShufflePressed.svg" : "Shuffle.svg"}", + width: 48, + ))), + InkWell( + customBorder: const CircleBorder(), + onTap: () { + setState(() { + isRepeatEnabled = !isRepeatEnabled; + }); + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + "assets/${isRepeatEnabled ? "RepeatPressed.svg" : "Repeat.svg"}", + width: 48, + ))), + ], + ) + ], + ); + } +} + +class FMPlayerSubDetails extends StatefulWidget { + const FMPlayerSubDetails({ + super.key, + }); + + @override + State<FMPlayerSubDetails> createState() => _FMPlayerSubDetailsState(); +} + +class _FMPlayerSubDetailsState extends State<FMPlayerSubDetails> { + onPressed({required String type}) {} + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(bottom: 5), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Text( + "Tune", + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.w400, + fontSize: 40, + shadows: [Helpers.dropShadowRegular]), + ), + const SizedBox( + width: 25, + ), + InkWell( + customBorder: const CircleBorder(), + onTap: () { + onPressed(type: "scanLeft"); + }, + child: const Padding( + padding: EdgeInsets.all(8.0), + child: Icon( + Icons.arrow_back, + size: 48, + color: AGLDemoColors.periwinkleColor, + ))), + const SizedBox( + width: 25, + ), + InkWell( + customBorder: const CircleBorder(), + onTap: () { + onPressed(type: "scanRight"); + }, + child: const Padding( + padding: EdgeInsets.all(8.0), + child: Icon( + Icons.arrow_forward, + color: AGLDemoColors.periwinkleColor, + size: 48, + ))), + ], + ), + Row( + children: [ + Text( + "Scan", + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.w400, + fontSize: 40, + shadows: [Helpers.dropShadowRegular]), + ), + const SizedBox( + width: 25, + ), + InkWell( + customBorder: const CircleBorder(), + onTap: () { + onPressed(type: "scanLeft"); + }, + child: const Padding( + padding: EdgeInsets.all(8.0), + child: Icon( + Icons.arrow_back, + color: AGLDemoColors.periwinkleColor, + size: 48, + ))), + const SizedBox( + width: 25, + ), + InkWell( + customBorder: const CircleBorder(), + onTap: () { + onPressed(type: "scanRight"); + }, + child: const Padding( + padding: EdgeInsets.all(8.0), + child: Icon( + Icons.arrow_forward, + color: AGLDemoColors.periwinkleColor, + size: 48, + ))), + ], + ) + ], + ), + ); + } +} + +class MediaPlayerActions extends StatefulWidget { + const MediaPlayerActions({super.key}); + + @override + State<MediaPlayerActions> createState() => _MediaPlayerActionsState(); +} + +class _MediaPlayerActionsState extends State<MediaPlayerActions> { + bool isPressed = false; + bool isPlaying = true; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + InkWell( + customBorder: const CircleBorder(), + onTap: () {}, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + "assets/SkipPrevious.svg", + width: 48, + ), + )), + const SizedBox( + width: 120, + ), + InkWell( + customBorder: const CircleBorder(), + onTap: () { + setState(() { + isPlaying = !isPlaying; + }); + }, + onTapDown: (details) { + setState(() { + isPressed = true; + }); + }, + onTapUp: (details) { + isPressed = false; + + }, + child: Container( + width: 64, + height: 64, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: + isPressed ? Colors.white : AGLDemoColors.periwinkleColor, + boxShadow: [Helpers.boxDropShadowRegular]), + child: Icon( + isPlaying ? Icons.pause : Icons.play_arrow, + color: AGLDemoColors.resolutionBlueColor, + size: 60, + ), + )), + const SizedBox( + width: 120, + ), + InkWell( + customBorder: const CircleBorder(), + onTap: () {}, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + "assets/SkipNext.svg", + width: 48, + ), + )), + ], + ); + } +} + +class FMPlayerSlider extends StatefulWidget { + const FMPlayerSlider( + {super.key, + required this.minHertz, + required this.maxHertz, + required this.songProgress}); + final String minHertz; + final String maxHertz; + final int songProgress; + + @override + State<FMPlayerSlider> createState() => _FMPlayerSliderState(); +} + +class _FMPlayerSliderState extends State<FMPlayerSlider> { + @override + Widget build(BuildContext context) { + return Row( + children: [ + Text( + widget.minHertz, + style: TextStyle( + color: Colors.white, + fontSize: 26, + shadows: [Helpers.dropShadowRegular]), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: GradientProgressIndicator( + percent: widget.songProgress, + height: 10, + type: "fm", + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + AGLDemoColors.jordyBlueColor, + AGLDemoColors.jordyBlueColor.withOpacity(0.8), + ]), + backgroundColor: AGLDemoColors.gradientBackgroundDarkColor, + ), + ), + ), + Text( + widget.maxHertz, + style: TextStyle( + color: Colors.white, + fontSize: 26, + shadows: [Helpers.dropShadowRegular]), + ) + ], + ); + } +} diff --git a/lib/presentation/screens/media_player/media_player.dart b/lib/presentation/screens/media_player/media_player.dart new file mode 100644 index 0000000..9ec31e2 --- /dev/null +++ b/lib/presentation/screens/media_player/media_player.dart @@ -0,0 +1,99 @@ +import 'package:flutter_ics_homescreen/presentation/screens/media_player/fm_player.dart'; + +import '/export.dart'; +import 'widgets/media_volume_bar.dart'; + +class MediaPlayerPage extends StatelessWidget { + const MediaPlayerPage({super.key}); + + static Page<void> page() => + const MaterialPage<void>(child: MediaPlayerPage()); + @override + Widget build(BuildContext context) { + Size size = MediaQuery.sizeOf(context); + + return Stack( + children: [ + // SizedBox( + // width: size.width, + // height: size.height, + // //color: Colors.black, + // // decoration: + // // BoxDecoration(gradient: AGLDemoColors.gradientBackgroundColor), + // child: SvgPicture.asset( + // 'assets/MediaPlayerBackground.svg', + // alignment: Alignment.center, + // fit: BoxFit.cover, + // //width: 200, + // //height: 200, + // ), + // ), + SizedBox( + width: size.width, + height: size.height, + // color: Colors.black, + child: SvgPicture.asset( + 'assets/MediaPlayerBackgroundTextures.svg', + // alignment: Alignment.center, + fit: BoxFit.cover, + //width: 200, + //height: 200, + ), + ), + const Padding( + padding: EdgeInsets.symmetric(vertical: 50, horizontal: 50), + child: MediaPlayerBackground(), + ) + //const MediaPlayer(), + ], + ); + } +} + +class MediaPlayerBackground extends StatefulWidget { + const MediaPlayerBackground({super.key}); + + @override + State<MediaPlayerBackground> createState() => _MediaPlayerBackgroundState(); +} + +class _MediaPlayerBackgroundState extends State<MediaPlayerBackground> { + String selectedNav = "My Media"; + onPressed(type) { + setState(() { + selectedNav = type; + }); + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( + children: [ + const SizedBox( + height: 55, + ), + PlayerNavigation( + onPressed: (val) { + onPressed(val); + }, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 144), + child: SingleChildScrollView( + child: selectedNav == "My Media" + ? const MediaPlayer() + : selectedNav == "FM" + ? const FMPlayer() + : Container(), + ), + ), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 144, vertical: 23.5), + child: CustomVolumeSlider(), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/screens/media_player/my_media.dart b/lib/presentation/screens/media_player/my_media.dart new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/presentation/screens/media_player/my_media.dart diff --git a/lib/presentation/screens/media_player/play_list_table.dart b/lib/presentation/screens/media_player/play_list_table.dart new file mode 100644 index 0000000..b17cfca --- /dev/null +++ b/lib/presentation/screens/media_player/play_list_table.dart @@ -0,0 +1,154 @@ +import 'package:flutter_ics_homescreen/core/utils/helpers.dart'; +import 'package:flutter_ics_homescreen/export.dart'; + +class PlayListTable extends StatefulWidget { + const PlayListTable( + {super.key, + required this.type, + required this.tableName, + required this.playList, + required this.selectedPlayListSongName}); + final String type; + final String tableName; + final List<PlayListModel> playList; + final String selectedPlayListSongName; + + @override + State<PlayListTable> createState() => _PlayListTableState(); +} + +class _PlayListTableState extends State<PlayListTable> { + bool isAudioSettingsEnabled = false; + late String tableName; + late List<PlayListModel> playList; + late String selectedPlayListSongName; + @override + void initState() { + tableName = widget.tableName; + playList = widget.playList; + selectedPlayListSongName = widget.selectedPlayListSongName; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Material( + color: Colors.transparent, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Text( + tableName, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w400, + fontSize: 40), + ), + if (widget.type == "media") + InkWell( + customBorder: const CircleBorder(), + onTap: () {}, + child: Opacity( + opacity: 0.5, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + "assets/AppleMusic.svg", + width: 32, + )), + )), + ], + ), + InkWell( + customBorder: const CircleBorder(), + onTap: () { + setState(() { + isAudioSettingsEnabled = !isAudioSettingsEnabled; + }); + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + "assets/${isAudioSettingsEnabled ? "AudioSettingsPressed.svg" : "AudioSettings.svg"}", + width: 48, + ))) + ], + ), + SizedBox( + height: 325, + child: SingleChildScrollView( + child: Column( + children: playList.map((index) { + return Container( + height: 100, + margin: const EdgeInsets.symmetric(vertical: 4), + decoration: BoxDecoration( + border: Border( + left: selectedPlayListSongName == index.songName + ? const BorderSide( + color: Colors.white, width: 4) + : BorderSide.none), + gradient: LinearGradient( + colors: selectedPlayListSongName == index.songName + ? [ + AGLDemoColors.neonBlueColor, + AGLDemoColors.neonBlueColor + .withOpacity(0.15) + ] + : [ + Colors.black, + Colors.black.withOpacity(0.20) + ])), + child: InkWell( + onTap: () { + setState(() { + selectedPlayListSongName = index.songName; + }); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 17, horizontal: 24), + child: Row( + children: [ + Expanded( + flex: 6, + child: Text( + index.songName, + style: TextStyle( + color: Colors.white, + fontSize: 40, + shadows: [Helpers.dropShadowRegular]), + )), + Expanded( + flex: 4, + child: Text( + index.albumName, + style: TextStyle( + color: Colors.white, + fontSize: 26, + shadows: [Helpers.dropShadowRegular]), + )) + ], + ), + ), + ), + ); + }).toList()), + ), + ), + ], + )); + } +} + +class PlayListModel { + final String songName; + final String albumName; + + PlayListModel({required this.songName, required this.albumName}); +} diff --git a/lib/presentation/screens/media_player/player_navigation.dart b/lib/presentation/screens/media_player/player_navigation.dart new file mode 100644 index 0000000..8e09e53 --- /dev/null +++ b/lib/presentation/screens/media_player/player_navigation.dart @@ -0,0 +1,80 @@ +import 'package:flutter_ics_homescreen/core/utils/helpers.dart'; +import 'package:flutter_ics_homescreen/export.dart'; + +class PlayerNavigation extends StatefulWidget { + const PlayerNavigation({super.key, required this.onPressed}); + final Function onPressed; + + @override + State<PlayerNavigation> createState() => _PlayerNavigationState(); +} + +class _PlayerNavigationState extends State<PlayerNavigation> { + List<String> navItems = ["My Media", "FM", "AM", "XM"]; + String selectedNav = "My Media"; + @override + Widget build(BuildContext context) { + return Row( + children: navItems + .map((e) => Expanded( + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 2), + decoration: BoxDecoration( + gradient: LinearGradient(colors: [ + selectedNav == e + ? AGLDemoColors.neonBlueColor + : AGLDemoColors.buttonFillEnabledColor, + AGLDemoColors.gradientBackgroundDarkColor + ], begin: Alignment.topCenter, end: Alignment.bottomCenter), + // color: selectedNav == e + // ? AGLDemoColors.neonBlueColor + // : AGLDemoColors.buttonFillEnabledColor, + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + setState(() { + selectedNav = e; + }); + widget.onPressed(selectedNav); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 7), + decoration: BoxDecoration( + border: Border( + left: selectedNav == e + ? const BorderSide(color: Colors.white12) + : BorderSide.none, + right: selectedNav == e + ? const BorderSide(color: Colors.white12) + : BorderSide.none, + top: BorderSide( + color: selectedNav == e + ? Colors.white + : Colors.white24, + width: selectedNav == e ? 2 : 1))), + child: Text( + e, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 26, + shadows: [ + selectedNav == e + ? Helpers.dropShadowRegular + : Helpers.dropShadowBig + ], + color: selectedNav == e + ? Colors.white + : AGLDemoColors.periwinkleColor, + fontWeight: selectedNav == e + ? FontWeight.w700 + : FontWeight.w500), + ), + ), + ), + ), + ))) + .toList()); + } +} diff --git a/lib/presentation/screens/media_player/segmented_buttons.dart b/lib/presentation/screens/media_player/segmented_buttons.dart new file mode 100644 index 0000000..e649be3 --- /dev/null +++ b/lib/presentation/screens/media_player/segmented_buttons.dart @@ -0,0 +1,82 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class SegmentedButtons extends StatefulWidget { + const SegmentedButtons( + {super.key, required this.navItems, required this.selectedNav}); + + final List<String> navItems; + final String selectedNav; + @override + State<SegmentedButtons> createState() => _SegmentedButtonsState(); +} + +class _SegmentedButtonsState extends State<SegmentedButtons> { + late List<String> navItems; + late String selectedNav; + + @override + void initState() { + navItems = widget.navItems; + selectedNav = widget.selectedNav; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + margin: const EdgeInsets.only(top: 40), + padding: const EdgeInsets.all(3), + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + offset: const Offset(0, 4), + blurRadius: 4, + color: Colors.black.withOpacity(0.25)) + ], + borderRadius: BorderRadius.circular(40), + color: AGLDemoColors.buttonFillEnabledColor, + border: Border.all(color: Colors.white12), + ), + child: Row( + children: navItems + .map((e) => Container( + padding: const EdgeInsets.symmetric( + vertical: 24, horizontal: 32), + decoration: BoxDecoration( + borderRadius: selectedNav == e + ? BorderRadius.circular(40) + : BorderRadius.zero, + color: selectedNav == e + ? AGLDemoColors.backgroundInsetColor + : null, + ), + child: InkWell( + borderRadius: BorderRadius.circular(40), + onTap: () { + setState(() { + selectedNav = e; + }); + }, + child: Text( + e, + style: TextStyle( + color: selectedNav == e + ? Colors.white + : AGLDemoColors.periwinkleColor, + fontSize: 26, + fontWeight: selectedNav == e + ? FontWeight.w700 + : FontWeight.w500), + ), + ), + )) + .toList(), + ), + ), + ], + ); + } +} diff --git a/lib/presentation/screens/media_player/widgets/gradient_progress_indicator.dart b/lib/presentation/screens/media_player/widgets/gradient_progress_indicator.dart new file mode 100644 index 0000000..24aa244 --- /dev/null +++ b/lib/presentation/screens/media_player/widgets/gradient_progress_indicator.dart @@ -0,0 +1,88 @@ +import 'package:flutter_ics_homescreen/core/utils/helpers.dart'; +import 'package:flutter_ics_homescreen/export.dart'; + +class GradientProgressIndicator extends StatelessWidget { + ///it can be anything between 0 to 100 + final int percent; + final Gradient gradient; + final Color backgroundColor; + final double height; + final String type; + + const GradientProgressIndicator( + {required this.percent, + required this.gradient, + required this.backgroundColor, + Key? key, + this.height = 16, + required this.type}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Flexible( + flex: percent, + fit: FlexFit.tight, + child: Container( + height: height, + margin: const EdgeInsets.all(1), + decoration: BoxDecoration( + border: Border.all( + color: AGLDemoColors.neonBlueColor.withOpacity(0.5), + width: 1), + gradient: gradient, + borderRadius: + BorderRadius.all(Radius.circular(type == "fm" ? 16 : 2)), + ), + alignment: Alignment.centerRight, + ), + ), + type == "media" + ? Container( + height: height, + width: 2, + color: Colors.white, + ) + : Container( + height: 64, + width: 64, + alignment: Alignment.center, + decoration: BoxDecoration( + shape: BoxShape.circle, + boxShadow: [ + Helpers.boxDropShadowRegular, + ], + color: AGLDemoColors.periwinkleColor), + child: Container( + height: 32, + width: 32, + decoration: BoxDecoration( + shape: BoxShape.circle, + boxShadow: [ + Helpers.boxDropShadowRegular, + ], + border: Border.all( + color: AGLDemoColors.neonBlueColor, width: 2), + color: AGLDemoColors.periwinkleColor), + ), + ), + Flexible( + fit: FlexFit.tight, + flex: 100 - percent, + child: Container( + decoration: BoxDecoration( + color: backgroundColor, + border: Border.all( + color: AGLDemoColors.neonBlueColor.withOpacity(0.5), + width: 1), + borderRadius: const BorderRadius.all(Radius.circular(2)), + ), + child: SizedBox(height: height), + ), + ), + ], + ); + } +} diff --git a/lib/presentation/screens/media_player/widgets/media_volume_bar.dart b/lib/presentation/screens/media_player/widgets/media_volume_bar.dart new file mode 100644 index 0000000..f8d58e6 --- /dev/null +++ b/lib/presentation/screens/media_player/widgets/media_volume_bar.dart @@ -0,0 +1,117 @@ +import 'package:flutter_ics_homescreen/presentation/custom_icons/custom_icons.dart'; + +import '../../../../export.dart'; +import '../../settings/settings_screens/audio_settings/widget/slider_widgets.dart'; + +class CustomVolumeSlider extends ConsumerStatefulWidget { + const CustomVolumeSlider({ + super.key, + }); + + @override + CustomVolumeSliderState createState() => CustomVolumeSliderState(); +} + +class CustomVolumeSliderState extends ConsumerState<CustomVolumeSlider> { + void _increase() { + setState(() { + if (_currentVal < 20) { + _currentVal++; + ref.read(audioStateProvider.notifier).setVolume(_currentVal); + } + }); + } + + void _dercrease() { + setState(() { + if (_currentVal > 0) { + _currentVal--; + ref.read(audioStateProvider.notifier).setVolume(_currentVal); + } + }); + } + + double _currentVal = 5; + @override + Widget build(BuildContext context) { + final volumeValue = + ref.watch(audioStateProvider.select((audio) => audio.volume)); + + return Column( + //crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + decoration: const ShapeDecoration( + color: AGLDemoColors.buttonFillEnabledColor, + shape: StadiumBorder( + side: BorderSide( + color: Color(0xFF5477D4), + width: 0.5, + )), + ), + height: 160, + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only(left: 10.0), + child: SizedBox( + width: 50, + child: IconButton( + padding: EdgeInsets.zero, + onPressed: () { + _dercrease(); + }, + icon: const Icon( + CustomIcons.vol_min, + color: AGLDemoColors.periwinkleColor, + size: 48, + )), + ), + ), + Expanded( + child: SliderTheme( + data: SliderThemeData( + overlayShape: SliderComponentShape.noOverlay, + valueIndicatorShape: SliderComponentShape.noOverlay, + activeTickMarkColor: Colors.transparent, + inactiveTickMarkColor: Colors.transparent, + inactiveTrackColor: AGLDemoColors.backgroundInsetColor, + thumbShape: const PolygonSliderThumb( + sliderValue: 3, thumbRadius: 23), + //trackHeight: 5, + ), + child: Slider( + divisions: 20, + min: 0, + max: 20, + value: volumeValue, + onChanged: (newValue) { + ref.read(audioStateProvider.notifier).setVolume(newValue); + _currentVal = newValue; + }, + ), + ), + ), + Padding( + padding: const EdgeInsets.only(right: 10.0), + child: SizedBox( + width: 60, + child: IconButton( + padding: EdgeInsets.zero, + onPressed: () { + _increase(); + }, + icon: const Icon( + CustomIcons.vol_max, + color: AGLDemoColors.periwinkleColor, + size: 48, + )), + ), + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/presentation/screens/settings/settings.dart b/lib/presentation/screens/settings/settings.dart new file mode 100644 index 0000000..aa0c150 --- /dev/null +++ b/lib/presentation/screens/settings/settings.dart @@ -0,0 +1,26 @@ +import '/export.dart'; +import 'widgets/settings_content.dart'; + +class SettingsPage extends StatelessWidget { + const SettingsPage({super.key}); + + static Page<void> page() => const MaterialPage<void>(child: SettingsPage()); + @override + Widget build(BuildContext context) { + return const SettingsContent(); + } +} + +class SettingsContent extends StatelessWidget { + const SettingsContent({super.key}); + + @override + Widget build(BuildContext context) { + return const Stack( + children: [ + Settings(), + ], + ); + } +} + diff --git a/lib/presentation/screens/settings/settings_screens/audio_settings/audio_settings_screen.dart b/lib/presentation/screens/settings/settings_screens/audio_settings/audio_settings_screen.dart new file mode 100644 index 0000000..3c3508e --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/audio_settings/audio_settings_screen.dart @@ -0,0 +1,30 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +import 'widget/audio_content.dart'; + + +class AudioSettingsPage extends ConsumerWidget { + const AudioSettingsPage({super.key}); + + static Page<void> page() => + const MaterialPage<void>(child: AudioSettingsPage()); + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + body: Column( + children: [ + CommonTitle( + title: 'Audio Settings', + hasBackButton: true, + onPressed: () { + context.flow<AppState>().update((state) => AppState.settings); + }, + ), + const Expanded( + child: AudioContent()), + ], + ), + ); + } +} + diff --git a/lib/presentation/screens/settings/settings_screens/audio_settings/widget/audio_content.dart b/lib/presentation/screens/settings/settings_screens/audio_settings/widget/audio_content.dart new file mode 100644 index 0000000..8fb0437 --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/audio_settings/widget/audio_content.dart @@ -0,0 +1,41 @@ +import '../../../../../../export.dart'; +import 'slider_widgets.dart'; + +class AudioContent extends ConsumerWidget { + const AudioContent({ + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 144), + child: Column( + children: [ + const Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CustomTrebleSlider(), + CustomBassSlider(), + CustomRearFrontSlider(), + ], + ), + ), + Padding( + padding: const EdgeInsets.only(bottom: 150), + child: GenericButton( + heigth: 130, + width: 420, + text: 'Reset to Default', + onTap: () { + ref.read(audioStateProvider.notifier).resetToDefaults(); + }, + ), + + ), + ], + ), + ); + } +} diff --git a/lib/presentation/screens/settings/settings_screens/audio_settings/widget/slider_widgets.dart b/lib/presentation/screens/settings/settings_screens/audio_settings/widget/slider_widgets.dart new file mode 100644 index 0000000..973c9bf --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/audio_settings/widget/slider_widgets.dart @@ -0,0 +1,603 @@ +import 'package:flutter_ics_homescreen/export.dart'; +import 'package:flutter_ics_homescreen/presentation/custom_icons/custom_icons.dart'; + +class CustomTrebleSlider extends ConsumerStatefulWidget { + const CustomTrebleSlider({ + super.key, + }); + + @override + CustomTrebleSliderState createState() => CustomTrebleSliderState(); +} + +class CustomTrebleSliderState extends ConsumerState<CustomTrebleSlider> { + bool isPressed = false; + void _increase() { + setState(() { + if (_currentVal < 10) { + _currentVal++; + ref.read(audioStateProvider.notifier).setTreble(_currentVal); + } + }); + } + + void _dercrease() { + setState(() { + if (_currentVal > 0) { + _currentVal--; + ref.read(audioStateProvider.notifier).setTreble(_currentVal); + } + }); + } + + double _currentVal = 5; + @override + Widget build(BuildContext context) { + final trebleValue = + ref.watch(audioStateProvider.select((audio) => audio.treble)); + return Column( + //crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: Text( + 'Treble', + style: TextStyle(fontSize: 40), + ), + ), + Container( + width: 792, + height: 160, + decoration: const ShapeDecoration( + gradient: LinearGradient( + colors: <Color>[ + AGLDemoColors.neonBlueColor, + AGLDemoColors.resolutionBlueColor, + Color.fromARGB(127, 20, 31, 100), + Color(0xFF2962FF) + ], + stops: [0, 0, 1, 1], + ), + shape: StadiumBorder( + side: BorderSide( + color: Color(0xFF5477D4), + width: 1, + )), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 40), + child: InkWell( + onTap: () { + _dercrease(); + }, + child: const Icon( + Icons.remove, + color: AGLDemoColors.periwinkleColor, + size: 48, + ), + ), + ), + SizedBox( + width: 584, + child: SliderTheme( + data: SliderThemeData( + showValueIndicator: ShowValueIndicator.always, + trackShape: CustomRoundedRectSliderTrackShape( + silderVal: trebleValue), + activeTickMarkColor: Colors.transparent, + inactiveTickMarkColor: Colors.transparent, + inactiveTrackColor: AGLDemoColors.backgroundInsetColor, + thumbShape: PolygonSliderThumb( + sliderValue: 3, thumbRadius: 23, isPressed: isPressed), + trackHeight: 16, + ), + child: Slider( + divisions: 10, + min: 0, + max: 10, + value: trebleValue, + onChanged: (newValue) { + ref.read(audioStateProvider.notifier).setTreble(newValue); + _currentVal = newValue; + }, + onChangeEnd: (value) { + setState(() { + isPressed = false; + }); + }, + onChangeStart: (value) { + setState(() { + isPressed = true; + }); + }, + ), + ), + ), + + Padding( + padding: const EdgeInsets.only( + right: 40, + ), + child: InkWell( + onTap: () { + _increase(); + }, + child: const Icon( + Icons.add, + color: AGLDemoColors.periwinkleColor, + size: 48, + )), + ), + ], + ), + ), + ], + ); + } +} + +class CustomBassSlider extends ConsumerStatefulWidget { + const CustomBassSlider({ + super.key, + }); + + @override + CustomBassSliderState createState() => CustomBassSliderState(); +} + +class CustomBassSliderState extends ConsumerState<CustomBassSlider> { + bool isPressed = false; + + void _increase() { + setState(() { + if (_currentVal < 10) { + _currentVal++; + ref.read(audioStateProvider.notifier).setBass(_currentVal); + } + }); + } + + void _dercrease() { + setState(() { + if (_currentVal > 0) { + _currentVal--; + ref.read(audioStateProvider.notifier).setBass(_currentVal); + } + }); + } + + double _currentVal = 5; + @override + Widget build(BuildContext context) { + final bassValue = + ref.watch(audioStateProvider.select((audio) => audio.bass)); + + return Column( + //crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: Text( + 'Bass', + style: TextStyle(fontSize: 40), + ), + ), + Container( + width: 792, + height: 160, + decoration: const ShapeDecoration( + gradient: LinearGradient( + colors: <Color>[ + AGLDemoColors.neonBlueColor, + AGLDemoColors.resolutionBlueColor, + Color.fromARGB(127, 20, 31, 100), + Color(0xFF2962FF) + ], + stops: [0, 0, 1, 1], + ), + shape: StadiumBorder( + side: BorderSide( + color: Color(0xFF5477D4), + width: 1, + )), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 40), + child: InkWell( + onTap: () { + _dercrease(); + }, + child: const Icon( + Icons.remove, + color: AGLDemoColors.periwinkleColor, + size: 48, + )), + ), + SizedBox( + width: 584, + child: SliderTheme( + data: SliderThemeData( + showValueIndicator: ShowValueIndicator.always, + trackShape: + CustomRoundedRectSliderTrackShape(silderVal: bassValue), + activeTickMarkColor: Colors.transparent, + inactiveTickMarkColor: Colors.transparent, + inactiveTrackColor: AGLDemoColors.backgroundInsetColor, + thumbShape: PolygonSliderThumb( + sliderValue: 3, thumbRadius: 23, isPressed: isPressed), + trackHeight: 16, + ), + child: Slider( + divisions: 10, + min: 0, + max: 10, + value: bassValue, + onChanged: (newValue) { + ref.read(audioStateProvider.notifier).setBass(newValue); + _currentVal = newValue; + }, + onChangeEnd: (value) { + setState(() { + isPressed = false; + }); + }, + onChangeStart: (value) { + setState(() { + isPressed = true; + }); + }, + ), + ), + ), + Padding( + padding: const EdgeInsets.only(right: 40), + child: InkWell( + onTap: () { + _increase(); + }, + child: const Icon( + Icons.add, + color: AGLDemoColors.periwinkleColor, + size: 48, + )), + ), + ], + ), + ), + ], + ); + } +} + +class CustomRearFrontSlider extends ConsumerStatefulWidget { + const CustomRearFrontSlider({ + super.key, + }); + + @override + CustomRearFrontState createState() => CustomRearFrontState(); +} + +class CustomRearFrontState extends ConsumerState<CustomRearFrontSlider> { + bool isPressed = false; + + void _increase() { + setState(() { + if (_currentVal < 10) { + _currentVal++; + ref.read(audioStateProvider.notifier).setRearFront(_currentVal); + } + }); + } + + void _dercrease() { + setState(() { + if (_currentVal > 0) { + _currentVal--; + ref.read(audioStateProvider.notifier).setRearFront(_currentVal); + } + }); + } + + double _currentVal = 5; + @override + Widget build(BuildContext context) { + final rearFrontValue = + ref.watch(audioStateProvider.select((audio) => audio.rearFront)); + return Column( + //crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: Text( + 'Rear/Front', + style: TextStyle(fontSize: 40), + ), + ), + Container( + width: 792, + height: 160, + decoration: const ShapeDecoration( + gradient: LinearGradient( + colors: <Color>[ + AGLDemoColors.neonBlueColor, + AGLDemoColors.resolutionBlueColor, + Color.fromARGB(127, 20, 31, 100), + Color(0xFF2962FF) + ], + stops: [0, 0, 1, 1], + ), + shape: StadiumBorder( + side: BorderSide( + color: Color(0xFF5477D4), + width: 1, + )), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 40), + child: InkWell( + onTap: () { + _dercrease(); + }, + child: const Icon( + CustomIcons.slider_rear, + color: AGLDemoColors.periwinkleColor, + size: 48, + )), + ), + SizedBox( + width: 584, + child: SliderTheme( + data: SliderThemeData( + showValueIndicator: ShowValueIndicator.always, + trackShape: CustomRoundedRectSliderTrackShape( + silderVal: rearFrontValue, isFrontRear: true), + activeTickMarkColor: Colors.transparent, + inactiveTickMarkColor: Colors.transparent, + inactiveTrackColor: AGLDemoColors.backgroundInsetColor, + thumbShape: PolygonSliderThumb( + sliderValue: 3, thumbRadius: 23, isPressed: isPressed), + trackHeight: 16, + ), + child: Slider( + divisions: 10, + min: 0, + max: 10, + value: rearFrontValue, + onChanged: (newValue) { + ref + .read(audioStateProvider.notifier) + .setRearFront(newValue); + _currentVal = newValue; + }, + onChangeEnd: (value) { + setState(() { + isPressed = false; + }); + }, + onChangeStart: (value) { + setState(() { + isPressed = true; + }); + }, + ), + ), + ), + Padding( + padding: const EdgeInsets.only(right: 40), + child: InkWell( + onTap: () { + _increase(); + }, + child: const Icon( + CustomIcons.slider_front, + color: AGLDemoColors.periwinkleColor, + size: 48, + )), + ), + ], + ), + ), + ], + ); + } +} + +class PolygonSliderThumb extends SliderComponentShape { + final double thumbRadius; + final double sliderValue; + final bool isPressed; + const PolygonSliderThumb( + {required this.thumbRadius, + required this.sliderValue, + this.isPressed = false}); + + @override + Size getPreferredSize(bool isEnabled, bool isDiscrete) { + return Size.fromRadius(thumbRadius); + } + + @override + void paint( + PaintingContext context, + Offset center, { + required Animation<double> activationAnimation, + required Animation<double> enableAnimation, + required bool isDiscrete, + required TextPainter labelPainter, + required RenderBox parentBox, + required SliderThemeData sliderTheme, + required TextDirection textDirection, + required double value, + required double textScaleFactor, + required Size sizeWithOverflow, + }) { + // Define the slider thumb design here + final Canvas canvas = context.canvas; + var paintStroke = Paint() + ..color = + isPressed ? AGLDemoColors.jordyBlueColor : AGLDemoColors.neonBlueColor + ..strokeWidth = 2 + ..style = PaintingStyle.stroke + ..strokeCap = StrokeCap.round; + var paintFill = Paint() + ..color = isPressed ? Colors.white : AGLDemoColors.periwinkleColor + ..strokeWidth = 2 + ..style = PaintingStyle.fill + ..strokeCap = StrokeCap.round; + var path = Path(); + path.addOval(Rect.fromCircle( + center: center, + radius: 9, + )); + canvas.drawCircle(center, isPressed ? 37 : 32, paintFill); + canvas.drawShadow(path, Colors.black26, 0.5, false); + canvas.drawCircle(center, isPressed ? 21 : 16, paintStroke); + } +} + +//TODO add border to custom track Shape +class CustomRoundedRectSliderTrackShape extends SliderTrackShape + with BaseSliderTrackShape { + final double silderVal; + final bool? isFrontRear; + + CustomRoundedRectSliderTrackShape({ + required this.silderVal, + this.isFrontRear = false, + }); + @override + void paint( + PaintingContext context, + Offset offset, { + required RenderBox parentBox, + required SliderThemeData sliderTheme, + required Animation<double> enableAnimation, + required TextDirection textDirection, + required Offset thumbCenter, + Offset? secondaryOffset, + bool isDiscrete = false, + bool isEnabled = false, + double additionalActiveTrackHeight = 2, + }) { + assert(sliderTheme.disabledActiveTrackColor != null); + assert(sliderTheme.disabledInactiveTrackColor != null); + assert(sliderTheme.activeTrackColor != null); + assert(sliderTheme.inactiveTrackColor != null); + assert(sliderTheme.thumbShape != null); + if (sliderTheme.trackHeight == null || sliderTheme.trackHeight! <= 0) { + return; + } + + final Rect trackRect = getPreferredRect( + parentBox: parentBox, + offset: offset, + sliderTheme: sliderTheme, + isEnabled: isEnabled, + isDiscrete: isDiscrete, + ); + final Radius trackRadius = Radius.circular(trackRect.height / 2); + final Radius activeTrackRadius = + Radius.circular((trackRect.height + additionalActiveTrackHeight) / 2); + final activeGradientRect = Rect.fromLTRB( + trackRect.left, + textDirection == TextDirection.ltr + ? trackRect.top - (additionalActiveTrackHeight / 2) + : trackRect.top, + thumbCenter.dx, + (textDirection == TextDirection.ltr) + ? trackRect.bottom + (additionalActiveTrackHeight / 2) + : trackRect.bottom, + ); + + LinearGradient gradient = const LinearGradient( + colors: [AGLDemoColors.jordyBlueColor, Colors.white]); + // Assign the track segment paints, which are leading: active and + // trailing: inactive. + final ColorTween activeTrackColorTween = ColorTween( + begin: sliderTheme.disabledActiveTrackColor, + end: sliderTheme.activeTrackColor); + final ColorTween inactiveTrackColorTween = ColorTween( + begin: sliderTheme.disabledInactiveTrackColor, + end: sliderTheme.inactiveTrackColor); + final Paint activePaint = Paint() + ..shader = gradient.createShader(activeGradientRect) + ..color = activeTrackColorTween.evaluate(enableAnimation)!; + final Paint inactivePaint = Paint() + ..color = inactiveTrackColorTween.evaluate(enableAnimation)!; + final Paint leftTrackPaint; + final Paint rightTrackPaint; + switch (textDirection) { + case TextDirection.ltr: + leftTrackPaint = activePaint; + rightTrackPaint = inactivePaint; + case TextDirection.rtl: + leftTrackPaint = inactivePaint; + rightTrackPaint = activePaint; + } + //center divider + final smallRect = + Rect.fromLTWH(trackRect.right / 2, trackRect.bottom / 2 + 15, 10, 40); + context.canvas.drawRRect( + RRect.fromRectAndCorners(smallRect, + topLeft: const Radius.circular(25), + topRight: const Radius.circular(25), + bottomLeft: const Radius.circular(25), + bottomRight: const Radius.circular(25)), + //silderVal > 5 ? leftTrackPaint : rightTrackPaint); + isFrontRear! + ? rightTrackPaint + : silderVal > 5 + ? leftTrackPaint + : rightTrackPaint); +//active + context.canvas.drawRRect( + RRect.fromLTRBAndCorners( + trackRect.left, + (textDirection == TextDirection.ltr) + ? trackRect.top - (additionalActiveTrackHeight / 2) + : trackRect.top, + thumbCenter.dx, + (textDirection == TextDirection.ltr) + ? trackRect.bottom + (additionalActiveTrackHeight / 2) + : trackRect.bottom, + topLeft: (textDirection == TextDirection.ltr) + ? activeTrackRadius + : trackRadius, + bottomLeft: (textDirection == TextDirection.ltr) + ? activeTrackRadius + : trackRadius, + ), + isFrontRear! ? rightTrackPaint : leftTrackPaint, + ); + //inactive + context.canvas.drawRRect( + RRect.fromLTRBAndCorners( + thumbCenter.dx, + (textDirection == TextDirection.rtl) + ? trackRect.top - (additionalActiveTrackHeight / 2) + : trackRect.top, + trackRect.right, + (textDirection == TextDirection.rtl) + ? trackRect.bottom + (additionalActiveTrackHeight / 2) + : trackRect.bottom, + topRight: (textDirection == TextDirection.rtl) + ? activeTrackRadius + : trackRadius, + bottomRight: (textDirection == TextDirection.rtl) + ? activeTrackRadius + : trackRadius, + ), + rightTrackPaint, + ); + } +} diff --git a/lib/presentation/screens/settings/settings_screens/bluetooth/bluetooth_screen.dart b/lib/presentation/screens/settings/settings_screens/bluetooth/bluetooth_screen.dart new file mode 100644 index 0000000..fe53953 --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/bluetooth/bluetooth_screen.dart @@ -0,0 +1,12 @@ +import 'package:flutter_ics_homescreen/export.dart'; +import 'widgets/bluetooth_content.dart'; + +class BluetoothPage extends ConsumerWidget { + const BluetoothPage({super.key}); + + static Page<void> page() => const MaterialPage<void>(child: BluetoothPage()); + @override + Widget build(BuildContext context, WidgetRef ref) { + return const Scaffold(body: BluetoothContent()); + } +} diff --git a/lib/presentation/screens/settings/settings_screens/bluetooth/widgets/bluetooth.dart b/lib/presentation/screens/settings/settings_screens/bluetooth/widgets/bluetooth.dart new file mode 100644 index 0000000..39ba417 --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/bluetooth/widgets/bluetooth.dart @@ -0,0 +1,12 @@ +import '../../../../../../export.dart'; + +class Bluetooth { + final Icon icon; + final String name; + final bool? isConnected; + Bluetooth({ + required this.icon, + required this.name, + this.isConnected = false, + }); +} diff --git a/lib/presentation/screens/settings/settings_screens/bluetooth/widgets/bluetooth_content.dart b/lib/presentation/screens/settings/settings_screens/bluetooth/widgets/bluetooth_content.dart new file mode 100644 index 0000000..446a3b5 --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/bluetooth/widgets/bluetooth_content.dart @@ -0,0 +1,219 @@ +import 'package:flutter_ics_homescreen/presentation/custom_icons/custom_icons.dart'; + +import '../../../../../../../export.dart'; +import 'bluetooth.dart'; + +class BluetoothContent extends ConsumerStatefulWidget { + const BluetoothContent({ + super.key, + }); + + @override + BluetoothContentState createState() => BluetoothContentState(); +} + +class BluetoothContentState extends ConsumerState<BluetoothContent> { + final List<Bluetooth> btList = [ + Bluetooth( + icon: const Icon(CustomIcons.wifi_4_bar_unlocked), + name: 'bt', + isConnected: true), + Bluetooth( + icon: const Icon(CustomIcons.wifi_4_bar_locked), name: 'BT Phone 0'), + Bluetooth( + icon: const Icon(CustomIcons.wifi_3_bar_locked), name: 'BT Phone 1'), + Bluetooth( + icon: const Icon(CustomIcons.wifi_2_bar_locked), name: 'BT Phone 2'), + Bluetooth( + icon: const Icon(CustomIcons.wifi_1_bar_locked), name: 'BT Phone 1'), + ]; + bool isLoading = false; + Bluetooth currentBt = + Bluetooth(icon: const Icon(Icons.wifi), name: '22', isConnected: true); + @override + void initState() { + currentBt = btList[0]; + super.initState(); + } + + void setCurrentBt(int index) async { + if (currentBt == btList[index]) return; + isLoading = true; + setState(() { + currentBt = btList[index]; + }); + Future.delayed(const Duration(seconds: 2), () { + setState(() { + isLoading = false; + }); + }); + } + + void removeBtPair(int index) { + setState(() { + btList.removeAt(index); + }); + } + + void disconnect() { + setState(() { + currentBt = Bluetooth( + icon: const Icon( + Icons.bluetooth_disabled, + ), + name: ''); + }); + } + + @override + Widget build(BuildContext context) { + ref.watch(usersProvider.select((user) => user.selectedUser)); + + return Column( + children: [ + CommonTitle( + title: "Bluetooth", + hasBackButton: true, + onPressed: () { + context.flow<AppState>().update((state) => AppState.settings); + }, + ), + Expanded( + child: ListView.separated( + padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 144), + itemCount: btList.length, + separatorBuilder: (context, index) { + return const SizedBox( + height: 8, + ); + }, + itemBuilder: (context, index) { + return Container( + height: 130, + alignment: Alignment.center, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: currentBt == btList[index] + ? [0, 0.01, 0.8] + : [0.1, 1], + colors: currentBt == btList[index] + ? <Color>[ + Colors.white, + Colors.blue, + const Color.fromARGB(16, 41, 98, 255) + ] + : <Color>[Colors.black, Colors.black12]), + ), + child: InkWell( + onTap: () { + setCurrentBt(index); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 17, horizontal: 24), + child: Row(children: [ + Expanded( + child: Text( + btList[index].name, + //style: Theme.of(context).textTheme.titleMedium, + style: TextStyle( + color: currentBt == btList[index] + ? Colors.white + : AGLDemoColors.periwinkleColor, + fontSize: 40), + ), + ), + currentBt == btList[index] + ? Row( + mainAxisSize: MainAxisSize.min, + children: [ + isLoading + ? const Padding( + padding: EdgeInsets.only(right: 15.0), + child: Text( + 'Connecting...', + style: TextStyle(fontSize: 26), + ), + ) + : Padding( + padding: + const EdgeInsets.only(right: 8.0), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: + const Color(0xFF1C2D92), + side: const BorderSide( + color: Color(0xFF285DF4), + width: 2), + ), + child: const Padding( + padding: EdgeInsets.all(18), + child: Text( + 'Disconnect', + style: TextStyle( + color: Color(0xFFC1D8FF), + fontSize: 26, + ), + ), + ), + onPressed: () { + disconnect(); + }, + ), + ), + isLoading + ? const SizedBox( + width: 48, + height: 48, + child: CircularProgressIndicator( + strokeWidth: 3, + )) + : IconButton( + padding: EdgeInsets.zero, + onPressed: () { + removeBtPair(index); + }, + icon: const Icon( + Icons.close, + color: AGLDemoColors.periwinkleColor, + size: 48, + ), + ), + ], + ) + : IconButton( + padding: EdgeInsets.zero, + onPressed: () { + removeBtPair(index); + }, + icon: const Icon( + Icons.close, + color: AGLDemoColors.periwinkleColor, + size: 48, + ), + ), + ]), + ), + ), + ); + }, + ), + ), + Padding( + padding: const EdgeInsets.only(bottom: 150.0), + child: GenericButton( + heigth: 130, + width: 501, + text: 'Scan for New Device', + onTap: () {}, + ), + ), + const SizedBox( + height: 100, + ) + ], + ); + } +} diff --git a/lib/presentation/screens/settings/settings_screens/date_time/date/date_screen.dart b/lib/presentation/screens/settings/settings_screens/date_time/date/date_screen.dart new file mode 100644 index 0000000..6802ed0 --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/date_time/date/date_screen.dart @@ -0,0 +1,218 @@ +import 'package:flutter_ics_homescreen/export.dart'; +import 'package:calendar_date_picker2/calendar_date_picker2.dart'; +import 'package:intl/intl.dart'; + +class DatePage extends ConsumerWidget { + const DatePage({super.key}); + static Page<void> page() => const MaterialPage<void>(child: DatePage()); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + body: Column(children: [ + CommonTitle( + title: 'Date', + hasBackButton: true, + onPressed: () { + context.flow<AppState>().update((state) => AppState.dateTime); + }, + ), + const Expanded( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 20, horizontal: 144), + child: SingleChildScrollView(child: DateScreenWidget())), + ), + ]), + ); + } +} + +class DateScreenWidget extends ConsumerStatefulWidget { + const DateScreenWidget({super.key}); + Page<void> page() => const MaterialPage<void>(child: DateScreenWidget()); + + @override + DateScreenWidgetState createState() => DateScreenWidgetState(); +} + +class DateScreenWidgetState extends ConsumerState<DateScreenWidget> { + late String selectedDate; + + onPressed({required String type}) { + if (type == "confirm") { + ref.read(dateTimeStateProvider.notifier).setDate(selectedDate); + context.flow<AppState>().update((state) => AppState.dateTime); + } else if (type == "cancel") { + context.flow<AppState>().update((state) => AppState.dateTime); + } + } + + @override + void initState() { + selectedDate = ref.read(dateTimeStateProvider).date; + + super.initState(); + } + + @override + Widget build(BuildContext context) { + Size size = MediaQuery.sizeOf(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + CalendarDatePicker2( + config: CalendarDatePicker2Config( + calendarType: CalendarDatePicker2Type.single, + dayBuilder: ( + {required date, + decoration, + isDisabled, + isSelected, + isToday, + textStyle}) { + Widget? dayWidget; + dayWidget = Container( + decoration: decoration, + child: Center( + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + Text( + MaterialLocalizations.of(context) + .formatDecimal(date.day), + style: textStyle, + ), + ], + ), + ), + ); + + return dayWidget; + }, + dayTextStyle: const TextStyle( + color: AGLDemoColors.periwinkleColor, fontSize: 40), + selectedDayHighlightColor: AGLDemoColors.neonBlueColor, + controlsTextStyle: const TextStyle( + color: AGLDemoColors.periwinkleColor, fontSize: 40), + weekdayLabelTextStyle: const TextStyle( + color: AGLDemoColors.periwinkleColor, fontSize: 40), + controlsHeight: 40, + dayTextStylePredicate: ({required date}) { + return const TextStyle( + color: AGLDemoColors.periwinkleColor, fontSize: 40); + }, + selectedDayTextStyle: + const TextStyle(color: Colors.white, fontSize: 40)), + value: selectedDate == "mm/dd/yyyy" + ? [] + : [DateFormat().add_yMMMMd().parse(selectedDate)], + onValueChanged: (dates) { + setState(() { + selectedDate = DateFormat().add_yMMMMd().format(dates.first!); + }); + }, + ), + const SizedBox( + height: 120, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Material( + color: Colors.transparent, + child: InkWell( + onHighlightChanged: (value) { + // setState(() { + // isCancelButtonHighlighted = value; + // }); + }, + onTap: () { + onPressed(type: "cancel"); + + // onTap(type: "cancel"); + }, + child: Container( + width: size.width / 3.2, + alignment: Alignment.center, + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.7), + blurRadius: 1.5, + offset: const Offset(1, 2)) + ], + gradient: LinearGradient(colors: [ + AGLDemoColors.resolutionBlueColor, + AGLDemoColors.neonBlueColor.withOpacity(0.15), + ]), + borderRadius: BorderRadius.circular(3), + border: Border.all( + color: + AGLDemoColors.neonBlueColor.withOpacity(0.20))), + child: const Padding( + padding: EdgeInsets.symmetric(vertical: 20), + child: Text( + "Cancel", + style: TextStyle( + color: AGLDemoColors.periwinkleColor, + fontWeight: FontWeight.w600, + fontSize: 40, + letterSpacing: 0.4), + ), + ), + ), + ), + ), + const SizedBox( + width: 10, + ), + Material( + color: Colors.transparent, + child: InkWell( + onHighlightChanged: (value) { + // setState(() { + // isCancelButtonHighlighted = value; + // }); + }, + onTap: () { + onPressed(type: "confirm"); + // onTap(type: "cancel"); + }, + child: Container( + width: MediaQuery.sizeOf(context).width / 3.2, + alignment: Alignment.center, + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.7), + blurRadius: 1.5, + offset: const Offset(1, 2)) + ], + gradient: LinearGradient(colors: [ + AGLDemoColors.resolutionBlueColor, + AGLDemoColors.neonBlueColor.withOpacity(0.15), + ]), + borderRadius: BorderRadius.circular(3), + border: Border.all( + color: + AGLDemoColors.neonBlueColor.withOpacity(0.20))), + child: const Padding( + padding: EdgeInsets.symmetric(vertical: 20), + child: Text( + "Confirm", + style: TextStyle( + color: AGLDemoColors.periwinkleColor, + fontWeight: FontWeight.w600, + fontSize: 40, + letterSpacing: 0.4), + ), + ), + ), + ), + ), + ], + ), + ], + ); + } +} diff --git a/lib/presentation/screens/settings/settings_screens/date_time/date_time_screen.dart b/lib/presentation/screens/settings/settings_screens/date_time/date_time_screen.dart new file mode 100644 index 0000000..2365ecc --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/date_time/date_time_screen.dart @@ -0,0 +1,54 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class DateTimePage extends ConsumerWidget { + const DateTimePage({super.key}); + + static Page<void> page() => const MaterialPage<void>(child: DateTimePage()); + @override + Widget build(BuildContext context, WidgetRef ref) { + final dateTime = ref.watch(dateTimeStateProvider.select((val) => val)); + + return Scaffold( + body: Column( + children: [ + CommonTitle( + title: 'Date & Time', + hasBackButton: true, + onPressed: () { + context.flow<AppState>().update((state) => AppState.settings); + }, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 144), + child: ListView( + children: [ + UnitsTile( + image: "assets/Calendar.svg", + title: 'Date', + unitName: dateTime.date, + hasSwich: false, + voidCallback: () async { + context + .flow<AppState>() + .update((next) => AppState.date); + }), + UnitsTile( + image: "assets/Time.svg", + title: 'Time', + unitName: dateTime.time, + hasSwich: true, + voidCallback: () { + context + .flow<AppState>() + .update((next) => AppState.time); + }), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/screens/settings/settings_screens/date_time/time/time_screen.dart b/lib/presentation/screens/settings/settings_screens/date_time/time/time_screen.dart new file mode 100644 index 0000000..61131b5 --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/date_time/time/time_screen.dart @@ -0,0 +1,426 @@ +import 'package:flutter_ics_homescreen/export.dart'; +import 'package:flutter/services.dart'; +import 'package:intl/intl.dart'; + +class TimePage extends ConsumerWidget { + const TimePage({super.key}); + static Page<void> page() => const MaterialPage<void>(child: TimePage()); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + body: Column(children: [ + CommonTitle( + title: 'Time', + hasBackButton: true, + onPressed: () { + context.flow<AppState>().update((state) => AppState.dateTime); + }, + ), + const Expanded( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 20, horizontal: 144), + child: SingleChildScrollView(child: TimeScreenWidget())), + ), + ]), + ); + } +} + +class TimeScreenWidget extends ConsumerStatefulWidget { + const TimeScreenWidget({super.key}); + Page<void> page() => const MaterialPage<void>(child: TimeScreenWidget()); + + @override + TimeScreenWidgetState createState() => TimeScreenWidgetState(); +} + +class TimeScreenWidgetState extends ConsumerState<TimeScreenWidget> { + late int selectedTimeHour; + late int selectedTimeMinute; + String selectedMeridien = "AM"; + + TextEditingController hourController = TextEditingController(); + TextEditingController minuteController = TextEditingController(); + + onPressed({required String type}) { + if (type == "confirm") { + ref.read(dateTimeStateProvider.notifier).setTime( + "${hourController.text}:${minuteController.text} $selectedMeridien"); + context.flow<AppState>().update((state) => AppState.dateTime); + } else if (type == "cancel") { + context.flow<AppState>().update((state) => AppState.dateTime); + } + } + + @override + void initState() { + String time = ref.read(dateTimeStateProvider).time; + if (time == "hh:mm a") { + time = DateFormat('hh:mm a').format(DateTime.now()); + } + List<String> split = time.split(":"); + selectedTimeHour = int.parse(split[0]); + List<String> splitMeridian = split[1].split(" "); + + selectedTimeMinute = int.parse(splitMeridian[0]); + + setState(() { + selectedMeridien = splitMeridian[1]; + hourController.text = selectedTimeHour.toString(); + minuteController.text = selectedTimeMinute.toString(); + }); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + Size size = MediaQuery.sizeOf(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox( + height: 60, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TimeTextField( + controller: hourController, + type: "hour", + ), + const Padding( + padding: EdgeInsets.all(15), + child: Text( + ":", + style: TextStyle( + color: AGLDemoColors.periwinkleColor, fontSize: 44), + ), + ), + TimeTextField( + controller: minuteController, + type: "minute", + ), + ], + ), + const SizedBox( + height: 50, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(30), + bottomLeft: Radius.circular(30), + ), + border: Border.all(color: AGLDemoColors.periwinkleColor), + color: selectedMeridien == "AM" + ? AGLDemoColors.neonBlueColor + : Colors.transparent), + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(30), + bottomLeft: Radius.circular(30), + ), + onTap: () { + setState(() { + selectedMeridien = "AM"; + }); + }, + child: Padding( + padding: const EdgeInsets.only( + top: 17, bottom: 17, right: 30, left: 25), + child: Row( + children: [ + selectedMeridien == "AM" + ? const Icon( + Icons.check, + size: 48, + color: Colors.white, + ) + : const SizedBox( + width: 48, + height: 48, + ), + const SizedBox( + width: 3, + ), + Text( + "AM", + style: TextStyle( + color: selectedMeridien == "AM" + ? Colors.white + : AGLDemoColors.periwinkleColor, + fontSize: 40), + ) + ], + ), + ), + ), + ), + ), + Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topRight: Radius.circular(30), + bottomRight: Radius.circular(30), + ), + border: Border.all(color: AGLDemoColors.periwinkleColor), + color: selectedMeridien == "PM" + ? AGLDemoColors.neonBlueColor + : Colors.transparent), + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: const BorderRadius.only( + topRight: Radius.circular(30), + bottomRight: Radius.circular(30), + ), + onTap: () { + setState(() { + selectedMeridien = "PM"; + }); + }, + child: Padding( + padding: const EdgeInsets.only( + top: 17, bottom: 17, right: 30, left: 25), + child: Row( + children: [ + selectedMeridien == "PM" + ? const Icon( + Icons.check, + size: 48, + color: Colors.white, + ) + : const SizedBox( + width: 48, + height: 48, + ), + const SizedBox( + width: 3, + ), + Text( + "PM", + style: TextStyle( + color: selectedMeridien == "PM" + ? Colors.white + : AGLDemoColors.periwinkleColor, + fontSize: 40), + ), + ], + ), + ), + ), + ), + ), + ], + ), + const SizedBox( + height: 200, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Material( + color: Colors.transparent, + child: InkWell( + onHighlightChanged: (value) { + // setState(() { + // isCancelButtonHighlighted = value; + // }); + }, + onTap: () { + onPressed(type: "cancel"); + + // onTap(type: "cancel"); + }, + child: Container( + width: size.width / 3.2, + alignment: Alignment.center, + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.7), + blurRadius: 1.5, + offset: const Offset(1, 2)) + ], + gradient: LinearGradient(colors: [ + AGLDemoColors.resolutionBlueColor, + AGLDemoColors.neonBlueColor.withOpacity(0.15), + ]), + borderRadius: BorderRadius.circular(3), + border: Border.all( + color: + AGLDemoColors.neonBlueColor.withOpacity(0.20))), + child: const Padding( + padding: EdgeInsets.symmetric(vertical: 10), + child: Text( + "Cancel", + style: TextStyle( + color: AGLDemoColors.periwinkleColor, + fontWeight: FontWeight.w600, + fontSize: 40, + letterSpacing: 0.4), + ), + ), + ), + ), + ), + const SizedBox( + width: 10, + ), + Material( + color: Colors.transparent, + child: InkWell( + onHighlightChanged: (value) { + // setState(() { + // isCancelButtonHighlighted = value; + // }); + }, + onTap: () { + onPressed(type: "confirm"); + // onTap(type: "cancel"); + }, + child: Container( + width: MediaQuery.sizeOf(context).width / 3.2, + alignment: Alignment.center, + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.7), + blurRadius: 1.5, + offset: const Offset(1, 2)) + ], + gradient: LinearGradient(colors: [ + AGLDemoColors.resolutionBlueColor, + AGLDemoColors.neonBlueColor.withOpacity(0.15), + ]), + borderRadius: BorderRadius.circular(3), + border: Border.all( + color: + AGLDemoColors.neonBlueColor.withOpacity(0.20))), + child: const Padding( + padding: EdgeInsets.symmetric(vertical: 10), + child: Text( + "Confirm", + style: TextStyle( + color: AGLDemoColors.periwinkleColor, + fontWeight: FontWeight.w600, + fontSize: 40, + letterSpacing: 0.4), + ), + ), + ), + ), + ), + ], + ), + ], + ); + } +} + +class TimeTextField extends StatefulWidget { + const TimeTextField( + {super.key, required this.controller, required this.type}); + final TextEditingController controller; + final String type; + + @override + State<TimeTextField> createState() => _TimeTextFieldState(); +} + +class _TimeTextFieldState extends State<TimeTextField> { + TextEditingController controller = TextEditingController(); + @override + void initState() { + super.initState(); + controller = widget.controller; + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + onKeyBoardEvent(RawKeyEvent event) { + if (event.isKeyPressed(LogicalKeyboardKey.arrowUp)) { + if (controller.text != "") { + int value = int.tryParse(controller.text) ?? 0; + if (widget.type == "hour") { + if (value > 11) { + controller.text = "12"; + } else { + controller.text = (value + 1).toString(); + } + } else if (widget.type == "minute") { + if (value > 58) { + controller.text = "59"; + } else { + controller.text = (value + 1).toString(); + } + } + return KeyEventResult.handled; + } else { + controller.text = "0"; + return KeyEventResult.handled; + } + } else if (event.isKeyPressed(LogicalKeyboardKey.arrowDown)) { + if (controller.text.isNotEmpty) { + int value = int.tryParse(controller.text) ?? 0; + if (value < 1) { + controller.text = "0"; + } else { + controller.text = (value - 1).toString(); + } + return KeyEventResult.handled; + } else { + controller.text = "0"; + return KeyEventResult.handled; + } + } + return KeyEventResult.ignored; + } + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 185, + child: RawKeyboardListener( + focusNode: FocusNode(onKey: (node, event) { + return onKeyBoardEvent(event); + }), + child: TextField( + style: const TextStyle(color: Colors.white, fontSize: 40), + decoration: InputDecoration( + contentPadding: const EdgeInsets.symmetric(vertical: 23), + filled: true, + fillColor: AGLDemoColors.blueGlowFillColor.withOpacity(0.1)), + controller: controller, + textAlign: TextAlign.center, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ], + onChanged: (value) { + if (value.isNotEmpty) { + if (widget.type == "hour") { + if (int.parse(value) > 12) { + widget.controller.text = '12'; + } + } else if (widget.type == "minute") { + if (int.parse(value) > 59) { + widget.controller.text = '59'; + } + } + } + }, + ), + )); + } +} diff --git a/lib/presentation/screens/settings/settings_screens/profiles/profiles_screen.dart b/lib/presentation/screens/settings/settings_screens/profiles/profiles_screen.dart new file mode 100644 index 0000000..cd831b1 --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/profiles/profiles_screen.dart @@ -0,0 +1,20 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +import 'widgets/profiles_content.dart'; + +class ProfilesPage extends StatelessWidget { + const ProfilesPage({super.key}); + + static Page<void> page() => const MaterialPage<void>(child: ProfilesPage()); + @override + Widget build(BuildContext context) { + return const Scaffold( + body: Stack( + children: [ + ProfilesContent(), + ], + ), + ); + } +} + diff --git a/lib/presentation/screens/settings/settings_screens/profiles/widgets/new_profile_screen.dart b/lib/presentation/screens/settings/settings_screens/profiles/widgets/new_profile_screen.dart new file mode 100644 index 0000000..0cf1ddb --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/profiles/widgets/new_profile_screen.dart @@ -0,0 +1,252 @@ +import 'package:new_virtual_keyboard/virtual_keyboard.dart'; +import 'package:flutter_ics_homescreen/export.dart'; + +class NewProfilePage extends ConsumerStatefulWidget { + const NewProfilePage({super.key}); + + static Page<void> page() => const MaterialPage<void>(child: NewProfilePage()); + + @override + NewProfilePageState createState() => NewProfilePageState(); +} + +class NewProfilePageState extends ConsumerState<NewProfilePage> { + final _controller = TextEditingController(); + final _formKey = GlobalKey<FormState>(); + bool shiftEnabled = false; + + int chars = 0; + @override + void initState() { + super.initState(); + } + + _onKeyPress(VirtualKeyboardKey key) { + String text = _controller.text; + if (key.keyType == VirtualKeyboardKeyType.String) { + text = text + (shiftEnabled ? key.capsText : key.text)!; + } else if (key.keyType == VirtualKeyboardKeyType.Action) { + switch (key.action) { + case VirtualKeyboardKeyAction.Backspace: + if (text.isEmpty) return; + text = text.substring(0, text.length - 1); + break; + case VirtualKeyboardKeyAction.Return: + text = '$text\n'; + break; + case VirtualKeyboardKeyAction.Space: + text = text + key.text!; + break; + case VirtualKeyboardKeyAction.Shift: + shiftEnabled = !shiftEnabled; + break; + default: + } + } + +// Update the screen + if (text.length >= 25) { + _controller.text = text.substring(0, 25); + } else { + _controller.text = text; + } + + updateMaxChar(_controller.text.length); + } + + void showKeyboard() { + var ctx = homeScaffoldKey.currentContext; + showModalBottomSheet( + elevation: 0.0, + backgroundColor: Colors.transparent, + barrierColor: Colors.transparent, + context: ctx!, + builder: (ctx) { + return Container( + height: 479, + width: 1080, + decoration: const BoxDecoration( + color: AGLDemoColors.resolutionBlueColor, + border: Border( + top: BorderSide( + color: Color(0xFF295EF7), + width: 1, + )), + ), + child: VirtualKeyboard( + height: 478, + textColor: AGLDemoColors.periwinkleColor, + fontSize: 40, + // [A-Z, 0-9] + type: VirtualKeyboardType.Alphanumeric, + // Callback for key press event + onKeyPress: (key) { + _onKeyPress(key); + }, + ), + ); + }, + ); + } + + @override + void didChangeDependencies() async { + Future.delayed(const Duration(seconds: 0), () { + showKeyboard(); + }); + super.didChangeDependencies(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + void updateMaxChar(int charsCount) { + setState(() { + chars = charsCount; + }); + } + + void addUser() { + ref.read(usersProvider.notifier).addUser(_controller.text); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CommonTitle( + title: 'New Profile', + hasBackButton: true, + onPressed: () { + context.flow<AppState>().update((state) => AppState.profiles); + }, + ), + Expanded( + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 50, horizontal: 144), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + 'Profile Name', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(fontSize: 40), + ), + const SizedBox( + height: 20, + ), + Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: [0, 0.5, 1], + colors: <Color>[ + Colors.black12, + Colors.black, + Colors.black12 + ], + ), + ), + child: Column( + children: [ + Container( + height: 140, + padding: const EdgeInsets.only(top: 30), + child: TextFormField( + onTap: () { + showKeyboard(); + }, + controller: _controller, + autofocus: true, + maxLength: 25, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter some text'; + } + return null; + }, + //maxLengthEnforcement: MaxLengthEnforcement.none, + onChanged: (value) { + if (_controller.text.length <= 1) { + if (_formKey.currentState!.validate()) {} + _formKey.currentState!.save(); + } + updateMaxChar(_controller.text.length); + }, + decoration: const InputDecoration( + border: InputBorder.none, + counterText: '', + ), + textAlign: TextAlign.center, + textDirection: TextDirection.rtl, + style: const TextStyle(fontSize: 60), + ), + ), + Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: [0, 0.2, 0.8, 1], + colors: <Color>[ + Colors.transparent, + AGLDemoColors.neonBlueColor, + AGLDemoColors.neonBlueColor, + Colors.transparent, + ], + ), + ), + height: 2, + ) + ], + ), + ), + const SizedBox( + height: 20, + ), + Center( + child: Text('$chars/25 Characters', + style: const TextStyle(fontSize: 26)), + ), + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.only(bottom: 350.0), + child: GenericButton( + heigth: 130, + width: 493, + text: 'Save Profile', + onTap: () { + if (_formKey.currentState!.validate()) { + addUser(); + context + .flow<AppState>() + .update((state) => AppState.profiles); + } + }, + ), + + ), + Padding( + padding: const EdgeInsets.only(bottom: 150.0), + child: Container(), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/screens/settings/settings_screens/profiles/widgets/profiles_content.dart b/lib/presentation/screens/settings/settings_screens/profiles/widgets/profiles_content.dart new file mode 100644 index 0000000..eb89553 --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/profiles/widgets/profiles_content.dart @@ -0,0 +1,125 @@ +import '../../../../../../data/models/user.dart'; +import '../../../../../../export.dart'; + +class ProfilesContent extends ConsumerStatefulWidget { + const ProfilesContent({super.key}); + + @override + ProfilesContentState createState() => ProfilesContentState(); +} + +class ProfilesContentState extends ConsumerState<ProfilesContent> { + late User currentUser; + + void setCurrentUser(String userId) { + setState(() { + ref.read(usersProvider.notifier).selectUser(userId); + }); + } + + void removeUser(String userId) { + setState(() { + ref.read(usersProvider.notifier).removeUser(userId); + }); + } + + @override + Widget build(BuildContext context) { + var users = ref.watch(usersProvider.select((users) => users)); + final currentUser = users.selectedUser; + final usersList = users.users; + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + CommonTitle( + title: "Profiles", + hasBackButton: true, + onPressed: () { + context.flow<AppState>().update((state) => AppState.settings); + }, + ), + Expanded( + child: ListView.separated( + padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 144), + itemCount: usersList.length, + separatorBuilder: (context, index) { + return const SizedBox( + height: 8, + ); + }, + itemBuilder: (context, index) { + return Container( + height: 130, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: currentUser == usersList[index] + ? [0, 0.01, 0.8] + : [0.1, 1], + colors: currentUser == usersList[index] + ? <Color>[ + Colors.white, + Colors.blue, + const Color.fromARGB(16, 41, 98, 255) + ] + : <Color>[Colors.black, Colors.black12]), + ), + child: ListTile( + minVerticalPadding: 0.0, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16.0, vertical: 40.0), + leading: Text(users.users[index].name, + style: const TextStyle(fontSize: 40)), + //title: Text(widget.title), + //enabled: isSwitchOn, + trailing: IconButton( + padding: EdgeInsets.zero, + onPressed: () { + removeUser(users.users[index].id); + }, + icon: const Icon( + Icons.close, + color: AGLDemoColors.periwinkleColor, + size: 48, + ), + ), + + onTap: () { + setCurrentUser(usersList[index].id); + }, + ), + ); + }, + ), + ), + Padding( + padding: const EdgeInsets.only(bottom: 150.0), + child: Column( + children: [ + GenericButton( + heigth: 122, + width: 317, + text: 'New Profile', + onTap: () { + context + .flow<AppState>() + .update((state) => AppState.newProfile); + }, + ), + + const SizedBox(height: 20), + GenericButton( + heigth: 122, + width: 412, + text: 'Reset to Default', + onTap: () {}, + ), + ], + ), + ), + ], + ); + } +} + diff --git a/lib/presentation/screens/settings/settings_screens/units/distance/distance_unit_screen.dart b/lib/presentation/screens/settings/settings_screens/units/distance/distance_unit_screen.dart new file mode 100644 index 0000000..3e9c135 --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/units/distance/distance_unit_screen.dart @@ -0,0 +1,120 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class DistanceUnitPage extends ConsumerWidget { + const DistanceUnitPage({super.key}); + + static Page<void> page() => + const MaterialPage<void>(child: DistanceUnitPage()); + @override + Widget build(BuildContext context, WidgetRef ref) { + final unit = + ref.watch(unitStateProvider.select((unit) => unit.distanceUnit)); + + return Scaffold( + + body: Column( + children: [ + CommonTitle( + title: 'Distance', + hasBackButton: true, + onPressed: () { + context.flow<AppState>().update((state) => AppState.units); + }, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 144), + child: ListView( + children: [ + Container( + height: 130, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: unit == DistanceUnit.kilometers + ? [0, 0.01, 0.8] + : [0.1, 1], + colors: unit == DistanceUnit.kilometers + ? <Color>[ + Colors.white, + Colors.blue, + const Color.fromARGB(16, 41, 98, 255) + ] + : <Color>[Colors.black, Colors.black12]), + ), + child: ListTile( + minVerticalPadding: 0.0, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16.0, vertical: 40.0), + leading: Text( + 'Kilometers', + style: Theme.of(context).textTheme.titleMedium, + ), + //title: Text(widget.title), + //enabled: isSwitchOn, + trailing: unit == DistanceUnit.kilometers + ? const Icon(Icons.done, + color: AGLDemoColors.periwinkleColor, + size: 48, + ) + : null, + onTap: () { + ref + .read(unitStateProvider.notifier) + .setDistanceUnit(DistanceUnit.kilometers); + }), + ), + const SizedBox( + height: 8, + ), + Container( + height: 130, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: unit == DistanceUnit.miles + ? [0, 0.01, 0.8] + : [0.1, 1], + colors: unit == DistanceUnit.miles + ? <Color>[ + Colors.white, + Colors.blue, + const Color.fromARGB(16, 41, 98, 255) + ] + : <Color>[Colors.black, Colors.black12]), + ), + child: ListTile( + minVerticalPadding: 0.0, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16.0, vertical: 40.0), + leading: Text( + 'Miles', + style: Theme.of(context).textTheme.titleMedium, + ), + //title: Text(widget.title), + //enabled: isSwitchOn, + trailing: unit == DistanceUnit.miles + ? const Icon(Icons.done, + color: AGLDemoColors.periwinkleColor, + size: 48, + ) + : null, + + onTap: () { + ref + .read(unitStateProvider.notifier) + .setDistanceUnit(DistanceUnit.miles); + }, + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/screens/settings/settings_screens/units/temperature/temperature_unit_screen.dart b/lib/presentation/screens/settings/settings_screens/units/temperature/temperature_unit_screen.dart new file mode 100644 index 0000000..414bf32 --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/units/temperature/temperature_unit_screen.dart @@ -0,0 +1,120 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class TemperatureUnitPage extends ConsumerWidget { + const TemperatureUnitPage({super.key}); + + static Page<void> page() => + const MaterialPage<void>(child: TemperatureUnitPage()); + @override + Widget build(BuildContext context, WidgetRef ref) { + final unit = + ref.watch(unitStateProvider.select((unit) => unit.temperatureUnit)); + + return Scaffold( + + body: Column( + children: [ + CommonTitle( + title: 'Temperature', + hasBackButton: true, + onPressed: () { + context.flow<AppState>().update((state) => AppState.units); + }, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 144), + child: ListView( + children: [ + Container( + height: 130, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: unit == TemperatureUnit.celsius + ? [0, 0.01, 0.8] + : [0.1, 1], + colors: unit == TemperatureUnit.celsius + ? <Color>[ + Colors.white, + Colors.blue, + const Color.fromARGB(16, 41, 98, 255) + ] + : <Color>[Colors.black, Colors.black12]), + ), + child: ListTile( + minVerticalPadding: 0.0, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16.0, vertical: 40.0), + leading: Text( + 'Celsius', + style: Theme.of(context).textTheme.titleMedium, + ), + //title: Text(widget.title), + //enabled: isSwitchOn, + trailing: unit == TemperatureUnit.celsius + ? const Icon(Icons.done, + color: AGLDemoColors.periwinkleColor, + size: 48, + ) + : null, + onTap: () { + ref + .read(unitStateProvider.notifier) + .setTemperatureUnit(TemperatureUnit.celsius); + }), + ), + const SizedBox( + height: 5, + ), + Container( + height: 130, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: unit == TemperatureUnit.fahrenheit + ? [0, 0.01, 0.8] + : [0.1, 1], + colors: unit == TemperatureUnit.fahrenheit + ? <Color>[ + Colors.white, + Colors.blue, + const Color.fromARGB(16, 41, 98, 255) + ] + : <Color>[Colors.black, Colors.black12]), + ), + child: ListTile( + minVerticalPadding: 0.0, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16.0, vertical: 40.0), + leading: Text( + 'Fahrenheit', + style: Theme.of(context).textTheme.titleMedium, + ), + //title: Text(widget.title), + //enabled: isSwitchOn, + trailing: unit == TemperatureUnit.fahrenheit + ? const Icon(Icons.done, + color: AGLDemoColors.periwinkleColor, + size: 38, + ) + : null, + + onTap: () { + ref + .read(unitStateProvider.notifier) + .setTemperatureUnit(TemperatureUnit.fahrenheit); + }, + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/screens/settings/settings_screens/units/units_screen.dart b/lib/presentation/screens/settings/settings_screens/units/units_screen.dart new file mode 100644 index 0000000..1c6e37c --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/units/units_screen.dart @@ -0,0 +1,159 @@ +import 'package:flutter_ics_homescreen/core/utils/helpers.dart'; +import 'package:flutter_ics_homescreen/export.dart'; + +class UnitsPage extends ConsumerWidget { + const UnitsPage({super.key}); + + static Page<void> page() => const MaterialPage<void>(child: UnitsPage()); + @override + Widget build(BuildContext context, WidgetRef ref) { + final unit = ref.watch(unitStateProvider.select((unit) => unit)); + + return Scaffold( + //appBar: SettingsTopBar('Units'), + + body: Column( + children: [ + CommonTitle( + title: 'Units', + hasBackButton: true, + onPressed: () { + context.flow<AppState>().update((state) => AppState.settings); + }, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 144), + child: ListView( + children: [ + UnitsTile( + icon: Icons.calendar_month_outlined, + title: 'Distance', + unitName: unit.distanceUnit == DistanceUnit.kilometers + ? 'Kilometers' + : 'Miles', + hasSwich: false, + voidCallback: () async { + context + .flow<AppState>() + .update((next) => AppState.distanceUnit); + }), + UnitsTile( + icon: Icons.straighten, + title: 'Temperature', + unitName: unit.temperatureUnit == TemperatureUnit.celsius + ? 'Celsius' + : 'Fahrenheit', + hasSwich: true, + voidCallback: () { + context + .flow<AppState>() + .update((next) => AppState.tempUnit); + }), + ], + ), + ), + ), + ], + ), + ); + } +} + +class UnitsTile extends ConsumerStatefulWidget { + final IconData? icon; + final String title; + final String unitName; + final bool hasSwich; + final VoidCallback voidCallback; + final String? image; + const UnitsTile({ + Key? key, + this.icon, + required this.title, + required this.unitName, + required this.hasSwich, + required this.voidCallback, + this.image, + }) : super(key: key); + + @override + UnitsTileState createState() => UnitsTileState(); +} + +class UnitsTileState extends ConsumerState<UnitsTile> { + @override + Widget build(BuildContext context) { + return Column( + children: [ + Container( + margin: const EdgeInsets.symmetric(vertical: 8), + padding: const EdgeInsets.symmetric(vertical: 15), + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: [0.3, 1], + colors: <Color>[Colors.black, Colors.black12]), + ), + //color: Color(0xFF0D113F), + child: ListTile( + contentPadding: + const EdgeInsets.symmetric(vertical: 17, horizontal: 24), + leading: widget.icon != null + ? Icon( + widget.icon, + color: AGLDemoColors.periwinkleColor, + size: 48, + ) + : Padding( + padding: const EdgeInsets.only(right: 24), + child: SvgPicture.asset( + widget.image!, + width: 48, + height: 48, + ), + ), + title: Text( + widget.title, + style: TextStyle( + color: AGLDemoColors.periwinkleColor, + shadows: [ + Helpers.dropShadowRegular, + ], + fontSize: 40), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + widget.unitName, + style: TextStyle( + color: AGLDemoColors.periwinkleColor, + shadows: [ + Helpers.dropShadowRegular, + ], + fontSize: 40, + ), + ), + const SizedBox( + width: 24, + ), + const Icon( + Icons.arrow_forward_ios, + color: AGLDemoColors.periwinkleColor, + size: 48, + ), + ], + ), + onTap: widget.voidCallback, + ), + ), + const SizedBox( + height: 8, + ) + ], + ); + } +} diff --git a/lib/presentation/screens/settings/settings_screens/version_info/version_info_screend.dart b/lib/presentation/screens/settings/settings_screens/version_info/version_info_screend.dart new file mode 100644 index 0000000..fce1837 --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/version_info/version_info_screend.dart @@ -0,0 +1,83 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class VersionInfoPage extends ConsumerWidget { + const VersionInfoPage({super.key}); + + static Page<void> page() => + const MaterialPage<void>(child: VersionInfoPage()); + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + body: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CommonTitle( + title: 'Version Information', + hasBackButton: true, + onPressed: () { + context.flow<AppState>().update((state) => AppState.settings); + }, + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 144), + child: Column( + children: [ + //Lottie.asset('animations/hybrid_model_yellow_arrow.json'), + Lottie.asset( + 'animations/Logo_JSON.json', + fit: BoxFit.cover, + repeat: false, + ), + const SizedBox( + height: 24, + ), + Container( + height: 140, + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: [0.1, 1], + colors: <Color>[Colors.black, Colors.black12]), + ), + child: ListTile( + contentPadding: const EdgeInsets.only(top: 50, left: 25), + leading: Text( + aglVeriosn, + style: Theme.of(context).textTheme.titleMedium, + ), + ), + ), + const SizedBox( + height: 5, + ), + Container( + height: 140, + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: [0.1, 1], + colors: <Color>[Colors.black, Colors.black12]), + ), + child: ListTile( + contentPadding: const EdgeInsets.only(top: 50, left: 25), + + leading: Text( + kernelVeriosn, + style: Theme.of(context).textTheme.titleMedium, + ), + ), + ), + ], + ), + ), + const SizedBox( + height: 100, + ) + ], + ), + + ); + } +} diff --git a/lib/presentation/screens/settings/settings_screens/wifi/widgets/wifi.dart b/lib/presentation/screens/settings/settings_screens/wifi/widgets/wifi.dart new file mode 100644 index 0000000..cef2014 --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/wifi/widgets/wifi.dart @@ -0,0 +1,12 @@ +import '../../../../../../export.dart'; + +class Wifi { + final Icon icon; + final String name; + final bool? isConnected; + Wifi({ + required this.icon, + required this.name, + this.isConnected = false, + }); +} diff --git a/lib/presentation/screens/settings/settings_screens/wifi/widgets/wifi_content.dart b/lib/presentation/screens/settings/settings_screens/wifi/widgets/wifi_content.dart new file mode 100644 index 0000000..2473847 --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/wifi/widgets/wifi_content.dart @@ -0,0 +1,150 @@ +import 'package:flutter_ics_homescreen/presentation/custom_icons/custom_icons.dart'; + +import '../../../../../../export.dart'; +import 'wifi.dart'; + +class WifiContent extends ConsumerStatefulWidget { + const WifiContent({ + super.key, + }); + + @override + WifiContentState createState() => WifiContentState(); +} + +class WifiContentState extends ConsumerState<WifiContent> { + final List<Wifi> wifiList = [ + Wifi( + icon: const Icon( + CustomIcons.wifi_4_bar_unlocked, + size: 48, + ), + name: 'box2', + isConnected: true), + Wifi( + icon: const Icon( + CustomIcons.wifi_4_bar_locked, + size: 48, + ), + name: 'WIVACOM_FiberNew_B61E'), + Wifi( + icon: const Icon( + CustomIcons.wifi_3_bar_locked, + size: 48, + ), + name: 'OpenWrt', + ), + Wifi( + icon: const Icon( + CustomIcons.wifi_2_bar_locked, + size: 48, + ), + name: 'kahuna2'), + Wifi( + icon: const Icon( + CustomIcons.wifi_1_bar_locked, + size: 48, + ), + name: 'mip2'), + ]; + Wifi currentWifi = + Wifi(icon: const Icon(Icons.wifi), name: 'box2', isConnected: true); + @override + void initState() { + currentWifi = wifiList[0]; + super.initState(); + } + + void setCurrentWifi(int index) { + setState(() { + currentWifi = wifiList[index]; + }); + } + + @override + Widget build(BuildContext context) { + ref.watch(usersProvider.select((user) => user.selectedUser)); + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + CommonTitle( + title: "Wifi", + hasBackButton: true, + onPressed: () { + context.flow<AppState>().update((state) => AppState.settings); + }, + ), + Expanded( + child: ListView.separated( + padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 144), + itemCount: wifiList.length, + separatorBuilder: (context, index) { + return const SizedBox( + height: 8, + ); + }, + itemBuilder: (context, index) { + return Container( + height: 130, + + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: currentWifi == wifiList[index] + ? [0, 0.01, 0.8] + : [0.1, 1], + colors: currentWifi == wifiList[index] + ? <Color>[ + Colors.white, + Colors.blue, + const Color.fromARGB(16, 41, 98, 255) + ] + : <Color>[Colors.black, Colors.black12]), + ), + child: ListTile( + contentPadding: + const EdgeInsets.symmetric(vertical: 41, horizontal: 24), + + leading: wifiList[index].icon, + title: Text( + wifiList[index].name, + style: Theme.of(context).textTheme.titleMedium, + ), + onTap: () { + setCurrentWifi(index); + }, + ), + ); + }, + ), + ), + // Container( + // padding: const EdgeInsets.symmetric( + // horizontal: 175, + // ), + // child: ElevatedButton( + // style: ElevatedButton.styleFrom( + // backgroundColor: const Color(0xFF1C2D92), + // ), + // child: const Padding( + // padding: EdgeInsets.symmetric(horizontal: 0, vertical: 15), + // child: Text( + // 'New Profile', + // textAlign: TextAlign.center, + // style: TextStyle( + // color: Color(0xFFC1D8FF), + // fontSize: 20, + // ), + // ), + // ), + // onPressed: () { + // //context.flow<AppState>().update((state) => AppState.newProfile); + // }, + // ), + // ), + ], + ); + } +} diff --git a/lib/presentation/screens/settings/settings_screens/wifi/wifi_screen.dart b/lib/presentation/screens/settings/settings_screens/wifi/wifi_screen.dart new file mode 100644 index 0000000..1f85ae8 --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/wifi/wifi_screen.dart @@ -0,0 +1,17 @@ + +import 'package:flutter_ics_homescreen/export.dart'; +import 'widgets/wifi_content.dart'; + +class WifiPage extends StatelessWidget { + const WifiPage({super.key}); + + static Page<void> page() => const MaterialPage<void>(child: WifiPage()); + @override + Widget build(BuildContext context) { + //final currentUser = + //ref.watch(usersProvider.select((user) => user.selectedUser)); + + return const Scaffold(body: WifiContent()); + } +} + diff --git a/lib/presentation/screens/settings/settings_screens/wired/wired_screen.dart b/lib/presentation/screens/settings/settings_screens/wired/wired_screen.dart new file mode 100644 index 0000000..916b1b6 --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/wired/wired_screen.dart @@ -0,0 +1,76 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class WiredPage extends ConsumerWidget { + const WiredPage({super.key}); + + static Page<void> page() => const MaterialPage<void>(child: WiredPage()); + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + body: Column( + children: [ + CommonTitle( + title: 'Wired', + hasBackButton: true, + onPressed: () { + context.flow<AppState>().update((state) => AppState.settings); + }, + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 80), + child: Container( + height: 130, + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: [ + 0, + 0.01, + 0.8 + ], + colors: <Color>[ + Colors.white, + Colors.blue, + Color.fromARGB(16, 41, 98, 255) + ]), + ), + child: ListTile( + contentPadding: + const EdgeInsets.symmetric(vertical: 41, horizontal: 24), + + title: const Text( + 'hernet_0090451v407b_cable', + style: TextStyle(color: Colors.white, fontSize: 40), + ), + subtitle: const Text( + 'connected, 192.168.234.120', + style: TextStyle(color: Colors.white, fontSize: 26), + ), + trailing: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF1C2D92), + side: const BorderSide(color: Color(0xFF285DF4), width: 2), + ), + child: const Padding( + padding: + EdgeInsets.symmetric(vertical: 15.0, horizontal: 0), + child: Text( + + 'Configure', + style: TextStyle( + color: Color(0xFFC1D8FF), + fontSize: 26, + ), + ), + ), + onPressed: () {}, + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/screens/settings/widgets/settings_content.dart b/lib/presentation/screens/settings/widgets/settings_content.dart new file mode 100644 index 0000000..f73bf6d --- /dev/null +++ b/lib/presentation/screens/settings/widgets/settings_content.dart @@ -0,0 +1,95 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +import '../../../custom_icons/custom_icons.dart'; + +class Settings extends StatelessWidget { + const Settings({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + //crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CommonTitle( + title: 'Settings', + ), + Expanded( + child: ListView( + padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 144), + children: [ + SettingsTile( + icon: Icons.calendar_month_outlined, + title: 'Date & Time', + hasSwich: false, + voidCallback: () async { + context + .flow<AppState>() + .update((next) => AppState.dateTime); + }), + SettingsTile( + icon: Icons.bluetooth, + title: 'Bluetooth', + hasSwich: true, + voidCallback: () { + context + .flow<AppState>() + .update((next) => AppState.bluetooth); + }), + SettingsTile( + icon: Icons.wifi, + title: 'Wifi', + hasSwich: true, + voidCallback: () { + context.flow<AppState>().update((next) => AppState.wifi); + }), + SettingsTile( + icon: CustomIcons.wiredicon, + title: 'Wired', + hasSwich: false, + voidCallback: () { + context.flow<AppState>().update((next) => AppState.wired); + }), + SettingsTile( + icon: Icons.tune, + title: 'Audio Settings', + hasSwich: false, + voidCallback: () { + context + .flow<AppState>() + .update((next) => AppState.audioSettings); + }), + SettingsTile( + icon: Icons.person_2_outlined, + title: 'Profiles', + hasSwich: false, + voidCallback: () { + context + .flow<AppState>() + .update((next) => AppState.profiles); + }), + SettingsTile( + icon: Icons.straighten, + title: 'Units', + hasSwich: false, + voidCallback: () { + context.flow<AppState>().update((next) => AppState.units); + }), + SettingsTile( + icon: Icons.help_sharp, + title: 'Version Info', + hasSwich: false, + voidCallback: () { + context + .flow<AppState>() + .update((next) => AppState.versionInfo); + }), + ], + ), + ), + ], + ); + } +} diff --git a/lib/presentation/screens/settings/widgets/settings_list_tile.dart b/lib/presentation/screens/settings/widgets/settings_list_tile.dart new file mode 100644 index 0000000..4720001 --- /dev/null +++ b/lib/presentation/screens/settings/widgets/settings_list_tile.dart @@ -0,0 +1,234 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class SettingsTile extends ConsumerStatefulWidget { + final IconData icon; + final String title; + final bool hasSwich; + final VoidCallback voidCallback; + const SettingsTile({ + Key? key, + required this.icon, + required this.title, + required this.hasSwich, + required this.voidCallback, + }) : super(key: key); + + @override + SettingsTileState createState() => SettingsTileState(); +} + +class SettingsTileState extends ConsumerState<SettingsTile> { + bool isSwitchOn = true; + @override + Widget build(BuildContext context) { + final signal = ref.watch(signalsProvider.select((signal) => signal)); + if (widget.title == 'Bluetooth') { + isSwitchOn = signal.isBluetoothConnected; + } else if (widget.title == 'Wifi') { + isSwitchOn = signal.isWifiConnected; + } else { + // isSwitchOn = false; + } + return Column( + children: [ + GestureDetector( + onTap: isSwitchOn ? widget.voidCallback : () {}, + child: Container( + height: 130, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: isSwitchOn ? [0.3, 1] : [0.8, 1], + colors: isSwitchOn + ? <Color>[Colors.black, Colors.black12] + : <Color>[ + const Color.fromARGB(50, 0, 0, 0), + Colors.transparent + ], + ), + ), + child: Card( + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(12), + // ), + color: Colors.transparent, + elevation: 5, + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 0, horizontal: 24), + child: Row( + children: [ + Icon( + widget.icon, + color: AGLDemoColors.periwinkleColor, + size: 48, + ), + const SizedBox(width: 24), + Expanded( + child: Text( + widget.title, + style: const TextStyle(fontSize: 40), + ), + ), + widget.hasSwich + ? Container( + width: 126, + height: 80, + decoration: const ShapeDecoration( + color: + AGLDemoColors.gradientBackgroundDarkColor, + shape: StadiumBorder( + side: BorderSide( + color: Color(0xFF5477D4), + width: 4, + )), + ), + child: FittedBox( + fit: BoxFit.fill, + child: Switch( + value: isSwitchOn, + onChanged: (bool value) { + switch (widget.title) { + case 'Bluetooth': + ref + .read(signalsProvider.notifier) + .toggleBluetooth(); + break; + case 'Wifi': + ref + .read(signalsProvider.notifier) + .toggleWifi(); + break; + default: + } + setState(() { + isSwitchOn = value; + }); + // This is called when the user toggles the switch. + }, + inactiveTrackColor: Colors.transparent, + activeTrackColor: Colors.transparent, + thumbColor: + MaterialStateProperty.all<Color>( + AGLDemoColors.periwinkleColor)), + ), + ) + : const SizedBox(), + ], + ), + ), + ) + // ListTile( + // contentPadding: + // const EdgeInsets.symmetric(vertical: 41, horizontal: 24), + // minLeadingWidth: 55.0, + // minVerticalPadding: 0.0, + // leading: Icon( + // widget.icon, + // color: AGLDemoColors.periwinkleColor, + // size: 48, + // ), + // title: Text( + // widget.title, + // style: const TextStyle(fontSize: 40), + // ), + // enabled: isSwitchOn, + // trailing: widget.hasSwich + // ? Container( + // width: 126, + // height: 80, + // decoration: const ShapeDecoration( + // color: AGLDemoColors.gradientBackgroundDarkColor, + // shape: StadiumBorder( + // side: BorderSide( + // color: Color(0xFF5477D4), + // //color: Colors.amber, + + // width: 1.5, + // )), + // ), + // child: Switch( + // value: isSwitchOn, + // onChanged: (bool value) { + // switch (widget.title) { + // case 'Bluetooth': + // ref + // .read(signalsProvider.notifier) + // .toggleBluetooth(); + // break; + // case 'Wifi': + // ref.read(signalsProvider.notifier).toggleWifi(); + // break; + // default: + // } + // setState(() { + // isSwitchOn = value; + // }); + // // This is called when the user toggles the switch. + // }, + // inactiveTrackColor: Colors.transparent, + // activeTrackColor: Colors.transparent, + // thumbColor: MaterialStateProperty.all<Color>( + // AGLDemoColors.periwinkleColor)), + // ) + // : const SizedBox( + // //Spacer + // height: 80, + // ), + // onTap: widget.voidCallback, + + // ), + ), + ), + const SizedBox( + height: 8, + ) + ], + ); + } +} + +// List<SettingsTile> settingsList = [ +// SettingsTile( +// icon: Icons.calendar_month_outlined, +// title: 'Date & Time', +// hasSwich: false, +// voidCallback: () {}), +// SettingsTile( +// icon: Icons.bluetooth, +// title: 'Bluetooth', +// hasSwich: true, +// voidCallback: () {}), +// SettingsTile( +// icon: Icons.wifi, +// title: 'Wifi', +// hasSwich: true, +// voidCallback: () {}, +// ), +// SettingsTile( +// icon: Icons.settings, +// title: 'Wired', +// hasSwich: false, +// voidCallback: () {}), +// SettingsTile( +// icon: Icons.tune, +// title: 'Audio Settings', +// hasSwich: false, +// voidCallback: () {}), +// SettingsTile( +// icon: Icons.person_2_outlined, +// title: 'Profiles', +// hasSwich: false, +// voidCallback: () {}), +// SettingsTile( +// icon: Icons.straighten, +// title: 'Units', +// hasSwich: false, +// voidCallback: () {}), +// SettingsTile( +// icon: Icons.help_sharp, +// title: 'Veriosn Info', +// hasSwich: false, +// voidCallback: () {}), +// ]; diff --git a/lib/presentation/screens/splash/splash.dart b/lib/presentation/screens/splash/splash.dart new file mode 100644 index 0000000..a51f425 --- /dev/null +++ b/lib/presentation/screens/splash/splash.dart @@ -0,0 +1,26 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class SplashPage extends StatelessWidget { + const SplashPage({super.key}); + + static Page<void> page() => const MaterialPage<void>(child: SplashPage()); + @override + Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; + debugPrint(size.width.toString()); + debugPrint(size.height.toString()); + return Stack( + children: [ + SizedBox( + width: double.infinity, + height: double.infinity, + child: SvgPicture.asset( + 'assets/splashTextures.svg', + alignment: Alignment.center, + ), + ), + const Center(child: SplashContent()), + ], + ); + } +} diff --git a/lib/presentation/screens/splash/widget/splash_content.dart b/lib/presentation/screens/splash/widget/splash_content.dart new file mode 100644 index 0000000..325baeb --- /dev/null +++ b/lib/presentation/screens/splash/widget/splash_content.dart @@ -0,0 +1,142 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class SplashContent extends ConsumerStatefulWidget { + const SplashContent({super.key}); + + @override + SplashContentState createState() => SplashContentState(); +} + +class SplashContentState extends ConsumerState<SplashContent> + with TickerProviderStateMixin { + late Animation<double> _fadeAnimation; + late AnimationController _lottieController; + late AnimationController _fadeController; + bool _showLottieAnimation = + true; // New state to control the visibility of Lottie animation + + @override + void initState() { + super.initState(); + // If you need to control the Lottie animation, initialize its controller + _lottieController = AnimationController( + vsync: this, + duration: const Duration(seconds: 7), + ); + + Future.delayed(const Duration(milliseconds: 1500), () { + _lottieController.repeat(); + }); + + // Initialize the fade animation controller + _fadeController = AnimationController( + vsync: this, + duration: const Duration(seconds: 1), // Fade transition duration + ); + + // Set up the fade animation + _fadeAnimation = + Tween<double>(begin: 0.0, end: 1.0).animate(_fadeController) + ..addListener(() { + // Check the status of the animation and set the state to hide Lottie when fading starts. + if (_fadeAnimation.value > 0.0 && _showLottieAnimation) { + setState(() { + _showLottieAnimation = false; + }); + } + }); + + // Start the fade-in transition after the Lottie animation has played for some time + Future.delayed(const Duration(seconds: 6), () { + // Stop the Lottie animation if needed + _lottieController.stop(); + + // Start the fade-in transition + _fadeController.forward(); + }); + } + + @override + void dispose() { + // Dispose the animation controller to release resources. + _fadeController.dispose(); + _lottieController.dispose(); + super.dispose(); + } + + @override + void didChangeDependencies() { + ref.read(vehicleProvider.notifier).startListen(); + super.didChangeDependencies(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + if (_showLottieAnimation) + Center( + child: Lottie.asset( + 'animations/Logo_JSON.json', + controller: _lottieController, + onLoaded: (composition) { + _lottieController.duration = composition.duration; + }, + ), + ), + // FadeTransition wraps existing UI. + FadeTransition( + opacity: _fadeAnimation, + child: Center( + child: buildWarningUI(), + ), + ), + ], + ); + } + + Widget buildWarningUI() { + return Column( + children: [ + const Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'WARNING:', + style: TextStyle(color: Color(0xFFC1D8FF), fontSize: 44), + ), + SizedBox(height: 38), + SizedBox( + //color: Colors.amber, + width: 757, + height: 488, + child: Text( + splashWarning, + style: TextStyle(color: Colors.white, fontSize: 40, height: 1.7, fontWeight: FontWeight.w400), + textAlign: TextAlign.left, + + ), + ), + ], + ), + ), + GenericButton( + heigth: 122, + width: 452, + text: 'Continue', + onTap: () { + ref.read(vehicleProvider.notifier).setInitialState(); + ref + .read(appProvider.notifier) + .update((state) => state = AppState.dashboard); + }, + ), + + const SizedBox( + height: 72, + ) + ], + ); + } +} diff --git a/lib/presentation/screens/weather/hourly_forecast.dart b/lib/presentation/screens/weather/hourly_forecast.dart new file mode 100644 index 0000000..aed8a6c --- /dev/null +++ b/lib/presentation/screens/weather/hourly_forecast.dart @@ -0,0 +1,152 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class HourlyForecast extends StatefulWidget { + const HourlyForecast({super.key}); + + @override + State<HourlyForecast> createState() => _HourlyForecastState(); +} + +class _HourlyForecastState extends State<HourlyForecast> { + String selectedForescastTime = "13:00"; + List<ForecastModel> foreCastList = [ + ForecastModel( + time: "13:00", image: "assets/weatherStat.svg", weather: "29.1°"), + ForecastModel( + time: "14:00", image: "assets/weatherStat.svg", weather: "28.1°"), + ForecastModel( + time: "15:00", image: "assets/weatherStat.svg", weather: "27.1°"), + ForecastModel( + time: "16:00", image: "assets/weatherStat.svg", weather: "29.1°"), + ForecastModel( + time: "13:00", image: "assets/weatherStat.svg", weather: "29.1°"), + ForecastModel( + time: "14:00", image: "assets/weatherStat.svg", weather: "28.1°"), + ForecastModel( + time: "15:00", image: "assets/weatherStat.svg", weather: "27.1°"), + ForecastModel( + time: "16:00", image: "assets/weatherStat.svg", weather: "29.1°"), + ]; + @override + Widget build(BuildContext context) { + double weatherIconSize = 126; + return Container( + padding: const EdgeInsets.all(1), + decoration: BoxDecoration( + gradient: LinearGradient(colors: [ + AGLDemoColors.periwinkleColor.withOpacity(0.2), + AGLDemoColors.periwinkleColor + ]), + boxShadow: [ + BoxShadow( + offset: const Offset(1, 2), + blurRadius: 16, + color: Colors.black.withOpacity(0.5)) + ], + borderRadius: BorderRadius.circular(40), + // border: Border.all(color: Colors.white12), + ), + child: Container( + padding: const EdgeInsets.only(top: 5, bottom: 20, left: 5, right: 5), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(40), + // border: Border.all(color: AGLDemoColors.periwinkleColor), + // boxShadow: [ + // BoxShadow( + // offset: const Offset(1, 2), + // blurRadius: 16, + // color: Colors.black.withOpacity(0.5)) + // ], + gradient: const RadialGradient( + //center: Alignment(0.7, -0.6), // near the top right + radius: 1, + colors: <Color>[ + Color.fromARGB(255, 12, 16, 57), // yellow sun + Color.fromARGB(255, 0, 0, 0), // blue sky + ], + ), + ), + child: + Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + "Hourly Forecast", + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.w500, + fontSize: 44), + ), + Text( + "March 9", + style: GoogleFonts.firaSans( + color: Colors.white, + fontWeight: FontWeight.w200, + fontSize: 44), + ), + ]), + ), + const SizedBox( + height: 20, + ), + SizedBox( + height: 320, + child: ListView.builder( + itemCount: foreCastList.length, + shrinkWrap: true, + padding: EdgeInsets.zero, + scrollDirection: Axis.horizontal, + itemBuilder: (context, index) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 32), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(32), + color: foreCastList[index].time == selectedForescastTime + ? AGLDemoColors.resolutionBlueColor + : Colors.transparent), + padding: const EdgeInsets.symmetric( + horizontal: 18, vertical: 10), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + foreCastList[index].time, + style: GoogleFonts.firaSans( + fontWeight: FontWeight.w100, + color: Colors.white, + fontSize: 40), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 30), + child: SvgPicture.asset( + "assets/weatherStat.svg", + width: weatherIconSize, + height: weatherIconSize, + ), + ), + Text( + foreCastList[index].weather, + style: const TextStyle( + color: Colors.white, fontSize: 40), + ), + ]), + ); + }), + ) + ]), + ), + ); + } +} + +class ForecastModel { + final String time; + final String image; + final String weather; + + ForecastModel( + {required this.time, required this.image, required this.weather}); +} diff --git a/lib/presentation/screens/weather/weather.dart b/lib/presentation/screens/weather/weather.dart new file mode 100644 index 0000000..7231700 --- /dev/null +++ b/lib/presentation/screens/weather/weather.dart @@ -0,0 +1,91 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class WeatherPage extends ConsumerWidget { + const WeatherPage({super.key}); + + static Page<void> page() => const MaterialPage<void>(child: WeatherPage()); + @override + Widget build(BuildContext context, WidgetRef ref) { + double weatherIconSize = MediaQuery.sizeOf(context).width * 0.278; + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + CommonTitle( + title: "Weather", + hasBackButton: true, + onPressed: () { + context.flow<AppState>().update((state) => AppState.apps); + }, + ), + const SizedBox( + height: 25, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 144), + child: SingleChildScrollView( + child: Column( + children: [ + const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.location_on_outlined, + color: Colors.white, + size: 48, + ), + SizedBox( + width: 7, + ), + Text( + "Fortaleza", + style: TextStyle( + color: Colors.white, + fontSize: 40, + fontWeight: FontWeight.w500), + ), + ], + ), + const SizedBox( + height: 80, + ), + SvgPicture.asset( + "assets/weatherStat.svg", + width: weatherIconSize, + height: weatherIconSize, + ), + const SizedBox( + height: 60, + ), + Text( + "28.3°C", + style: GoogleFonts.brunoAce( + color: Colors.white, fontSize: 128), + ), + const Padding( + padding: EdgeInsets.all(30.0), + child: Text( + "Partially Cloudy", + style: TextStyle(color: Colors.white, fontSize: 44), + ), + ), + const SizedBox( + height: 5, + ), + const Text( + "Max: 31° Min: 25°", + style: TextStyle(color: Colors.white, fontSize: 40), + ), + const SizedBox( + height: 80, + ), + const HourlyForecast() + ], + ), + ), + ), + ) + ], + ); + } +} |