diff options
Diffstat (limited to 'lib/presentation')
12 files changed, 883 insertions, 5 deletions
diff --git a/lib/presentation/common_widget/voice_assistant_button.dart b/lib/presentation/common_widget/voice_assistant_button.dart new file mode 100644 index 0000000..2a82a0a --- /dev/null +++ b/lib/presentation/common_widget/voice_assistant_button.dart @@ -0,0 +1,214 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class VoiceAssistantButton extends ConsumerStatefulWidget { + const VoiceAssistantButton({super.key}); + + @override + ConsumerState<VoiceAssistantButton> createState() => _VoiceAssistantButtonState(); +} + +class _VoiceAssistantButtonState extends ConsumerState<VoiceAssistantButton> with SingleTickerProviderStateMixin { + bool _showOverlay = false; + late AnimationController _animationController; + late Animation<double> _pulseAnimation; + int overlayLock = 0; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 700), + )..stop(); // Stop the animation initially + + _pulseAnimation = Tween<double>(begin: 1.0, end: 1.05).animate( + CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), + ); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + void _onTap() { + ref.read(voiceAssistantStateProvider.notifier).updateCommandResponse(""); + ref.read(voiceAssistantStateProvider.notifier).updateCommand(""); + bool state = ref.read(voiceAssistantStateProvider.notifier).toggleButtonPressed(); + if(state){ + var voiceAgentClient = ref.read(voiceAgentClientProvider); + voiceAgentClient.startVoiceAssistant(); + } + } + + void _showAssistantPopup(BuildContext context) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) { + return Consumer( + builder: (context, ref, child) { + final String? command = ref.watch(voiceAssistantStateProvider.select((value) => value.command)); + final String? commandResponse = ref.watch(voiceAssistantStateProvider.select((value) => value.commandResponse)); + final bool isRecording = ref.watch(voiceAssistantStateProvider.select((value)=>value.isRecording)); + final bool isProcessing = ref.watch(voiceAssistantStateProvider.select((value)=>value.isCommandProcessing)); + + if (isRecording) { + _animationController.repeat(reverse: true); + } else { + _animationController.stop(); + } + + return Container( + height: MediaQuery.of(context).size.height * 0.35, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/VoiceAssistantBottomSheetBg.png'), + fit: BoxFit.cover, + ), + ), + child: Padding( + padding: const EdgeInsets.fromLTRB(30, 0, 40, 0), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if(!isRecording && !isProcessing) + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Text( + command ?? "No Command Detected", + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.white70, + fontSize: 43, + fontWeight: FontWeight.w400, + ), + ), + ), + SizedBox( + height: MediaQuery.of(context).size.height * 0.03 + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Text( + commandResponse ?? "No Response", + textAlign: TextAlign.center, + style: const TextStyle( + color: Color.fromRGBO(41, 95, 248, 1), + fontSize: 43, + fontWeight: FontWeight.w800, + ), + ), + ), + + SizedBox( + height: MediaQuery.of(context).size.height * 0.02, + ), + ], + ), + + if(isRecording) + Column( + children: [ + const Text("Listening...", + style: TextStyle( + color: Colors.white70, + fontSize: 43, + fontWeight: FontWeight.w400, + ), + ), + SizedBox( + height: MediaQuery.of(context).size.height*0.02, + ), + ScaleTransition( + scale: _pulseAnimation, // Apply the pulse animation here + child: SvgPicture.asset( + 'assets/VoiceControlButton.svg', + fit: BoxFit.cover, + semanticsLabel: 'Voice Assistant', + ), + ), + ], + ), + + if(!isRecording && isProcessing) + Column( + children: [ + const Text("Processing...", + style: TextStyle( + color: Colors.white70, + fontSize: 43, + fontWeight: FontWeight.w400, + ), + ), + SizedBox( + height: MediaQuery.of(context).size.height*0.05, + ), + Lottie.asset( + 'animations/LoadingAnimation.json', + fit: BoxFit.cover, + repeat: true, + ), + ], + ), + SizedBox( + height: MediaQuery.of(context).size.height * 0.035, + ), + ], + ), + ), + ); + }, + ); + }, + ).whenComplete(() { + ref.read(voiceAssistantStateProvider.notifier).updateCommandResponse(null); + ref.read(voiceAssistantStateProvider.notifier).updateCommand(null); + ref.read(voiceAssistantStateProvider.notifier).toggleShowOverlay(false); + overlayLock = 0; + }); + } + + @override + Widget build(BuildContext context) { + _showOverlay = ref.watch(voiceAssistantStateProvider.select((value) => value.showOverLay)); + + if(_showOverlay){ + WidgetsBinding.instance!.addPostFrameCallback((_) { + if(overlayLock == 0){ + overlayLock = 1; + _showAssistantPopup(context); + } + }); + } + else if(overlayLock == 1){ + overlayLock = 0; + Navigator.of(context).pop(); + } + + String svgPath = ref.watch(voiceAssistantStateProvider.select((value) => value.buttonPressed)) + ? 'assets/VoiceAssistantActive.svg' + : 'assets/VoiceAssistantEnabled.svg'; + + return Padding( + padding: const EdgeInsets.only(left: 8), + child: GestureDetector( + onTap: _onTap, + child: Container( + padding: EdgeInsets.zero, + child: SvgPicture.asset( + svgPath, + fit: BoxFit.cover, + semanticsLabel: 'Voice Assistant', + ), + ), + ), + ); + } +} diff --git a/lib/presentation/router/routes/routes.dart b/lib/presentation/router/routes/routes.dart index 328d495..24eab3a 100644 --- a/lib/presentation/router/routes/routes.dart +++ b/lib/presentation/router/routes/routes.dart @@ -3,6 +3,8 @@ import 'package:flutter_ics_homescreen/presentation/screens/settings/settings_sc import 'package:flutter_ics_homescreen/presentation/screens/settings/settings_screens/date_time/time/time_screen.dart'; import '../../../../export.dart'; +import '../../screens/settings/settings_screens/voice_assistant/voice_assistant_screen.dart'; +import '../../screens/settings/settings_screens/voice_assistant/widgets/stt_model/stt_model_screen.dart'; List<Page<dynamic>> onGenerateAppViewPages( AppState state, @@ -57,5 +59,9 @@ List<Page<dynamic>> onGenerateAppViewPages( return [TimePage.page()]; case AppState.year: return [SelectYearPage.page()]; + case AppState.voiceAssistant: + return [VoiceAssistantPage.page()]; + case AppState.sttModel: + return [STTModelPage.page()]; } } diff --git a/lib/presentation/screens/home/home.dart b/lib/presentation/screens/home/home.dart index 0ee52ac..6e3e119 100644 --- a/lib/presentation/screens/home/home.dart +++ b/lib/presentation/screens/home/home.dart @@ -1,4 +1,6 @@ import 'package:flutter_ics_homescreen/export.dart'; + +import '../../common_widget/voice_assistant_button.dart'; // import 'package:media_kit_video/media_kit_video.dart'; final bkgImageProvider = Provider((ref) { @@ -76,6 +78,15 @@ class HomeScreenState extends ConsumerState<HomeScreen> { height: 500, child: const VolumeFanControl()), ), + // Voice Assistant Button + if (appState != AppState.splash && ref.watch(voiceAssistantStateProvider.select((value)=>value.isVoiceAssistantEnable))) + Positioned( + top: MediaQuery.of(context).size.height * 0.82, + child: Container( + padding: const EdgeInsets.only(left: 8), + child: const VoiceAssistantButton() + ), + ), ], ), bottomNavigationBar: 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 index caf56a1..acc1541 100644 --- 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 @@ -16,7 +16,7 @@ class DateTimePage extends ConsumerWidget { body: Column( children: [ CommonTitle( - title: 'Date & Time', + title: 'Date & Time', hasBackButton: true, onPressed: () { context.flow<AppState>().update((state) => AppState.settings); diff --git a/lib/presentation/screens/settings/settings_screens/units/units_screen.dart b/lib/presentation/screens/settings/settings_screens/units/units_screen.dart index a49546f..ba84471 100644 --- a/lib/presentation/screens/settings/settings_screens/units/units_screen.dart +++ b/lib/presentation/screens/settings/settings_screens/units/units_screen.dart @@ -100,7 +100,6 @@ class UnitsTileState extends ConsumerState<UnitsTile> { children: [ Container( margin: const EdgeInsets.symmetric(vertical: 8), - padding: const EdgeInsets.symmetric(vertical: 15), decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.centerLeft, @@ -111,7 +110,7 @@ class UnitsTileState extends ConsumerState<UnitsTile> { //color: Color(0xFF0D113F), child: ListTile( contentPadding: - const EdgeInsets.symmetric(vertical: 17, horizontal: 24), + const EdgeInsets.symmetric(vertical: 32, horizontal: 24), leading: widget.icon != null ? Icon( widget.icon, 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 index fce1837..c5e571d 100644 --- 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 @@ -1,6 +1,32 @@ import 'package:flutter_ics_homescreen/export.dart'; class VersionInfoPage extends ConsumerWidget { + static String aglVersionFilePath = '/etc/os-release'; + static String kernelVersionFilePath = '/proc/version'; + + static String aglVersion() { + try { + final file = File(aglVersionFilePath); + final data = file.readAsStringSync().split("\n"); + if (!data[1].contains('Automotive Grade Linux')) { + throw 'Non-AGL distribution'; + } + return "AGL: " + data[2].split('"')[1]; + } catch (_) { + return '<AGL version not found>'; + } + } + + static String kernelVersion() { + try { + final file = File(kernelVersionFilePath); + final data = file.readAsStringSync().split(" "); + return "Kernel: " + data[2]; + } catch (_) { + return '<Kernel version not found>'; + } + } + const VersionInfoPage({super.key}); static Page<void> page() => @@ -43,7 +69,7 @@ class VersionInfoPage extends ConsumerWidget { child: ListTile( contentPadding: const EdgeInsets.only(top: 50, left: 25), leading: Text( - aglVeriosn, + aglVersion(), style: Theme.of(context).textTheme.titleMedium, ), ), @@ -64,7 +90,7 @@ class VersionInfoPage extends ConsumerWidget { contentPadding: const EdgeInsets.only(top: 50, left: 25), leading: Text( - kernelVeriosn, + kernelVersion(), style: Theme.of(context).textTheme.titleMedium, ), ), diff --git a/lib/presentation/screens/settings/settings_screens/voice_assistant/voice_assistant_screen.dart b/lib/presentation/screens/settings/settings_screens/voice_assistant/voice_assistant_screen.dart new file mode 100644 index 0000000..e1f38ae --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/voice_assistant/voice_assistant_screen.dart @@ -0,0 +1,28 @@ + +import 'package:flutter_ics_homescreen/export.dart'; +import 'widgets/voice_assistant_content.dart'; + +class VoiceAssistantPage extends ConsumerWidget{ + const VoiceAssistantPage({super.key}); + + static Page<void> page() => const MaterialPage<void>(child: VoiceAssistantPage()); + @override + Widget build(BuildContext context,WidgetRef ref) { + + return Scaffold( + body: Column( + children: [ + CommonTitle( + title: 'Voice Assistant', + hasBackButton: true, + onPressed: () { + ref.read(appProvider.notifier).back(); + }, + ), + Expanded(child: VoiceAssistantContent()), + ], + ), + ); + } +} + diff --git a/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/stt_model/stt_model_screen.dart b/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/stt_model/stt_model_screen.dart new file mode 100644 index 0000000..614763d --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/stt_model/stt_model_screen.dart @@ -0,0 +1,120 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +import '../../../../../../../data/models/voice_assistant_state.dart'; + +class STTModelPage extends ConsumerWidget { + const STTModelPage({super.key}); + + static Page<void> page() => + const MaterialPage<void>(child: STTModelPage()); + @override + Widget build(BuildContext context, WidgetRef ref) { + final SttModel sttModel = ref.watch(voiceAssistantStateProvider.select((value) => value.sttModel)); + + return Scaffold( + body: Column( + children: [ + CommonTitle( + title: 'Speech to Text Model', + hasBackButton: true, + onPressed: () { + context.flow<AppState>().update((state) => AppState.voiceAssistant); + }, + ), + 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: sttModel == SttModel.whisper + ? [0, 0.01, 0.8] + : [0.1, 1], + colors: sttModel == SttModel.whisper + ? <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( + 'Whisper AI', + style: Theme.of(context).textTheme.titleMedium, + ), + trailing: sttModel == SttModel.whisper + ? const Icon( + Icons.done, + color: AGLDemoColors.periwinkleColor, + size: 48, + ) + : null, + onTap: () { + ref + .read(voiceAssistantStateProvider.notifier) + .updateSttModel(SttModel.whisper); + }), + ), + const SizedBox( + height: 8, + ), + Container( + height: 130, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: sttModel == SttModel.vosk + ? [0, 0.01, 0.8] + : [0.1, 1], + colors: sttModel == SttModel.vosk + ? <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( + 'Vosk', + style: Theme.of(context).textTheme.titleMedium, + ), + //title: Text(widget.title), + //enabled: isSwitchOn, + trailing: sttModel == SttModel.vosk + ? const Icon( + Icons.done, + color: AGLDemoColors.periwinkleColor, + size: 48, + ) + : null, + + onTap: () { + ref + .read(voiceAssistantStateProvider.notifier) + .updateSttModel(SttModel.vosk); + }, + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_content.dart b/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_content.dart new file mode 100644 index 0000000..924a219 --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_content.dart @@ -0,0 +1,251 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +import 'package:flutter_ics_homescreen/export.dart'; +import 'package:flutter_ics_homescreen/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_tile.dart'; + +import '../../../../../../core/utils/helpers.dart'; +import '../../../../../../data/models/voice_assistant_state.dart'; + +@immutable +class VoiceAssistantContent extends ConsumerWidget { + VoiceAssistantContent({Key? key}) : super(key: key); + bool isWakeWordMode = false; + bool isVoiceAssistantOverlay = false; + bool isOnlineMode = false; + SttModel sttModel = SttModel.whisper; + + @override + Widget build(BuildContext context, WidgetRef ref) { + isWakeWordMode = + ref.watch(voiceAssistantStateProvider.select((value) => value.isWakeWordMode)); + isVoiceAssistantOverlay = + ref.watch(voiceAssistantStateProvider.select((value) => value.voiceAssistantOverlay)); + isOnlineMode = + ref.watch(voiceAssistantStateProvider.select((value) => value.isOnlineMode)); + sttModel = + ref.watch(voiceAssistantStateProvider.select((value) => value.sttModel)); + + final wakeWordCallback = () { + bool status = ref.read(voiceAssistantStateProvider.notifier).toggleWakeWordMode(); + if(status){ + var voiceAgentClient = ref.read(voiceAgentClientProvider); + voiceAgentClient.startWakeWordDetection(); + } + }; + + final voiceAssistantOverlayCallback = () { + ref.read(voiceAssistantStateProvider.notifier).toggleVoiceAssistantOverlay(); + }; + + final onlineModeCallback = () { + ref.read(voiceAssistantStateProvider.notifier).toggleOnlineMode(); + }; + + + return Column( + children: [ + Expanded( + child: ListView( + padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 144), + children: [ + VoiceAssistantTile( + icon: Icons.insert_comment_outlined, + title: "Voice Assistant Overlay", + hasSwitch: true, + voidCallback: voiceAssistantOverlayCallback, + isSwitchOn: isVoiceAssistantOverlay + ), + if(ref.watch(voiceAssistantStateProvider.select((value) => value.isOnlineModeAvailable))) + VoiceAssistantTile( + icon: Icons.cloud_circle, + title: "Online Mode", + hasSwitch: true, + voidCallback: onlineModeCallback, + isSwitchOn: isOnlineMode + ), + VoiceAssistantTile( + icon: Icons.mic_none_outlined, + title: "Wake Word Mode", + hasSwitch: true, + voidCallback: wakeWordCallback, + isSwitchOn: isWakeWordMode + ), + if(ref.watch(voiceAssistantStateProvider.select((value) => value.isWakeWordMode))) + WakeWordTile(), + SttTile( + title: " Speech To Text", + sttName: sttModel==SttModel.whisper ? "Whisper AI" : "Vosk", + hasSwich: true, + voidCallback: () async { + context + .flow<AppState>() + .update((next) => AppState.sttModel); + }), + ], + ) + ), + ], + ); + } +} + +class SttTile extends ConsumerStatefulWidget { + final IconData? icon; + final String title; + final String sttName; + final bool hasSwich; + final VoidCallback voidCallback; + final String? image; + const SttTile({ + Key? key, + this.icon, + required this.title, + required this.sttName, + required this.hasSwich, + required this.voidCallback, + this.image, + }) : super(key: key); + + @override + SttTileState createState() => SttTileState(); +} + +class SttTileState extends ConsumerState<SttTile> { + @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: Icon( + Icons.transcribe_outlined, + color: AGLDemoColors.periwinkleColor, + size: 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.sttName, + 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, + ) + ], + ); + } +} + + + +class WakeWordTile extends ConsumerStatefulWidget { + const WakeWordTile({Key? key}) : super(key: key); + + @override + WakeWordTileState createState() => WakeWordTileState(); +} + +class WakeWordTileState extends ConsumerState<WakeWordTile> { + @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: Icon( + Icons.mic_none_outlined, + color: AGLDemoColors.periwinkleColor, + size: 48, + ), + title: Text( + "Wake Word", + style: TextStyle( + color: AGLDemoColors.periwinkleColor, + shadows: [ + Helpers.dropShadowRegular, + ], + fontSize: 40), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + ref.watch(voiceAssistantStateProvider.select((value) => value.wakeWord)) ?? "Not Set", + style: TextStyle( + color: AGLDemoColors.periwinkleColor, + shadows: [ + Helpers.dropShadowRegular, + ], + fontSize: 40, + ), + ), + const SizedBox( + width: 50, + ), + + ], + ), + ), + ), + const SizedBox( + height: 8, + ) + ], + ); + } +} diff --git a/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_settings_list_tile.dart b/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_settings_list_tile.dart new file mode 100644 index 0000000..ee0365a --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_settings_list_tile.dart @@ -0,0 +1,111 @@ +import 'package:flutter_ics_homescreen/export.dart'; +import 'package:protos/val_api.dart'; + +class VoiceAssistantSettingsTile extends ConsumerStatefulWidget { + final IconData icon; + final String title; + final bool hasSwich; + final VoidCallback voidCallback; + const VoiceAssistantSettingsTile({ + Key? key, + required this.icon, + required this.title, + required this.hasSwich, + required this.voidCallback, + }) : super(key: key); + + @override + VoiceAssistantSettingsTileState createState() => VoiceAssistantSettingsTileState(); +} + +class VoiceAssistantSettingsTileState extends ConsumerState<VoiceAssistantSettingsTile> { + bool isSwitchOn = true; + @override + Widget build(BuildContext context) { + isSwitchOn = ref.watch(voiceAssistantStateProvider.select((voiceAssistant) => voiceAssistant.isVoiceAssistantEnable)); + 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( + 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) async { + var voiceAgentClient = ref.read(voiceAgentClientProvider); + ServiceStatus status = await voiceAgentClient.checkServiceStatus(); + ref.read(voiceAssistantStateProvider.notifier).toggleVoiceAssistant(status); + 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(), + ], + ), + ), + ) + ), + ), + const SizedBox( + height: 8, + ) + ], + ); + } +} diff --git a/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_tile.dart b/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_tile.dart new file mode 100644 index 0000000..d4bdd48 --- /dev/null +++ b/lib/presentation/screens/settings/settings_screens/voice_assistant/widgets/voice_assistant_tile.dart @@ -0,0 +1,102 @@ +import 'package:flutter_ics_homescreen/export.dart'; + +class VoiceAssistantTile extends ConsumerStatefulWidget { + final IconData icon; + final String title; + final bool hasSwitch; + final VoidCallback voidCallback; + final bool isSwitchOn; + const VoiceAssistantTile({super.key, required this.icon, required this.title, required this.hasSwitch, required this.voidCallback,required this.isSwitchOn}); + + @override + ConsumerState<VoiceAssistantTile> createState() => _VoiceAssistantTileState(); +} + +class _VoiceAssistantTileState extends ConsumerState<VoiceAssistantTile> { + bool isSwitchOn = true; + @override + Widget build(BuildContext context) { + isSwitchOn = widget.isSwitchOn; + return Column( + children: [ + 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( + + 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.hasSwitch + ? 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) { + setState(() { + isSwitchOn = value; + }); + widget.voidCallback(); + }, + inactiveTrackColor: Colors.transparent, + activeTrackColor: Colors.transparent, + thumbColor: + MaterialStateProperty.all<Color>( + AGLDemoColors.periwinkleColor)), + ), + ) + : const SizedBox(), + ], + ), + ), + ) + ), + const SizedBox( + height: 14, + ) + ], + ); + } +} + + diff --git a/lib/presentation/screens/settings/widgets/settings_content.dart b/lib/presentation/screens/settings/widgets/settings_content.dart index 6d0df50..b43c07b 100644 --- a/lib/presentation/screens/settings/widgets/settings_content.dart +++ b/lib/presentation/screens/settings/widgets/settings_content.dart @@ -1,6 +1,7 @@ import 'package:flutter_ics_homescreen/export.dart'; import '../../../custom_icons/custom_icons.dart'; +import '../settings_screens/voice_assistant/widgets/voice_assistant_settings_list_tile.dart'; class Settings extends ConsumerWidget { const Settings({ @@ -55,6 +56,15 @@ class Settings extends ConsumerWidget { voidCallback: () { ref.read(appProvider.notifier).update(AppState.audioSettings); }), + if(ref.watch(appConfigProvider).enableVoiceAssistant) + VoiceAssistantSettingsTile( + icon: Icons.keyboard_voice_outlined, + title: "Voice Assistant", + hasSwich: true, + voidCallback: (){ + ref.read(appProvider.notifier).update(AppState.voiceAssistant); + } + ), SettingsTile( icon: Icons.person_2_outlined, title: 'Profiles', |