From ba83575aafad8f53e88ae283d1f942d9673c00ce Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Sat, 1 Jan 2022 14:12:44 +0330 Subject: [PATCH] D1APP-31 basic ui + audio recording support --- android/app/build.gradle | 3 +- android/app/src/main/AndroidManifest.xml | 2 + lib/pages/home/chat/chat.dart | 135 ++++++++++++++++++ lib/pages/home/chat/chat_state.dart | 3 + lib/pages/home/profile/settings/settings.dart | 3 +- .../home/profile/settings/settings_state.dart | 6 +- lib/pages/home/profile/widgets/menu_item.dart | 7 +- lib/routes/route_generator.dart | 10 +- lib/routes/routes.dart | 1 + lib/services/app_initalizer.dart | 19 ++- lib/services/storage/storage.dart | 3 + lib/widgets/didvan/app_bar.dart | 5 +- lib/widgets/didvan/badge.dart | 2 + lib/widgets/didvan/scaffold.dart | 45 +++--- pubspec.lock | 113 ++++++++++++++- pubspec.yaml | 8 ++ 16 files changed, 325 insertions(+), 40 deletions(-) create mode 100644 lib/pages/home/chat/chat.dart create mode 100644 lib/pages/home/chat/chat_state.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 52a5999..a74c195 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -26,7 +26,8 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 30 + compileSdkVersion 31 + compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 8494439..799c3f3 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,7 @@ + + diff --git a/lib/pages/home/chat/chat.dart b/lib/pages/home/chat/chat.dart new file mode 100644 index 0000000..59cfbe3 --- /dev/null +++ b/lib/pages/home/chat/chat.dart @@ -0,0 +1,135 @@ +import 'dart:developer'; + +import 'package:just_audio/just_audio.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:record/record.dart'; +import 'package:universal_html/js.dart' as js; +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/models/view/app_bar_data.dart'; +import 'package:didvan/widgets/didvan/scaffold.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_vibrate/flutter_vibrate.dart'; + +class Chat extends StatelessWidget { + const Chat({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Material( + child: Stack( + children: [ + Positioned( + top: 0, + bottom: 56, + left: 0, + right: 0, + child: DidvanScaffold( + appBarData: AppBarData( + hasBack: true, + subtitle: 'ارتباط با سردبیر', + title: 'رادار اقتصادی', + ), + slivers: const [], + ), + ), + Positioned( + bottom: 0, + right: 0, + left: 0, + child: Container( + height: 56, + decoration: BoxDecoration( + border: Border( + top: BorderSide( + color: Theme.of(context).colorScheme.cardBorder, + ), + ), + color: Theme.of(context).colorScheme.surface, + ), + child: Row( + children: [ + const _VoiceRecorderButton(), + Expanded( + child: TextField( + decoration: InputDecoration( + border: InputBorder.none, + hintText: 'بنویسید یا پیام صوتی بگذارید...', + hintStyle: Theme.of(context) + .textTheme + .caption! + .copyWith( + color: Theme.of(context).colorScheme.disabledText, + ), + ), + onChanged: (value) {}, + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} + +class _VoiceRecorderButton extends StatefulWidget { + const _VoiceRecorderButton({Key? key}) : super(key: key); + + @override + _VoiceRecorderButtonState createState() => _VoiceRecorderButtonState(); +} + +class _VoiceRecorderButtonState extends State<_VoiceRecorderButton> { + final _recorder = Record(); + final _player = AudioPlayer(); + + @override + void initState() { + _recorder.hasPermission(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onLongPressStart: (details) async { + if (!kIsWeb) { + Vibrate.feedback(FeedbackType.medium); + } + await _recorder.start(); + }, + onLongPressEnd: (details) async { + final path = await _recorder.stop(); + if (kIsWeb) { + await _player.setUrl(path!); + } else { + await _player.setFilePath(path!); + } + await _player.play(); + if (kIsWeb) { + js.context.callMethod('playAudio', ['/dash.mp3']); + } + }, + child: Container( + color: Colors.transparent, + height: double.infinity, + width: 52, + child: Icon( + DidvanIcons.mic_solid, + color: Theme.of(context).colorScheme.focusedBorder, + ), + ), + ); + } + + @override + void dispose() { + _recorder.dispose(); + _player.dispose(); + super.dispose(); + } +} diff --git a/lib/pages/home/chat/chat_state.dart b/lib/pages/home/chat/chat_state.dart new file mode 100644 index 0000000..19ff935 --- /dev/null +++ b/lib/pages/home/chat/chat_state.dart @@ -0,0 +1,3 @@ +import 'package:didvan/providers/core_provider.dart'; + +class ChatState extends CoreProvier {} diff --git a/lib/pages/home/profile/settings/settings.dart b/lib/pages/home/profile/settings/settings.dart index 27a65fa..3025044 100644 --- a/lib/pages/home/profile/settings/settings.dart +++ b/lib/pages/home/profile/settings/settings.dart @@ -133,7 +133,8 @@ class Settings extends StatelessWidget { okText: 'تایید', cancelText: 'بازگشت', accentColor: Theme.of(context).colorScheme.primary, - okCancelStyle: Theme.of(context).textTheme.bodyText2!, + okStyle: Theme.of(context).textTheme.bodyText2!, + cancelStyle: Theme.of(context).textTheme.bodyText2!, unselectedColor: Theme.of(context).colorScheme.text, blurredBackground: true, hourLabel: 'ساعت', diff --git a/lib/pages/home/profile/settings/settings_state.dart b/lib/pages/home/profile/settings/settings_state.dart index 0f0e973..617c835 100644 --- a/lib/pages/home/profile/settings/settings_state.dart +++ b/lib/pages/home/profile/settings/settings_state.dart @@ -7,12 +7,12 @@ class SettingsState extends CoreProvier { getSettingsFromStorage(); } - List _notificationTimeRange = []; + List _notificationTimeRange = []; String _fontFamily = 'Dana-FA'; double _fontSizeScale = 1; String _brightness = 'light'; - set notificationTimeRange(List value) { + set notificationTimeRange(List value) { _notificationTimeRange = value; StorageService.setValue( key: 'notificationTimeRange', @@ -21,7 +21,7 @@ class SettingsState extends CoreProvier { ); } - List get notificationTimeRange => _notificationTimeRange; + List get notificationTimeRange => _notificationTimeRange; set fontFamily(String value) { _fontFamily = value; diff --git a/lib/pages/home/profile/widgets/menu_item.dart b/lib/pages/home/profile/widgets/menu_item.dart index d792327..085f6ae 100644 --- a/lib/pages/home/profile/widgets/menu_item.dart +++ b/lib/pages/home/profile/widgets/menu_item.dart @@ -28,7 +28,12 @@ class MenuItem extends StatelessWidget { color: Colors.transparent, child: Row( children: [ - if (icon != null) Icon(icon, size: 18, color: color), + if (icon != null) + Icon( + icon, + size: 18, + color: color ?? Theme.of(context).colorScheme.title, + ), if (icon != null) const SizedBox(width: 4), DidvanText( title, diff --git a/lib/routes/route_generator.dart b/lib/routes/route_generator.dart index ca92ad5..159efcf 100644 --- a/lib/routes/route_generator.dart +++ b/lib/routes/route_generator.dart @@ -1,5 +1,7 @@ import 'package:didvan/pages/authentication/authentication.dart'; import 'package:didvan/pages/authentication/authentication_state.dart'; +import 'package:didvan/pages/home/chat/chat.dart'; +import 'package:didvan/pages/home/chat/chat_state.dart'; import 'package:didvan/pages/home/home.dart'; import 'package:didvan/pages/home/home_state.dart'; import 'package:didvan/pages/home/news/news_details/news_details.dart'; @@ -73,7 +75,13 @@ class RouteGenerator { child: const ChatList(), ), ); - + case Routes.chat: + return _createRoute( + ChangeNotifierProvider( + create: (context) => ChatState(), + child: const Chat(), + ), + ); default: return _errorRoute(); } diff --git a/lib/routes/routes.dart b/lib/routes/routes.dart index 8089c54..ae38e38 100644 --- a/lib/routes/routes.dart +++ b/lib/routes/routes.dart @@ -8,4 +8,5 @@ class Routes { static const String radarDetails = '/radar-details'; static const String newsDetails = '/news-details'; static const String chatList = '/chat-list'; + static const String chat = '/chat'; } diff --git a/lib/services/app_initalizer.dart b/lib/services/app_initalizer.dart index ac76df2..0d628fb 100644 --- a/lib/services/app_initalizer.dart +++ b/lib/services/app_initalizer.dart @@ -1,15 +1,22 @@ -import 'dart:io'; - import 'package:didvan/models/settings_data.dart'; import 'package:didvan/services/storage/storage.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:hive/hive.dart'; -import 'package:path_provider/path_provider.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:permission_handler/permission_handler.dart'; class AppInitializer { static Future setupServices() async { - final Directory appDir = await getApplicationDocumentsDirectory(); - Hive.init(appDir.path); + // late final Directory appDir; + // late final Directory appTempsDir; + // if (!kIsWeb) { + await Hive.initFlutter(); + // appDir = await getApplicationDocumentsDirectory(); + // appTempsDir = await getApplicationDocumentsDirectory(); + // } + // Hive.init(appDir.path); + // StorageService.appDocsDir = appDir.path; + // StorageService.appTempsDir = appTempsDir.path; } static Future initilizeSettings() async { diff --git a/lib/services/storage/storage.dart b/lib/services/storage/storage.dart index ec01ccf..0f1a673 100644 --- a/lib/services/storage/storage.dart +++ b/lib/services/storage/storage.dart @@ -1,6 +1,9 @@ import 'package:hive/hive.dart'; class StorageService { + static late final String appDocsDir; + static late final String appTempsDir; + static Future setValue({ required String key, required dynamic value, diff --git a/lib/widgets/didvan/app_bar.dart b/lib/widgets/didvan/app_bar.dart index 3daaf44..d4ca0df 100644 --- a/lib/widgets/didvan/app_bar.dart +++ b/lib/widgets/didvan/app_bar.dart @@ -9,9 +9,8 @@ class DidvanAppBar extends StatelessWidget { @override Widget build(BuildContext context) { - final MediaQueryData d = MediaQuery.of(context); return Padding( - padding: EdgeInsets.only(top: d.padding.top, right: 4, left: 20), + padding: const EdgeInsets.only(right: 4, left: 20), child: Row( children: [ IconButton( @@ -34,7 +33,7 @@ class DidvanAppBar extends StatelessWidget { ), if (appBarData.subtitle != null) DidvanText( - appBarData.title!, + appBarData.subtitle!, style: Theme.of(context).textTheme.overline, color: Theme.of(context).colorScheme.caption, ), diff --git a/lib/widgets/didvan/badge.dart b/lib/widgets/didvan/badge.dart index 6446337..855b36f 100644 --- a/lib/widgets/didvan/badge.dart +++ b/lib/widgets/didvan/badge.dart @@ -1,3 +1,4 @@ +import 'package:didvan/config/theme_data.dart'; import 'package:didvan/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; @@ -24,6 +25,7 @@ class DidvanBadge extends StatelessWidget { text, isEnglishFont: true, style: Theme.of(context).textTheme.overline, + color: Theme.of(context).colorScheme.white, ), ), ); diff --git a/lib/widgets/didvan/scaffold.dart b/lib/widgets/didvan/scaffold.dart index 51fac9e..77853d6 100644 --- a/lib/widgets/didvan/scaffold.dart +++ b/lib/widgets/didvan/scaffold.dart @@ -15,29 +15,34 @@ class DidvanScaffold extends StatelessWidget { @override Widget build(BuildContext context) { + final double statusBarHeight = MediaQuery.of(context).padding.top; return Scaffold( - body: CustomScrollView( - slivers: [ - SliverAppBar( - backgroundColor: Theme.of(context).backgroundColor, - automaticallyImplyLeading: false, - pinned: true, - flexibleSpace: DidvanAppBar(appBarData: appBarData), - ), - const SliverToBoxAdapter( - child: SizedBox(height: 16), - ), - if (slivers != null) - SliverPadding( - padding: const EdgeInsets.symmetric(horizontal: 16), - sliver: SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) => slivers![index], - childCount: slivers!.length, + body: Padding( + padding: EdgeInsets.only(top: statusBarHeight), + child: CustomScrollView( + slivers: [ + SliverAppBar( + toolbarHeight: kToolbarHeight, + backgroundColor: Theme.of(context).backgroundColor, + automaticallyImplyLeading: false, + pinned: true, + flexibleSpace: DidvanAppBar(appBarData: appBarData), + ), + const SliverToBoxAdapter( + child: SizedBox(height: 16), + ), + if (slivers != null) + SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 16), + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => slivers![index], + childCount: slivers!.length, + ), ), ), - ), - ], + ], + ), ), ); } diff --git a/pubspec.lock b/pubspec.lock index b15174a..2a8196e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -8,6 +8,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.8.2" + audio_session: + dependency: transitive + description: + name: audio_session + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.6+1" boolean_selector: dependency: transitive description: @@ -85,6 +92,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.1" + csslib: + dependency: transitive + description: + name: csslib + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.1" cupertino_icons: dependency: "direct main" description: @@ -98,7 +112,7 @@ packages: name: day_night_time_picker url: "https://pub.dartlang.org" source: hosted - version: "1.0.3+1" + version: "1.0.4+1" fake_async: dependency: transitive description: @@ -177,6 +191,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_vibrate: + dependency: "direct main" + description: + name: flutter_vibrate + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" flutter_web_plugins: dependency: transitive description: flutter @@ -196,6 +217,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.5" + hive_flutter: + dependency: "direct main" + description: + name: hive_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + html: + dependency: transitive + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.15.0" http: dependency: transitive description: @@ -245,6 +280,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.6.3" + just_audio: + dependency: "direct main" + description: + name: just_audio + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.18" + just_audio_platform_interface: + dependency: transitive + description: + name: just_audio_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" + just_audio_web: + dependency: transitive + description: + name: just_audio_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.2" lints: dependency: transitive description: @@ -314,7 +370,7 @@ packages: name: path_provider_android url: "https://pub.dartlang.org" source: hosted - version: "2.0.9" + version: "2.0.11" path_provider_ios: dependency: transitive description: @@ -357,6 +413,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.11.1" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + url: "https://pub.dartlang.org" + source: hosted + version: "8.3.0" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "3.7.0" petitparser: dependency: transitive description: @@ -398,7 +468,28 @@ packages: name: provider url: "https://pub.dartlang.org" source: hosted - version: "6.0.1" + version: "6.0.2" + record: + dependency: "direct main" + description: + name: record + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" + record_platform_interface: + dependency: transitive + description: + name: record_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" + record_web: + dependency: "direct main" + description: + name: record_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.1" rive: dependency: "direct main" description: @@ -495,6 +586,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0" + universal_html: + dependency: "direct main" + description: + name: universal_html + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" + universal_io: + dependency: transitive + description: + name: universal_io + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" uuid: dependency: transitive description: @@ -515,7 +620,7 @@ packages: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "2.3.2" + version: "2.3.3" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e84d6af..395295a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -48,6 +48,14 @@ dependencies: cached_network_image: ^3.2.0 skeleton_text: ^3.0.0 carousel_slider: ^4.0.0 + flutter_vibrate: ^1.3.0 + hive_flutter: ^1.1.0 + # flutter_sound: ^8.4.2 + permission_handler: ^8.3.0 + universal_html: ^2.0.8 + record: ^3.0.2 + just_audio: ^0.9.18 + record_web: ^0.2.1 dev_dependencies: flutter_test: