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/presentation/screens |
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/presentation/screens')
60 files changed, 7528 insertions, 0 deletions
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() + ], + ), + ), + ), + ) + ], + ); + } +} |