diff --git a/lib/Screens/edit_note_page.dart b/lib/Screens/edit_note_page.dart index 31521db..6a72bcd 100644 --- a/lib/Screens/edit_note_page.dart +++ b/lib/Screens/edit_note_page.dart @@ -34,9 +34,10 @@ class _AddEditNotePageState extends State { @override Widget build(BuildContext context) => Scaffold( - backgroundColor: const Color(0xffffffff), + backgroundColor: const Color(0xff000000), appBar: AppBar( - backgroundColor: const Color(0xFF2f2554), + toolbarHeight: 75, + backgroundColor: const Color(0xff000000), actions: [buildButton()], ), body: Form( @@ -61,14 +62,21 @@ class _AddEditNotePageState extends State { return Padding( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), - child: FloatingActionButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + child: GestureDetector( + onTap: addOrUpdateNote, + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + border: Border.all(color: const Color(0x28ffffff)), + borderRadius: BorderRadius.circular(16), + color: const Color(0xff272727), + ), + child: const Icon( + Iconsax.book_saved, + size: 36, + color: Color(0xa1ffffff), + ), ), - backgroundColor: const Color(0xFFD1C4E9), - foregroundColor: const Color(0xFF512DA8), - onPressed: addOrUpdateNote, - child: const Icon(Iconsax.book_saved), ), ); } diff --git a/lib/Screens/note_detail_page.dart b/lib/Screens/note_detail_page.dart index a45ee8e..3962fd4 100644 --- a/lib/Screens/note_detail_page.dart +++ b/lib/Screens/note_detail_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:iconsax/iconsax.dart'; import 'package:intl/intl.dart'; import 'package:voicerra/db/notes_database.dart'; import 'package:voicerra/model/note.dart'; @@ -37,9 +38,9 @@ class _NoteDetailPageState extends State { @override Widget build(BuildContext context) => Scaffold( - backgroundColor: const Color(0xfff2f2f2), + backgroundColor: const Color(0xff000000), appBar: AppBar( - backgroundColor: const Color(0xFF2f2554), + backgroundColor: const Color(0xFF000000), centerTitle: true, actions: [editButton(), deleteButton()], ), @@ -47,35 +48,39 @@ class _NoteDetailPageState extends State { ? const Center(child: CircularProgressIndicator()) : Padding( padding: const EdgeInsets.all(12), - child: ListView( - padding: const EdgeInsets.symmetric(vertical: 8), - children: [ - Text( - note.title, - style: const TextStyle( - color: Color(0xff263238), - fontSize: 22, - fontWeight: FontWeight.bold, + child: SafeArea( + minimum: const EdgeInsets.only(left: 10), + child: ListView( + padding: const EdgeInsets.symmetric(vertical: 8), + children: [ + Text( + note.title, + style: const TextStyle( + color: Color(0xff808080), + fontSize: 25, + fontWeight: FontWeight.bold, + ), ), - ), - const SizedBox(height: 8), - Text( - DateFormat.yMMMd().format(note.createdTime), - style: const TextStyle(color: Colors.black26), - ), - const SizedBox(height: 8), - Text( - note.description, - style: const TextStyle( - color: Color(0xff263238), fontSize: 18), - ) - ], + const SizedBox(height: 25), + Text( + DateFormat.yMMMd().format(note.createdTime), + style: const TextStyle( + color: Color(0x81808080), fontSize: 15), + ), + const SizedBox(height: 28), + Text( + note.description, + style: const TextStyle( + color: Color(0xffffffff), fontSize: 20), + ) + ], + ), ), ), ); Widget editButton() => IconButton( - icon: const Icon(Icons.edit_outlined), + icon: const Icon(Iconsax.edit), onPressed: () async { if (isLoading) return; @@ -86,13 +91,16 @@ class _NoteDetailPageState extends State { refreshNote(); }); - Widget deleteButton() => IconButton( - icon: const Icon(Icons.delete), - onPressed: () async { - final navigator = Navigator.of(context); - await NotesDatabase.instance.delete(widget.noteId); + Widget deleteButton() => Padding( + padding: const EdgeInsets.only(right: 15.0, left: 5.0), + child: IconButton( + icon: const Icon(Iconsax.trash), + onPressed: () async { + final navigator = Navigator.of(context); + await NotesDatabase.instance.delete(widget.noteId); - navigator.pop(); - }, + navigator.pop(); + }, + ), ); } diff --git a/lib/Screens/notes_page.dart b/lib/Screens/notes_page.dart index eeadd5e..c14c799 100644 --- a/lib/Screens/notes_page.dart +++ b/lib/Screens/notes_page.dart @@ -4,6 +4,7 @@ import 'package:voicerra/db/notes_database.dart'; import 'package:voicerra/model/note.dart'; import 'package:voicerra/Screens/edit_note_page.dart'; import 'package:voicerra/Screens/note_detail_page.dart'; +import 'package:voicerra/widget/customappbar.dart'; import 'package:voicerra/widget/note_card_widget.dart'; import 'package:iconsax/iconsax.dart'; @@ -16,6 +17,29 @@ class NotesPage extends StatefulWidget { } class _NotesPageState extends State { + Widget buildNotes() => StaggeredGridView.countBuilder( + padding: const EdgeInsets.symmetric(vertical: 14.0, horizontal: 8.0), + itemCount: notes.length, + staggeredTileBuilder: (index) => const StaggeredTile.fit(2), + crossAxisCount: 4, + mainAxisSpacing: 4, + crossAxisSpacing: 4, + itemBuilder: (context, index) { + final note = notes[index]; + + return GestureDetector( + onTap: () async { + await Navigator.of(context).push(MaterialPageRoute( + builder: (context) => NoteDetailPage(noteId: note.id!), + )); + + refreshNotes(); + }, + child: NoteCardWidget(note: note, index: index), + ); + }, + ); + late List notes; bool isLoading = false; @@ -41,102 +65,72 @@ class _NotesPageState extends State { setState(() => isLoading = false); } + _add() async { + await Navigator.of(context).push( + MaterialPageRoute(builder: (context) => const AddEditNotePage()), + ); + + refreshNotes(); + } + @override Widget build(BuildContext context) => Scaffold( - backgroundColor: const Color(0xFF2f2554), - appBar: AppBar( - toolbarHeight: 80, - backgroundColor: const Color(0xFF2f2554), - elevation: 0, - centerTitle: true, - title: const Text( - 'Notes', - style: TextStyle( - fontSize: 24, fontFamily: 'Raleway', color: Colors.white), - ), - ), - body: SingleChildScrollView( - physics: const BouncingScrollPhysics( - parent: AlwaysScrollableScrollPhysics()), - child: Container( - height: MediaQuery.of(context).size.height, - width: MediaQuery.of(context).size.width, - decoration: const BoxDecoration( - color: Color(0xFFf2f2f2), - borderRadius: BorderRadius.only( - topRight: Radius.circular(40), - topLeft: Radius.circular(40), + backgroundColor: const Color(0xFF212025), + body: Stack( + children: [ + SingleChildScrollView( + physics: const BouncingScrollPhysics( + parent: AlwaysScrollableScrollPhysics()), + child: Center( + child: isLoading + ? const CircularProgressIndicator() + : notes.isEmpty + ? Container( + height: + MediaQuery.of(context).copyWith().size.height, + alignment: Alignment.center, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: const [ + Icon( + Iconsax.note_1, + color: Color(0x67ffffff), + size: 120, + ), + SizedBox( + height: 20, + ), + Text( + 'empty!', + style: TextStyle( + fontWeight: FontWeight.w300, + fontSize: 22, + color: Color(0x67ffffff)), + ), + ], + ), + ) + : SafeArea( + minimum: const EdgeInsets.only(top: 150), + child: Container( + height: MediaQuery.of(context) + .copyWith() + .size + .height, + alignment: Alignment.topCenter, + child: buildNotes()), + ), ), ), - child: Center( - child: isLoading - ? const CircularProgressIndicator() - : notes.isEmpty - ? Container( - alignment: Alignment.topCenter, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: const [ - SizedBox( - height: 150, - ), - Icon( - Iconsax.note_1, - size: 120, - ), - SizedBox( - height: 20, - ), - Text( - 'empty!', - style: TextStyle( - fontWeight: FontWeight.w300, fontSize: 22), - ), - ], - ), - ) - : buildNotes(), + SafeArea( + child: MyAppBar( + title: 'Notes', + onIconTap: _add, + iconName: Iconsax.add, + ), ), - ), - ), - floatingActionButton: FloatingActionButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - backgroundColor: Colors.blue.shade100, - foregroundColor: Colors.black38, - child: const Icon(Icons.add), - onPressed: () async { - await Navigator.of(context).push( - MaterialPageRoute(builder: (context) => const AddEditNotePage()), - ); - - refreshNotes(); - }, + ], ), ); - - Widget buildNotes() => StaggeredGridView.countBuilder( - padding: const EdgeInsets.symmetric(vertical: 14.0, horizontal: 8.0), - itemCount: notes.length, - staggeredTileBuilder: (index) => const StaggeredTile.fit(2), - crossAxisCount: 4, - mainAxisSpacing: 4, - crossAxisSpacing: 4, - itemBuilder: (context, index) { - final note = notes[index]; - - return GestureDetector( - onTap: () async { - await Navigator.of(context).push(MaterialPageRoute( - builder: (context) => NoteDetailPage(noteId: note.id!), - )); - - refreshNotes(); - }, - child: NoteCardWidget(note: note, index: index), - ); - }, - ); } diff --git a/lib/Screens/transcribe_page.dart b/lib/Screens/transcribe_page.dart index 2bb9466..ce6b0bd 100644 --- a/lib/Screens/transcribe_page.dart +++ b/lib/Screens/transcribe_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:google_speech/google_speech.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:loading_indicator/loading_indicator.dart'; @@ -7,6 +8,10 @@ import 'dart:io'; import 'dart:async'; import 'package:iconsax/iconsax.dart'; import 'package:file_picker/file_picker.dart'; +import 'package:sliding_up_panel/sliding_up_panel.dart'; +import 'package:voicerra/widget/BarIndicator.dart'; +import 'package:voicerra/widget/customappbar.dart'; +import 'package:clipboard/clipboard.dart'; class TranscribePage extends StatefulWidget { final String title; @@ -19,7 +24,7 @@ class TranscribePage extends StatefulWidget { class _TranscribePageState extends State { bool isTranscribing = false; - String content = ''; + String content = 'Select any audio from Storage'; File? file; void transcribe() async { @@ -76,127 +81,186 @@ class _TranscribePageState extends State { @override Widget build(BuildContext context) { + final panelHeightClosed = MediaQuery.of(context).size.height * 0.3; + final panelHeightOpen = MediaQuery.of(context).size.height * 0.7; return Scaffold( - backgroundColor: const Color(0xFF2f2554), - appBar: AppBar( - toolbarHeight: 80, - backgroundColor: const Color(0xFF2f2554), - elevation: 0, - centerTitle: true, - title: const Text('Transcribe Your Audio', - style: TextStyle( - fontFamily: 'Raleway', fontSize: 24.0, color: Colors.white)), - ), - body: SingleChildScrollView( - child: Container( - height: MediaQuery.of(context).size.height, - width: MediaQuery.of(context).size.width, - decoration: const BoxDecoration( - color: Color(0xFFf2f2f2), - borderRadius: BorderRadius.only( - topRight: Radius.circular(50), - topLeft: Radius.circular(50), + backgroundColor: const Color(0xff1c1c1e), + body: SafeArea( + child: Stack( + children: [ + MyAppBar( + title: 'Online Transcriber', + onIconTap: _copy, + iconName: Iconsax.copy, ), - ), - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const SizedBox( - height: 50, - ), - Container( - height: 200, - width: 350, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), + SlidingUpPanel( + minHeight: panelHeightClosed, + maxHeight: panelHeightOpen, + backdropEnabled: true, //darken background if panel is open + parallaxEnabled: true, + color: Colors.transparent, + panel: Container( + decoration: const BoxDecoration( + // background color of panel + color: Color(0xFF212025), + // rounded corners of panel + borderRadius: BorderRadius.only( + topLeft: Radius.circular(24.0), + topRight: Radius.circular(24.0), ), - padding: const EdgeInsets.all(20.0), - child: content == '' - ? const Center( - child: Text( - 'Your text will appear here', - style: TextStyle(color: Colors.grey), - ), - ) - : Center( - child: Text( - content, - style: const TextStyle(fontSize: 20), - ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const BarIndicator(), + const Text( + 'Transcribed Text', + style: TextStyle( + fontSize: 24.0, + fontFamily: 'Raleway', + color: Color(0xa3ffffff), + fontWeight: FontWeight.w400, + ), + ), + const SizedBox( + height: 20, + ), + Padding( + padding: const EdgeInsets.all(10.0), + child: Text( + content, + textAlign: TextAlign.center, + style: const TextStyle( + fontFamily: 'Raleway', + fontSize: 24.0, + color: Color(0xd8ffffff), + fontWeight: FontWeight.w400, ), + ), + ), + ], ), - const SizedBox( - height: 10, + ), + collapsed: Container( + decoration: const BoxDecoration( + color: Color(0xFF212025), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(24.0), + topRight: Radius.circular(24.0), + ), ), - //Transcribe button - Container( - child: isTranscribing - ? const Padding( - padding: EdgeInsets.symmetric( - horizontal: 150, vertical: 70), - child: SizedBox( - child: LoadingIndicator( - indicatorType: Indicator.lineScalePulseOutRapid, - colors: [ - Colors.red, - Color(0xFF2f2554), - Color(0xFFD1C4E9), - ], + child: Column( + children: [ + const BarIndicator(), + const Center( + child: Text( + "Swipe Up for more", + style: TextStyle( + color: Color(0xa3ffffff), fontFamily: 'Raleway'), + ), + ), + const SizedBox( + height: 20, + ), + Padding( + padding: const EdgeInsets.all(10.0), + child: Text( + content, + textAlign: TextAlign.center, + style: const TextStyle( + fontFamily: 'Raleway', + fontSize: 24.0, + color: Color(0xd8ffffff), + fontWeight: FontWeight.w400, + ), + ), + ), + ], + ), + ), + body: Column( + children: [ + const SizedBox( + height: 200, + ), + Container( + child: isTranscribing + ? const Padding( + padding: EdgeInsets.symmetric( + horizontal: 150, vertical: 70), + child: SizedBox( + child: LoadingIndicator( + indicatorType: Indicator.lineScalePulseOutRapid, + colors: [ + Color(0xffcabde4), + Colors.deepPurpleAccent, + Color(0xFFFF0005), + ], + ), ), - ), - ) - : Padding( - padding: const EdgeInsets.all(8.0), - child: TextButton( - onPressed: isTranscribing ? () {} : transcribe, - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 25, horizontal: 15), - child: Card( - color: const Color(0xFFD1C4E9), - child: Padding( - padding: const EdgeInsets.all(15), - child: Row( - children: const [ - SizedBox( - width: 35.0, - ), - Icon( - Iconsax.folder, - size: 30, - color: Color(0xFF512DA8), - ), - SizedBox( - width: 25.0, - ), - Center( - child: Text( - 'Pick an audio file', - textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'Raleway', - fontWeight: FontWeight.bold, - color: Color(0xFF512DA8), - fontSize: 17.0, - letterSpacing: 2, + ) + : Padding( + padding: const EdgeInsets.all(8.0), + child: TextButton( + onPressed: isTranscribing ? () {} : transcribe, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 25, horizontal: 15), + child: Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + ), + color: const Color(0x1acabde4), + child: Padding( + padding: const EdgeInsets.all(15), + child: Row( + children: const [ + SizedBox( + width: 35.0, + ), + Icon( + Iconsax.folder, + size: 30, + color: Color(0xFF2D50A8), + ), + SizedBox( + width: 25.0, + ), + Center( + child: Text( + 'Pick an audio file', + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: 'Raleway', + fontWeight: FontWeight.bold, + color: Color(0xFF2D50A8), + fontSize: 17.0, + letterSpacing: 2, + ), ), ), - ), - ], + ], + ), ), ), ), ), ), - ), - ), - ], + ), + ], + ), ), - ), + ], ), ), ); } + + void _copy() async { + await FlutterClipboard.copy(content); + Fluttertoast.showToast( + msg: "✓ Copied to Clipboard", + toastLength: Toast.LENGTH_SHORT, + ); + } } diff --git a/lib/Screens/voice.dart b/lib/Screens/voice.dart index a41c54d..35d4713 100644 --- a/lib/Screens/voice.dart +++ b/lib/Screens/voice.dart @@ -1,68 +1,28 @@ import 'package:flutter/material.dart'; -import 'package:highlight_text/highlight_text.dart'; +import 'package:voicerra/Screens/about_page.dart'; +import 'package:voicerra/Screens/translate.dart'; +import 'package:voicerra/Screens/voice_beta.dart'; +import 'package:voicerra/widget/BarIndicator.dart'; +import 'package:voicerra/widget/customappbar.dart'; +import 'package:sliding_up_panel/sliding_up_panel.dart'; import 'package:speech_to_text/speech_to_text.dart' as stt; -import 'package:clipboard/clipboard.dart'; +import 'package:flutter/services.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:iconsax/iconsax.dart'; -import 'package:voicerra/Screens/about_page.dart'; -import 'package:voicerra/Screens/voice_beta.dart'; +import 'package:voicerra/widget/glass.dart'; +import 'package:voicerra/widget/more_options_card_widget.dart'; -class VoiceApp extends StatelessWidget { +class VoiceApp extends StatefulWidget { const VoiceApp({Key? key}) : super(key: key); @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Voicerra', - theme: ThemeData( - primarySwatch: Colors.blue, - elevatedButtonTheme: ElevatedButtonThemeData( - style: ElevatedButton.styleFrom( - elevation: 12, - minimumSize: const Size.square(52), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - )))), - home: const SpeechScreen(), - ); - } + State createState() => _VoiceAppState(); } -class SpeechScreen extends StatefulWidget { - const SpeechScreen({super.key}); - - @override - State createState() => _SpeechScreenState(); -} - -class _SpeechScreenState extends State { - final Map _highlights = { - 'project': HighlightedWord( - textStyle: const TextStyle( - color: Colors.blue, - fontWeight: FontWeight.bold, - fontFamily: 'Raleway', - fontSize: 24.0), - ), - 'exhibition': HighlightedWord( - textStyle: const TextStyle( - color: Colors.green, - fontWeight: FontWeight.bold, - fontFamily: 'Raleway', - fontSize: 24.0), - ), - 'group': HighlightedWord( - textStyle: const TextStyle( - color: Colors.red, - fontWeight: FontWeight.bold, - fontFamily: 'Raleway', - fontSize: 24.0), - ), - }; - +class _VoiceAppState extends State { late stt.SpeechToText _speech; bool _isListening = false; - String _text = 'Voicerra!'; + String _text = 'Press the mic icon to start'; double _confidence = 1.0; @override @@ -73,122 +33,191 @@ class _SpeechScreenState extends State { @override Widget build(BuildContext context) { + final panelHeightClosed = MediaQuery.of(context).size.height * 0.3; + final panelHeightOpen = MediaQuery.of(context).size.height * 0.7; return Scaffold( - backgroundColor: const Color(0xFF2f2554), - appBar: AppBar( - toolbarHeight: 80, - backgroundColor: const Color(0xFF2f2554), - elevation: 0, - centerTitle: true, - title: Text( - 'Confidence: ${(_confidence * 100.0).toStringAsFixed(1)}%', - style: const TextStyle(fontFamily: 'Raleway', fontSize: 24.0)), - actions: [ - PopupMenuButton( - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all( - Radius.circular(20.0), - ), - ), - onSelected: (value) { - switch (value) { - case 'About us': - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const AboutPage()), - ); - break; - case 'Try (Beta)': - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const BetaVoice()), - ); - break; - } - }, - itemBuilder: (BuildContext context) { - return ['About us', 'Try (Beta)'].map((String choice) { - return PopupMenuItem( - value: choice, - child: Center( - child: Text( - choice, - style: const TextStyle(fontFamily: 'Raleway'), - )), - ); - }).toList(); - }, + backgroundColor: const Color(0xff1c1c1e), + body: SafeArea( + child: Stack( + children: [ + MyAppBar( + title: 'Voicerra', + onIconTap: _listen, + iconName: Iconsax.microphone, ), - ], - ), - floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, - floatingActionButton: Padding( - padding: const EdgeInsets.all(16.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - ElevatedButton( - style: ButtonStyle( - backgroundColor: - MaterialStateProperty.all(Colors.blue.shade100), - foregroundColor: - MaterialStateProperty.all(Colors.black), - overlayColor: getColor( - const Color(0xFFf6edfd), - const Color(0xFF2f2554), - )), - onPressed: _listen, - child: Icon(_isListening ? Icons.mic : Icons.mic_off), - ), - Expanded(child: Container()), - FloatingActionButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + SlidingUpPanel( + minHeight: panelHeightClosed, + maxHeight: panelHeightOpen, + backdropEnabled: true, //darken background if panel is open + parallaxEnabled: true, + color: Colors.transparent, + panel: Container( + decoration: const BoxDecoration( + // background color of panel + color: Color(0xFF212025), + // rounded corners of panel + borderRadius: BorderRadius.only( + topLeft: Radius.circular(24.0), + topRight: Radius.circular(24.0), + ), + ), + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const BarIndicator(), + Padding( + padding: const EdgeInsets.only(left: 20, right: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: MediaQuery.of(context).size.width / 4, + ), + const Text( + 'Transcribed Text', + style: TextStyle( + fontSize: 24.0, + fontFamily: 'Raleway', + color: Color(0xa3ffffff), + fontWeight: FontWeight.w400, + ), + ), + ], + ), + GestureDetector( + onTap: _copy, + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + border: Border.all( + color: const Color(0x28ffffff)), + borderRadius: BorderRadius.circular(16), + color: const Color(0xff272727), + ), + child: const Icon( + Iconsax.copy, + size: 20, + color: Color(0xa1ffffff), + ), + ), + ), + ], + ), + ), + const SizedBox( + height: 20, + ), + Padding( + padding: const EdgeInsets.all(10.0), + child: TextSelectionTheme( + data: const TextSelectionThemeData( + selectionColor: Colors.black, + selectionHandleColor: Colors.black), + child: SelectableText( + textAlign: TextAlign.center, + _text, + style: const TextStyle( + fontFamily: 'Raleway', + fontSize: 24.0, + color: Color(0xd8ffffff), + fontWeight: FontWeight.w400, + ), + ), + ), + ), + ], + ), ), - backgroundColor: Colors.blue.shade100, - foregroundColor: Colors.black, - onPressed: () async { - await FlutterClipboard.copy(_text); - Fluttertoast.showToast( - msg: "✓ Copied to Clipboard", - toastLength: Toast.LENGTH_SHORT, - ); - }, - child: const Icon(Iconsax.copy), - ), - ], - ), - ), - body: SingleChildScrollView( - physics: const BouncingScrollPhysics( - parent: AlwaysScrollableScrollPhysics()), - child: Container( - height: MediaQuery.of(context).size.height, - width: MediaQuery.of(context).size.width, - decoration: const BoxDecoration( - color: Color(0xFFf2f2f2), - borderRadius: BorderRadius.only( - topRight: Radius.circular(50), - topLeft: Radius.circular(50), ), - ), - child: Container( - padding: const EdgeInsets.fromLTRB(30.0, 30.0, 30.0, 150.0), - child: TextHighlight( - text: _text, - words: _highlights, - textStyle: const TextStyle( - fontFamily: 'Raleway', - fontSize: 24.0, - color: Colors.black, - fontWeight: FontWeight.w400, + collapsed: Container( + decoration: const BoxDecoration( + color: Color(0xFF212025), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(24.0), + topRight: Radius.circular(24.0), + ), ), + child: Column( + children: [ + const BarIndicator(), + const Center( + child: Text( + "Swipe Up for more", + style: TextStyle( + color: Color(0xa3ffffff), fontFamily: 'Raleway'), + ), + ), + const SizedBox( + height: 44, + ), + Padding( + padding: const EdgeInsets.all(10.0), + child: TextSelectionTheme( + data: const TextSelectionThemeData( + selectionColor: Colors.black, + selectionHandleColor: Colors.black), + child: SelectableText( + textAlign: TextAlign.center, + _text, + style: const TextStyle( + fontFamily: 'Raleway', + fontSize: 24.0, + color: Color(0xd8ffffff), + fontWeight: FontWeight.w400, + ), + ), + ), + ), + ], + ), + ), + body: Column( + children: [ + const SizedBox( + height: 130, + ), + SizedBox( + height: 60, + child: ListView( + shrinkWrap: true, + physics: const BouncingScrollPhysics(), + scrollDirection: Axis.horizontal, + children: const [ + OptionsCard( + cardTitle: 'Try Beta', + iconName: Iconsax.microphone, + pageName: BetaVoice(), + ), + OptionsCard( + cardTitle: 'Translate', + iconName: Icons.translate, + pageName: TranslatePage(), + ), + OptionsCard( + cardTitle: 'About', + iconName: Icons.person_outline_sharp, + pageName: AboutPage(), + ), + ], + ), + ), + const SizedBox(height: 40), + Glass(listening: _isListening, confidence: _confidence), + const Spacer( + flex: 3, + ), + ], ), ), - ), - )); + ], + ), + ), + ); } void _listen() async { @@ -216,15 +245,11 @@ class _SpeechScreenState extends State { } } - getColor(Color color, Color colorPressed) { - getColor(Set states) { - if (states.contains(MaterialState.pressed)) { - return colorPressed; - } else { - return color; - } - } - - return MaterialStateProperty.resolveWith(getColor); + void _copy() async { + await Clipboard.setData(ClipboardData(text: _text)); + Fluttertoast.showToast( + msg: "✓ Copied to Clipboard", + toastLength: Toast.LENGTH_SHORT, + ); } } diff --git a/lib/Screens/voice_recorder_page.dart b/lib/Screens/voice_recorder_page.dart index 690ae69..1df3bb2 100644 --- a/lib/Screens/voice_recorder_page.dart +++ b/lib/Screens/voice_recorder_page.dart @@ -1,9 +1,11 @@ import 'dart:async'; import 'dart:io'; import 'package:auto_size_text/auto_size_text.dart'; +import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_vibrate/flutter_vibrate.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; import 'package:loading_indicator/loading_indicator.dart'; import 'package:logger/logger.dart'; @@ -11,9 +13,10 @@ import 'package:share_plus/share_plus.dart'; import 'package:sliding_sheet/sliding_sheet.dart'; import 'package:flutter_sound/flutter_sound.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:sliding_up_panel/sliding_up_panel.dart'; import 'package:stop_watch_timer/stop_watch_timer.dart'; import 'package:voicerra/utils/utils.dart'; -import 'package:voicerra/widget/mini_player.dart'; +import 'package:voicerra/widget/BarIndicator.dart'; import 'package:wakelock/wakelock.dart'; class RecPage extends StatefulWidget { @@ -27,15 +30,10 @@ typedef Fn = void Function(); class _RecPageState extends State with SingleTickerProviderStateMixin, WidgetsBindingObserver { - static const List tabs = [ - Tab(text: 'Record'), - Tab(text: 'Play'), - ]; - FlutterSoundPlayer? audioPlayer = FlutterSoundPlayer(); FlutterSoundRecorder? _mRecorder = FlutterSoundRecorder(); final StopWatchTimer _stopWatchTimer = StopWatchTimer(); - late TabController _tabController; + final Codec _codec = Codec.pcm16WAV; final String _fileExtension = 'wav'; Duration duration = const Duration(); @@ -46,7 +44,6 @@ class _RecPageState extends State @override void initState() { super.initState(); - _tabController = TabController(length: tabs.length, vsync: this); openTheRecorder(); audioPlayer!.openPlayer(); @@ -63,7 +60,6 @@ class _RecPageState extends State _mRecorder!.closeRecorder(); _mRecorder = null; - _tabController.dispose(); super.dispose(); } @@ -203,23 +199,25 @@ class _RecPageState extends State @override Widget build(BuildContext context) { void saveAudioBottomSheet() async { + //TO SAVE AUDIO USING BOTTOM SHEET TextEditingController recordingTitle = TextEditingController(); String? selectedCategory; await showSlidingBottomSheet( context, builder: (BuildContext context) { + //record bottom sheet builder return SlidingSheetDialog( elevation: 8, cornerRadius: 15, - color: const Color(0xFFF2F2F2), + color: const Color(0xFF212025), builder: (context, state) { return Material( child: StatefulBuilder( builder: (BuildContext context, void Function(void Function()) setState) { return Container( - color: const Color(0xFFF2F2F2), + color: const Color(0xFF212025), padding: const EdgeInsets.only( left: 24, right: 24, top: 30, bottom: 30), child: Column( @@ -228,15 +226,18 @@ class _RecPageState extends State const Padding( padding: EdgeInsets.only(bottom: 12.0), child: Text( - 'Save Recording', + 'Save Recording', //record bottom sheet title style: TextStyle( fontFamily: 'Raleway', fontSize: 20.0, - color: Color(0xFF23262F), + color: Color(0xa3ffffff), fontWeight: FontWeight.w500, ), ), ), + const SizedBox( + height: 5, + ), TextField( controller: recordingTitle, style: const TextStyle( @@ -252,7 +253,8 @@ class _RecPageState extends State ], textCapitalization: TextCapitalization.sentences, decoration: const InputDecoration( - labelText: 'Name the recording', + labelText: + 'Name the recording', //record bottom sheet hint text ), ), ], @@ -263,8 +265,9 @@ class _RecPageState extends State ); }, footerBuilder: (context, state) { + //SAVE OR CANCEL BOTTOM SHEET BUTTONS return Container( - color: const Color(0xFFF2F2F2), + color: const Color(0xFF212025), child: Padding( padding: const EdgeInsets.only( left: 24.0, right: 24.0, bottom: 24.0), @@ -278,18 +281,18 @@ class _RecPageState extends State child: ElevatedButton( onPressed: () => Navigator.pop(context), style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFFbbdefb), + backgroundColor: const Color(0xFF2f2554), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), side: BorderSide.none, ), ), child: const Text( - 'Cancel', + 'Cancel', //cancel button of record bottom sheet style: TextStyle( fontFamily: 'Raleway', fontSize: 16.0, - color: Color(0xFF969AA0), + color: Colors.white, fontWeight: FontWeight.w500, ), ), @@ -331,7 +334,7 @@ class _RecPageState extends State .colorScheme ?.primary)), child: Text( - 'Save', + 'Save', //save button of record bottom sheet style: Theme.of(context) .textTheme .bodyText1! @@ -357,25 +360,27 @@ class _RecPageState extends State } void rename(File file) async { + //rename function String path = file.path; TextEditingController recordingTitle = TextEditingController(); String? selectedCategory; await showSlidingBottomSheet( + //rename bottom sheet builder context, builder: (BuildContext context) { return SlidingSheetDialog( elevation: 8, cornerRadius: 15, - color: const Color(0xFFF2F2F2), + color: Colors.transparent, //rename bottom sheet color builder: (context, state) { return Material( child: StatefulBuilder( builder: (BuildContext context, void Function(void Function()) setState) { return Container( - color: const Color(0xFFF2F2F2), + color: const Color(0xFF212025), padding: const EdgeInsets.only( left: 24, right: 24, top: 30, bottom: 30), child: Column( @@ -384,13 +389,16 @@ class _RecPageState extends State Padding( padding: const EdgeInsets.only(bottom: 12.0), child: Text( - 'Rename', + 'Rename', //rename bottom sheet title style: Theme.of(context) .textTheme .headline2! .copyWith(fontSize: 20), ), ), + const SizedBox( + height: 5, + ), TextField( controller: recordingTitle, style: Theme.of(context) @@ -404,7 +412,8 @@ class _RecPageState extends State ], textCapitalization: TextCapitalization.sentences, decoration: const InputDecoration( - labelText: 'New Name', + labelText: + 'New Name', //rename bottom sheet hint text ), ), ], @@ -416,7 +425,8 @@ class _RecPageState extends State }, footerBuilder: (context, state) { return Container( - color: const Color(0xFFF2F2F2), + //bottom Save or Cancel buttons of record bottom sheet + color: const Color(0xFF212025), child: Padding( padding: const EdgeInsets.only( left: 24.0, right: 24.0, bottom: 24.0), @@ -440,7 +450,7 @@ class _RecPageState extends State .colorScheme ?.secondary)), child: Text( - 'Cancel', + 'Cancel', //cancel button of rename bottom sheet style: Theme.of(context) .textTheme .bodyText1! @@ -456,6 +466,7 @@ class _RecPageState extends State child: SizedBox( height: 40, child: ElevatedButton( + //save button for rename onPressed: () async { String now = DateFormat.yMMMMd().format(DateTime.now()); @@ -491,7 +502,7 @@ class _RecPageState extends State .colorScheme ?.primary)), child: Text( - 'Save', + 'Save', //save of rename bottom sheet style: Theme.of(context) .textTheme .bodyText1! @@ -521,8 +532,9 @@ class _RecPageState extends State context: context, builder: (BuildContext context) { return AlertDialog( + //popup dialogue fo delete option elevation: 8, - backgroundColor: const Color(0xFFF2F2F2), + backgroundColor: const Color(0xFF212025), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), ), @@ -549,7 +561,7 @@ class _RecPageState extends State .colorScheme ?.secondary)), child: Text( - 'No', + 'No', //No button for delete popup dialogue style: Theme.of(context) .textTheme .bodyText1! @@ -579,7 +591,7 @@ class _RecPageState extends State .colorScheme ?.primary)), child: Text( - 'Yes', + 'Yes', // yes button for delete popup dialogue style: Theme.of(context).textTheme.bodyText1!.copyWith( fontSize: 16, color: Theme.of(context) @@ -596,293 +608,624 @@ class _RecPageState extends State ); } + final panelHeightClosed = MediaQuery.of(context).size.height * 0.3; + final panelHeightOpen = MediaQuery.of(context).size.height * 0.7; return Scaffold( - appBar: AppBar( - backgroundColor: const Color(0xFF2f2554), - elevation: 0.0, - actionsIconTheme: const IconThemeData(color: Color(0xFF323232)), - centerTitle: true, - toolbarHeight: 90, - title: const Text('Record Notes', - style: TextStyle( - fontFamily: 'Raleway', fontSize: 24.0, color: Colors.white)), - bottom: TabBar( - tabs: const [Tab(text: 'Record'), Tab(text: 'Play')], - controller: _tabController, - ), - ), - body: TabBarView( - physics: const BouncingScrollPhysics( - parent: AlwaysScrollableScrollPhysics()), - controller: _tabController, - children: [ - Column( - children: [ - const SizedBox(height: 40), - StreamBuilder( - stream: _stopWatchTimer.rawTime, - initialData: 0, - builder: (context, snap) { - final value = snap.data; - final displayTime = StopWatchTimer.getDisplayTime(value!); - return Text( - displayTime, - style: Theme.of(context) - .textTheme - .headline1! - .copyWith(fontSize: 38), - ); - }, + //scaffold of the recording page + backgroundColor: const Color(0xff1c1c1e), + body: SafeArea( + child: Stack( + children: [ + Padding( + padding: const EdgeInsets.all(25.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Text( + 'Recorder', + style: GoogleFonts.bebasNeue( + fontSize: 52, + color: Colors.white, + ), + ), + ), + ), + Container( + height: 62, + width: 62, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + border: Border.all(color: const Color(0x28ffffff)), + borderRadius: BorderRadius.circular(16), + color: const Color(0xff272727), + ), + child: Container( + child: _mRecorder!.isRecording + ? const LoadingIndicator( + indicatorType: Indicator.lineScalePulseOutRapid, + colors: [ + Color(0xffcabde4), + Colors.deepPurpleAccent, + Color(0xFFFF0005), + ], + ) + : const Icon( + FluentIcons.record_12_regular, + color: Color(0xa1ffffff), + size: 36, + )), + ), + ], ), - Padding( - padding: const EdgeInsets.only(bottom: 40.0), - child: Text( - 'High Quality', - style: Theme.of(context).textTheme.subtitle1, + ), + SlidingUpPanel( + minHeight: panelHeightClosed, + maxHeight: panelHeightOpen, + backdropEnabled: true, //darken background if panel is open + parallaxEnabled: true, + color: Colors.transparent, + panel: Container( + decoration: const BoxDecoration( + // background color of panel + color: Color(0xFF212025), + // rounded corners of panel + borderRadius: BorderRadius.only( + topLeft: Radius.circular(24.0), + topRight: Radius.circular(24.0), + ), ), - ), - const Spacer(), - Container( - child: _mRecorder!.isRecording - ? const Padding( - padding: EdgeInsets.symmetric( - horizontal: 150, vertical: 70), - child: SizedBox( - child: LoadingIndicator( - indicatorType: Indicator.lineScalePulseOutRapid, - colors: [ - Colors.red, - Color(0xFF2f2554), - Color(0xFFD1C4E9), - ], - ), - ), - ) - : Container( - height: 170, - color: const Color(0xFFf2f2f2), - )), - const Spacer(), - Padding( - padding: const EdgeInsets.only( - bottom: 24.0, left: 24.0, right: 24.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Visibility( - visible: _mRecorder!.isPaused ? true : false, - maintainSize: true, - maintainAnimation: true, - maintainState: true, - child: IconButton( - onPressed: () => cancelRecord(), - icon: const Icon(Icons.close), + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const BarIndicator(), + const Text( + 'Recordings', + style: TextStyle( + fontSize: 24.0, + fontFamily: 'Raleway', + color: Color(0xa3ffffff), + fontWeight: FontWeight.w400, + ), ), - ), - SizedBox( - width: 56, - height: 56, - child: ElevatedButton( - onPressed: getRecorderFn(), - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - _mRecorder!.isRecording - ? const Color(0xFFFF5656) - : Colors.white), - shape: - MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(50), - side: const BorderSide( - color: Color(0xFF2f2554), width: 8), - ), + const SizedBox( + height: 5, + ), + Padding( + padding: const EdgeInsets.all(10.0), + child: Padding( + padding: + const EdgeInsets.only(top: 10.0, bottom: 15.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FutureBuilder( + future: getDirectory(), + builder: (BuildContext context, + AsyncSnapshot snapshot) { + if (snapshot.hasData) { + Directory dir = snapshot.data; + List audiosFiles = dir + .listSync( + recursive: true, followLinks: false) + .map((file) { + if (file.statSync().type == + FileSystemEntityType.file) { + return file; + } + }).toList(); + + audiosFiles.removeWhere( + (element) => element == null); + + if (audiosFiles.isEmpty) { + return Center( + child: Text( + 'No recordings', // show when there are no recordings saved + style: Theme.of(context) + .textTheme + .bodyText1, + ), + ); + } + + return ListView.builder( + //builds for sliding panel is open + //list view to display the recordings in a list + shrinkWrap: true, + physics: + const BouncingScrollPhysics(), //bouncing physics + itemCount: audiosFiles.length, + itemBuilder: + (BuildContext context, int index) { + File audioFile = + File(audiosFiles[index]!.path); + + DateFormat dayFormat = DateFormat.yMd(); + DateFormat timeFormat = DateFormat.Hm(); + String fileSize = getFileSize( + audioFile.lengthSync(), 1); + DateTime createdAt = + audioFile.lastModifiedSync(); + String createdAtFormatted = ''; + String fileName = audioFile.name ?? + 'Recording'; //checks if file name starts with recording + + if (createdAt.isToday()) { + createdAtFormatted = + timeFormat.format(createdAt); + } else { + createdAtFormatted = + dayFormat.format(createdAt); + } + + return Padding( + padding: + const EdgeInsets.only(top: 10.0), + child: Container( + padding: const EdgeInsets.all(10), + decoration: const BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular(15)), + color: Color(0x94181818), + ), + child: Padding( + padding: const EdgeInsets.only( + bottom: 5, top: 5), + child: ListTile( + onTap: () {}, + title: Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + Expanded( + child: AutoSizeText( + fileName, //name of the file formatting inside the list of recordings + maxLines: 1, + style: Theme.of(context) + .textTheme + .headline2, + ), + ), + Text( + createdAtFormatted, + style: Theme.of(context) + .textTheme + .subtitle2, + ) + ], + ), + subtitle: Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + if (isPlaying(index)) + Text( + //duration of the recording + durationFormat( + duration), + style: Theme.of(context) + .textTheme + .subtitle2, + ), + Text( + fileSize, //file size of the recording as a subtitle in the list widget of the recorder + style: Theme.of(context) + .textTheme + .subtitle2, + ) + ], + ), + trailing: PopupMenuButton( + //rename share delete buttons inside a popup menu + onSelected: (value) { + switch (value) { + case 'Share': + Share.shareFiles( + [audioFile.path], + text: fileName); + break; + case 'Rename': + rename(audioFile); + break; + case 'Delete': + delete(audioFile); + break; + } + }, + itemBuilder: + (BuildContext context) { + //item builder of the popup button + return [ + 'Share', + 'Rename', + 'Delete' + ].map((String choice) { + return PopupMenuItem( + value: choice, + child: Text(choice), + ); + }).toList(); + }, + ), + leading: CircleAvatar( + //play pause circle avatar for leading icon + radius: 35, + backgroundColor: + const Color(0xffcabde4), + child: IconButton( + padding: EdgeInsets.zero, + onPressed: () async { + if ((audioPlayer! + .isPlaying)) { + pauseAudio(); + setState(() {}); + } else { + playAudio( + audioFile.path, + index); + } + }, + icon: Icon( + isPlaying(index) + ? Icons.pause + : Icons.play_arrow, + color: const Color( + 0xFF323232), + ), + iconSize: 30, + ), + ), + ), + ), + ), + ); + }, + ); + } + + return const Center( + child: CircularProgressIndicator(), + ); + }, + ) + ], ), ), - child: _mRecorder!.isRecording - ? const Icon(Icons.pause) - : null, ), - ), - Visibility( - visible: _mRecorder!.isPaused ? true : false, - maintainSize: true, - maintainAnimation: true, - maintainState: true, - child: SizedBox( - width: 40, - height: 40, - child: IconButton( - onPressed: () => saveAudioBottomSheet(), - icon: const Icon(Icons.check), - ), + ], + ), + ), + ), + collapsed: Container( + decoration: const BoxDecoration( + color: Color(0xFF212025), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(24.0), + topRight: Radius.circular(24.0), + ), + ), + child: Column( + children: [ + const BarIndicator(), + const Center( + child: Text( + "Swipe Up for more", + style: TextStyle( + color: Color(0xa3ffffff), fontFamily: 'Raleway'), ), ), - ], - ), - ) - ], - ), - SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.only( - left: 24.0, right: 24.0, top: 20.0, bottom: 15.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - FutureBuilder( - future: getDirectory(), - builder: (BuildContext context, - AsyncSnapshot snapshot) { - if (snapshot.hasData) { - Directory dir = snapshot.data; - List audiosFiles = dir - .listSync(recursive: true, followLinks: false) - .map((file) { - if (file.statSync().type == - FileSystemEntityType.file) { - return file; - } - }).toList(); - - audiosFiles.removeWhere((element) => element == null); - - if (audiosFiles.isEmpty) { - return Center( - child: Text( - 'No recordings', - style: Theme.of(context).textTheme.bodyText1, - ), - ); - } - - return ListView.builder( - shrinkWrap: true, - physics: const BouncingScrollPhysics(), - itemCount: audiosFiles.length, - itemBuilder: (BuildContext context, int index) { - File audioFile = File(audiosFiles[index]!.path); - - DateFormat dayFormat = DateFormat.yMd(); - DateFormat timeFormat = DateFormat.Hm(); - String fileSize = - getFileSize(audioFile.lengthSync(), 1); - DateTime createdAt = audioFile.lastModifiedSync(); - String createdAtFormatted = ''; - String fileName = audioFile.name ?? 'Recording'; - - if (createdAt.isToday()) { - createdAtFormatted = timeFormat.format(createdAt); - } else { - createdAtFormatted = dayFormat.format(createdAt); - } - - return Padding( - padding: const EdgeInsets.only(bottom: 5.0), - child: ListTile( - onTap: () {}, - title: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: AutoSizeText( - fileName, - maxLines: 1, - style: Theme.of(context) - .textTheme - .headline2, - ), - ), - Text( - createdAtFormatted, - style: - Theme.of(context).textTheme.subtitle2, - ) - ], - ), - subtitle: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - if (isPlaying(index)) - Text( - durationFormat(duration), + const SizedBox( + height: 20, + ), + Padding( + padding: const EdgeInsets.all(10.0), + child: Padding( + padding: const EdgeInsets.only(top: 10.0, bottom: 15.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FutureBuilder( + //list builder for when sliding up panel is closed + future: getDirectory(), + builder: (BuildContext context, + AsyncSnapshot snapshot) { + if (snapshot.hasData) { + Directory dir = snapshot.data; + List audiosFiles = dir + .listSync( + recursive: true, followLinks: false) + .map((file) { + if (file.statSync().type == + FileSystemEntityType.file) { + return file; + } + }).toList(); + + audiosFiles.removeWhere( + (element) => element == null); + + if (audiosFiles.isEmpty) { + return Center( + child: Text( + 'No recordings', // show when there are no recordings saved style: Theme.of(context) .textTheme - .subtitle2, + .bodyText1, ), - Text( - fileSize, - style: - Theme.of(context).textTheme.subtitle2, - ) - ], - ), - trailing: PopupMenuButton( - onSelected: (value) { - switch (value) { - case 'Share': - Share.shareFiles([audioFile.path], - text: fileName); - break; - case 'Rename': - rename(audioFile); - break; - case 'Delete': - delete(audioFile); - break; - } - }, - itemBuilder: (BuildContext context) { - return ['Share', 'Rename', 'Delete'] - .map((String choice) { - return PopupMenuItem( - value: choice, - child: Text(choice), - ); - }).toList(); - }, - ), - leading: CircleAvatar( - radius: 35, - backgroundColor: const Color(0xFFEFEFEF), - child: IconButton( - padding: EdgeInsets.zero, - onPressed: () async { - if ((audioPlayer!.isPlaying)) { - pauseAudio(); - setState(() {}); + ); + } + + return ListView.builder( + //for closed sliding up panel + //list view to display the recordings in a list + shrinkWrap: true, + physics: + const BouncingScrollPhysics(), //bouncing physics + itemCount: audiosFiles.length, + itemBuilder: + (BuildContext context, int index) { + File audioFile = + File(audiosFiles[index]!.path); + + DateFormat dayFormat = DateFormat.yMd(); + DateFormat timeFormat = DateFormat.Hm(); + String fileSize = getFileSize( + audioFile.lengthSync(), 1); + DateTime createdAt = + audioFile.lastModifiedSync(); + String createdAtFormatted = ''; + String fileName = audioFile.name ?? + 'Recording'; //checks if file name starts with recording + + if (createdAt.isToday()) { + createdAtFormatted = + timeFormat.format(createdAt); } else { - playAudio(audioFile.path, index); + createdAtFormatted = + dayFormat.format(createdAt); } + + return Padding( + padding: + const EdgeInsets.only(top: 10.0), + child: Container( + padding: const EdgeInsets.all(10), + decoration: const BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular(15)), + color: Color(0x94181818), + ), + child: Padding( + padding: const EdgeInsets.only( + bottom: 5, top: 5), + child: ListTile( + onTap: () {}, + title: Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + Expanded( + child: AutoSizeText( + fileName, //name of the file formatting inside the list of recordings + maxLines: 1, + style: Theme.of(context) + .textTheme + .headline2, + ), + ), + Text( + createdAtFormatted, + style: Theme.of(context) + .textTheme + .subtitle2, + ) + ], + ), + subtitle: Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + if (isPlaying(index)) + Text( + //duration of the recording + durationFormat(duration), + style: Theme.of(context) + .textTheme + .subtitle2, + ), + Text( + fileSize, //file size of the recording as a subtitle in the list widget of the recorder + style: Theme.of(context) + .textTheme + .subtitle2, + ) + ], + ), + trailing: PopupMenuButton( + color: Colors.white, + //rename share delete buttons inside a popup menu + onSelected: (value) { + switch (value) { + case 'Share': + Share.shareFiles( + [audioFile.path], + text: fileName); + break; + case 'Rename': + rename(audioFile); + break; + case 'Delete': + delete(audioFile); + break; + } + }, + itemBuilder: + (BuildContext context) { + //item builder of the popup button + return [ + 'Share', + 'Rename', + 'Delete' + ].map((String choice) { + return PopupMenuItem( + value: choice, + child: Text(choice), + ); + }).toList(); + }, + ), + leading: CircleAvatar( + //play pause circle avatar for leading icon + radius: 35, + backgroundColor: + const Color(0xffcabde4), + child: IconButton( + padding: EdgeInsets.zero, + onPressed: () async { + if ((audioPlayer! + .isPlaying)) { + pauseAudio(); + setState(() {}); + } else { + playAudio(audioFile.path, + index); + } + }, + icon: Icon( + isPlaying(index) + ? Icons.pause + : Icons.play_arrow, + color: + const Color(0xFF323232), + ), + iconSize: 30, + ), + ), + ), + ), + ), + ); }, - icon: Icon( - isPlaying(index) - ? Icons.pause - : Icons.play_arrow, - color: const Color(0xFF323232), - ), - iconSize: 30, - ), - ), - ), - ); - }, - ); - } + ); + } - return const Center( - child: CircularProgressIndicator(), + return const Center( + child: CircularProgressIndicator(), + ); + }, + ) + ], + ), + ), + ), + ], + ), + ), + body: Column( + children: [ + const SizedBox( + height: + 150), // spacing between the recording timer and tab bar + StreamBuilder( + stream: _stopWatchTimer.rawTime, + initialData: 0, + builder: (context, snap) { + final value = snap.data; + final displayTime = StopWatchTimer.getDisplayTime(value!); + return Text( + displayTime, + style: Theme.of(context).textTheme.headline1!.copyWith( + fontSize: 38, + color: const Color(0xa3ffffff), + ), ); }, + ), + Padding( + padding: const EdgeInsets.only(bottom: 40.0), + child: Text( + 'High Quality', //Text below the timer in record tab + style: Theme.of(context).textTheme.subtitle1, + ), + ), + const SizedBox( + height: 50, + ), // TODO space between countdown and animation + //auto spacing for the upcoming record button + Padding( + //record button starts + padding: const EdgeInsets.only( + bottom: 24.0, left: 24.0, right: 24.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Visibility( + visible: _mRecorder!.isPaused ? true : false, + maintainSize: true, + maintainAnimation: true, + maintainState: true, + child: IconButton( + onPressed: () => cancelRecord(), + icon: const Icon( + Icons.close, + color: Color(0xa3ffffff), + ), //x button to cancel the recording to be saved + ), + ), + SizedBox( + width: 76, + height: 76, + child: CircleAvatar( + radius: 50, + backgroundColor: const Color(0xd7cabde4), + child: IconButton( + padding: EdgeInsets.zero, + onPressed: getRecorderFn(), + icon: Icon( + _mRecorder!.isRecording + ? Icons.pause + : FluentIcons.mic_16_filled, + color: const Color(0xFF323232), + ), + iconSize: 30, + ), + ), + ), + Visibility( + visible: _mRecorder!.isPaused ? true : false, + maintainSize: true, + maintainAnimation: true, + maintainState: true, + child: SizedBox( + width: 40, + height: 40, + child: IconButton( + onPressed: () => saveAudioBottomSheet(), + icon: const Icon( + Icons.check, + color: Color(0xa3ffffff), + ), //tick button to proceed to save the recording to storage + ), + ), + ), + ], + ), ) ], ), ), - ), - ], + ], + ), ), - bottomNavigationBar: audioPlayer!.isPlaying ? const MiniPlayer() : null, ); } } diff --git a/lib/main.dart b/lib/main.dart index 022b6ed..c16d7f9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:iconsax/iconsax.dart'; import 'package:voicerra/Screens/notes_page.dart'; -import 'package:voicerra/Screens/translate.dart'; import 'package:voicerra/Screens/voice.dart'; -import 'package:iconsax/iconsax.dart'; import 'package:voicerra/Screens/transcribe_page.dart'; import 'package:voicerra/Screens/voice_recorder_page.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:flutter/services.dart'; +import 'package:fluentui_system_icons/fluentui_system_icons.dart'; void main() { runApp(const MyApp()); @@ -60,14 +60,9 @@ class MyApp extends StatelessWidget { ), inputDecorationTheme: InputDecorationTheme( filled: true, - fillColor: Colors.white, + fillColor: Colors.black, contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 14), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: const BorderSide( - style: BorderStyle.solid, width: 1.0, color: Color(0xFFEFEFEF)), - ), labelStyle: GoogleFonts.getFont( 'Raleway', fontWeight: FontWeight.w500, @@ -105,13 +100,13 @@ class MyApp extends StatelessWidget { 'Raleway', fontSize: 24, fontWeight: FontWeight.bold, - color: const Color(0xFF23262F), + color: const Color(0xa3ffffff), ), headline2: GoogleFonts.getFont( 'Raleway', fontSize: 17, fontWeight: FontWeight.w500, - color: const Color(0xFF23262F), + color: const Color(0xecffffff), ), subtitle1: GoogleFonts.getFont( 'Raleway', @@ -152,66 +147,82 @@ class MainPage extends StatefulWidget { } class _MainPageState extends State { - int index = 0; + int selectedIndex = 0; final List screens = [ const VoiceApp(), const TranscribePage( title: 'Transcribe', ), - const TranslatePage(), - const NotesPage(), const RecPage(), + const NotesPage(), + ]; + List data = [ + Icons.home_outlined, + FluentIcons.mic_16_regular, + FluentIcons.record_12_regular, + Iconsax.note, ]; + final style = const SystemUiOverlayStyle( - systemNavigationBarColor: Color(0xFFf6edfd), - systemNavigationBarDividerColor: Color(0xFFf6edfd), - systemNavigationBarIconBrightness: Brightness.dark, + systemNavigationBarColor: Color(0xFF212025), + systemNavigationBarDividerColor: Color(0xFF212025), + systemNavigationBarIconBrightness: Brightness.light, ); @override Widget build(BuildContext context) { SystemChrome.setSystemUIOverlayStyle(style); return Scaffold( - bottomNavigationBar: NavigationBarTheme( - data: NavigationBarThemeData( - indicatorColor: Colors.blue.shade100, - labelTextStyle: MaterialStateProperty.all( - const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), - ), - ), - child: NavigationBar( - height: 60, - backgroundColor: const Color(0xFFf6edfd), - labelBehavior: NavigationDestinationLabelBehavior.alwaysShow, - selectedIndex: index, - onDestinationSelected: (index) => setState(() => this.index = index), - destinations: const [ - NavigationDestination( - icon: Icon(Iconsax.microphone), - label: 'Liveaudio', - ), - NavigationDestination( - icon: Icon(Iconsax.book_saved), - label: 'Transcribe', + backgroundColor: const Color(0xFF212025), + bottomNavigationBar: Padding( + padding: const EdgeInsets.only(left: 20, right: 20, bottom: 20), + child: Material( + elevation: 10, + borderRadius: BorderRadius.circular(20), + color: const Color(0xff292933), + child: SizedBox( + height: 70, + width: double.infinity, + child: ListView.builder( + shrinkWrap: true, + physics: const BouncingScrollPhysics(), + itemCount: data.length, + padding: const EdgeInsets.symmetric(horizontal: 10), + itemBuilder: (ctx, i) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 25), + child: GestureDetector( + onTap: () { + setState(() { + selectedIndex = i; + }); + }, + child: AnimatedContainer( + duration: const Duration(milliseconds: 150), + width: 35, + decoration: BoxDecoration( + border: i == selectedIndex + ? const Border( + top: BorderSide( + width: 3.0, color: Color(0xffffff))) + : null, + ), + child: Icon( + data[i], + size: 35, + color: i == selectedIndex + ? const Color(0xdbffffff) + : const Color(0x67ffffff), + ), + ), + ), + ), + scrollDirection: Axis.horizontal, ), - NavigationDestination( - icon: Icon(Icons.translate), - label: 'Translate', - ), - NavigationDestination( - icon: Icon(Iconsax.note_1), - label: 'Notes', - ), - NavigationDestination( - icon: Icon(Iconsax.record_circle), - label: 'Recorder', - ), - ], + ), ), ), - backgroundColor: const Color(0xFFD1C4E9), body: IndexedStack( - index: index, + index: selectedIndex, children: screens, ), ); diff --git a/lib/widget/BarIndicator.dart b/lib/widget/BarIndicator.dart index e69de29..fe8ee25 100644 --- a/lib/widget/BarIndicator.dart +++ b/lib/widget/BarIndicator.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +class BarIndicator extends StatelessWidget { + const BarIndicator({ + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(20), + child: Container( + width: 40, + height: 3, + decoration: const BoxDecoration( + color: Colors.white60, + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + ), + ); + } +} diff --git a/lib/widget/customappbar.dart b/lib/widget/customappbar.dart index e69de29..7f6b13f 100644 --- a/lib/widget/customappbar.dart +++ b/lib/widget/customappbar.dart @@ -0,0 +1,57 @@ +import 'package:google_fonts/google_fonts.dart'; +import 'package:flutter/material.dart'; + +// ignore: must_be_immutable +class MyAppBar extends StatelessWidget { + final String title; + VoidCallback onIconTap; + // ignore: prefer_typing_uninitialized_variables + final iconName; + + MyAppBar({ + Key? key, + required this.onIconTap, + required this.title, + required this.iconName, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(25.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Text( + title, + style: GoogleFonts.bebasNeue( + fontSize: 52, + color: Colors.white, + ), + ), + ), + ), + GestureDetector( + onTap: onIconTap, + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + border: Border.all(color: const Color(0x28ffffff)), + borderRadius: BorderRadius.circular(16), + color: const Color(0xff272727), + ), + child: Icon( + iconName, + size: 36, + color: const Color(0xa1ffffff), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/widget/glass.dart b/lib/widget/glass.dart index e69de29..21b1b0b 100644 --- a/lib/widget/glass.dart +++ b/lib/widget/glass.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:glassmorphism/glassmorphism.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:loading_indicator/loading_indicator.dart'; + +class Glass extends StatelessWidget { + bool listening; + final confidence; + + Glass({ + super.key, + required this.listening, + required this.confidence, + }); + + @override + Widget build(BuildContext context) { + return GlassmorphicFlexContainer( + borderRadius: 30, + blur: 10, + padding: const EdgeInsets.only(left: 20, right: 20, bottom: 20), + alignment: Alignment.bottomCenter, + border: 1, + linearGradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + const Color(0xFFffffff).withOpacity(0.05), + const Color(0xFFFFFFFF).withOpacity(0.05), + ], + stops: const [ + 0.1, + 1, + ]), + borderGradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + const Color(0xFFffffff).withOpacity(0.05), + const Color((0xFFFFFFFF)).withOpacity(0.05), + ], + ), + child: Center( + child: Container( + child: listening + ? const Padding( + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 25), + child: SizedBox( + child: LoadingIndicator( + indicatorType: Indicator.lineScalePulseOutRapid, + colors: [ + Color(0xffcabde4), + Colors.deepPurpleAccent, + Color(0xFFFF0005), + ], + ), + ), + ) + : Text( + 'Accuracy: ${(confidence * 100.0).toStringAsFixed(1)}%', + style: GoogleFonts.bebasNeue( + fontSize: 34, + fontWeight: FontWeight.w400, + color: const Color(0xb5ffffff), + ), + ), + ), + ), + ); + } +} diff --git a/lib/widget/more_options_card_widget.dart b/lib/widget/more_options_card_widget.dart index e69de29..5c19461 100644 --- a/lib/widget/more_options_card_widget.dart +++ b/lib/widget/more_options_card_widget.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; + +class OptionsCard extends StatelessWidget { + final iconName; + final cardTitle; + final pageName; + + const OptionsCard({ + super.key, + required this.iconName, + required this.cardTitle, + required this.pageName, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(left: 20.0), + child: GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => pageName), + ); + }, + child: Container( + padding: const EdgeInsets.only(left: 20, right: 20), + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(15)), + color: Color(0xcdcabde4), + ), + child: Row( + children: [ + Icon(iconName), + const SizedBox(width: 10), + Text(cardTitle), + ], + ), + ), + ), + ); + } +} diff --git a/lib/widget/note_card_widget.dart b/lib/widget/note_card_widget.dart index f6e279f..88c003c 100644 --- a/lib/widget/note_card_widget.dart +++ b/lib/widget/note_card_widget.dart @@ -6,7 +6,7 @@ final _lightColors = [ const Color(0xffd2f2cd), const Color(0xffcdd2f2), const Color(0xffcdd2f2), - Colors.tealAccent.shade100 + Colors.tealAccent.shade100, ]; class NoteCardWidget extends StatelessWidget { diff --git a/lib/widget/note_form_widget.dart b/lib/widget/note_form_widget.dart index a399e28..56c23e2 100644 --- a/lib/widget/note_form_widget.dart +++ b/lib/widget/note_form_widget.dart @@ -42,14 +42,14 @@ class NoteFormWidget extends StatelessWidget { maxLines: 1, initialValue: title, style: const TextStyle( - color: Color(0xff263238), + color: Color(0xff808080), fontWeight: FontWeight.bold, fontSize: 24, ), decoration: const InputDecoration( border: InputBorder.none, hintText: 'Title', - hintStyle: TextStyle(color: Color(0xff263238)), + hintStyle: TextStyle(color: Color(0xff808080)), ), validator: (title) => title != null && title.isEmpty ? 'The title cannot be empty' : null, @@ -59,11 +59,11 @@ class NoteFormWidget extends StatelessWidget { Widget buildDescription() => TextFormField( maxLines: 50, initialValue: description, - style: const TextStyle(color: Color(0xff263238), fontSize: 18), + style: const TextStyle(color: Colors.white, fontSize: 25), decoration: const InputDecoration( border: InputBorder.none, - hintText: 'Type something...', - hintStyle: TextStyle(color: Color(0xff263238)), + hintText: 'Type your dukh dard...', + hintStyle: TextStyle(color: Colors.white, fontSize: 15), ), validator: (title) => title != null && title.isEmpty ? 'The description cannot be empty' diff --git a/pubspec.lock b/pubspec.lock index 47aaced..e3f9076 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -120,6 +120,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.1" + fluentui_system_icons: + dependency: "direct main" + description: + name: fluentui_system_icons + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.184" flutter: dependency: "direct main" description: flutter @@ -205,6 +212,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "8.0.9" + glassmorphism: + dependency: "direct main" + description: + name: glassmorphism + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" google_fonts: dependency: "direct main" description: @@ -602,6 +616,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.5.2" + sliding_up_panel: + dependency: "direct main" + description: + name: sliding_up_panel + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0+1" source_span: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 13b7fdd..ef75bb3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,6 +43,9 @@ dependencies: google_fonts: ^3.0.1 file_picker: ^5.0.1 translator: ^0.1.7 + sliding_up_panel: ^2.0.0+1 + glassmorphism: ^3.0.0 + fluentui_system_icons: ^1.1.184 dev_dependencies: flutter_lints: ^2.0.1