diff --git a/assets/fonts/NotoSansKR-Black.otf b/assets/fonts/NotoSansKR-Black.otf new file mode 100644 index 0000000..5599581 Binary files /dev/null and b/assets/fonts/NotoSansKR-Black.otf differ diff --git a/assets/fonts/NotoSansKR-Bold.otf b/assets/fonts/NotoSansKR-Bold.otf new file mode 100644 index 0000000..be388bf Binary files /dev/null and b/assets/fonts/NotoSansKR-Bold.otf differ diff --git a/assets/fonts/NotoSansKR-Light.otf b/assets/fonts/NotoSansKR-Light.otf new file mode 100644 index 0000000..548e667 Binary files /dev/null and b/assets/fonts/NotoSansKR-Light.otf differ diff --git a/assets/fonts/NotoSansKR-Medium.otf b/assets/fonts/NotoSansKR-Medium.otf new file mode 100644 index 0000000..5ddbbc0 Binary files /dev/null and b/assets/fonts/NotoSansKR-Medium.otf differ diff --git a/assets/fonts/NotoSansKR-Regular.otf b/assets/fonts/NotoSansKR-Regular.otf new file mode 100644 index 0000000..7c5c2fa Binary files /dev/null and b/assets/fonts/NotoSansKR-Regular.otf differ diff --git a/assets/fonts/NotoSansKR-Thin.otf b/assets/fonts/NotoSansKR-Thin.otf new file mode 100644 index 0000000..1299fef Binary files /dev/null and b/assets/fonts/NotoSansKR-Thin.otf differ diff --git a/assets/icon_dark/announcement_dark.png b/assets/icon_dark/announcement_dark.png new file mode 100644 index 0000000..1cb5cb3 Binary files /dev/null and b/assets/icon_dark/announcement_dark.png differ diff --git a/assets/icon_dark/bicycle_dark.png b/assets/icon_dark/bicycle_dark.png new file mode 100644 index 0000000..19f5cdc Binary files /dev/null and b/assets/icon_dark/bicycle_dark.png differ diff --git a/assets/icon_dark/carhorn_dark.png b/assets/icon_dark/carhorn_dark.png new file mode 100644 index 0000000..9458970 Binary files /dev/null and b/assets/icon_dark/carhorn_dark.png differ diff --git a/assets/icon_dark/crack_dark.png b/assets/icon_dark/crack_dark.png new file mode 100644 index 0000000..e5d15f0 Binary files /dev/null and b/assets/icon_dark/crack_dark.png differ diff --git a/assets/icon_dark/firealarm_dark.png b/assets/icon_dark/firealarm_dark.png new file mode 100644 index 0000000..60f40e3 Binary files /dev/null and b/assets/icon_dark/firealarm_dark.png differ diff --git a/assets/icon_dark/gun_dark.png b/assets/icon_dark/gun_dark.png new file mode 100644 index 0000000..fd9d857 Binary files /dev/null and b/assets/icon_dark/gun_dark.png differ diff --git a/assets/icon_dark/infantcrying_dark.png b/assets/icon_dark/infantcrying_dark.png new file mode 100644 index 0000000..7d41cac Binary files /dev/null and b/assets/icon_dark/infantcrying_dark.png differ diff --git a/assets/icon_dark/mamapapa_dark.png b/assets/icon_dark/mamapapa_dark.png new file mode 100644 index 0000000..ccab41a Binary files /dev/null and b/assets/icon_dark/mamapapa_dark.png differ diff --git a/assets/icon_dark/name_dark.png b/assets/icon_dark/name_dark.png new file mode 100644 index 0000000..fc16e61 Binary files /dev/null and b/assets/icon_dark/name_dark.png differ diff --git a/assets/icon_light/annoucement_light.png b/assets/icon_light/annoucement_light.png new file mode 100644 index 0000000..b97163b Binary files /dev/null and b/assets/icon_light/annoucement_light.png differ diff --git a/assets/icon_light/bicycle_light.png b/assets/icon_light/bicycle_light.png new file mode 100644 index 0000000..7ad86f0 Binary files /dev/null and b/assets/icon_light/bicycle_light.png differ diff --git a/assets/icon_light/carhorn_light.png b/assets/icon_light/carhorn_light.png new file mode 100644 index 0000000..291d558 Binary files /dev/null and b/assets/icon_light/carhorn_light.png differ diff --git a/assets/icon_light/crack_light.png b/assets/icon_light/crack_light.png new file mode 100644 index 0000000..ee7fc56 Binary files /dev/null and b/assets/icon_light/crack_light.png differ diff --git a/assets/icon_light/firealarm_light.png b/assets/icon_light/firealarm_light.png new file mode 100644 index 0000000..4fa19bf Binary files /dev/null and b/assets/icon_light/firealarm_light.png differ diff --git a/assets/icon_light/gun_light.png b/assets/icon_light/gun_light.png new file mode 100644 index 0000000..4e7b2ca Binary files /dev/null and b/assets/icon_light/gun_light.png differ diff --git a/assets/icon_light/infantcrying_light.png b/assets/icon_light/infantcrying_light.png new file mode 100644 index 0000000..2dddf64 Binary files /dev/null and b/assets/icon_light/infantcrying_light.png differ diff --git a/assets/icon_light/mamapapa_light.png b/assets/icon_light/mamapapa_light.png new file mode 100644 index 0000000..7de336e Binary files /dev/null and b/assets/icon_light/mamapapa_light.png differ diff --git a/assets/icon_light/name_light.png b/assets/icon_light/name_light.png new file mode 100644 index 0000000..142c129 Binary files /dev/null and b/assets/icon_light/name_light.png differ diff --git a/lib/audio_record.dart b/lib/audio_record.dart index afd9f02..283194f 100644 --- a/lib/audio_record.dart +++ b/lib/audio_record.dart @@ -1,19 +1,17 @@ import 'dart:async'; - -import 'package:avatar_glow/avatar_glow.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; import 'dart:typed_data'; import 'package:flutter/material.dart'; +import 'package:noise_meter/noise_meter.dart'; import 'package:permission_handler/permission_handler.dart'; -import 'package:see_our_sounds/src/config/app_constants.dart'; +import 'package:see_our_sounds/src/core/app_constants.dart'; import 'package:see_our_sounds/src/core/core.dart'; -import 'package:see_our_sounds/src/data/models/audio_tagging_model.dart'; -import 'package:see_our_sounds/src/data/repositories/audio_tagging_repo_impl.dart'; -import 'package:see_our_sounds/src/data/services/remote/audio_tagging_service.dart'; -import 'package:see_our_sounds/src/domain/repositories/audio_tagging_repo.dart'; -import 'package:see_our_sounds/src/screen/provider/audio_tagging_provider.dart'; +import 'package:see_our_sounds/src/core/notification/local_notification.dart'; +import 'package:see_our_sounds/src/core/utils/audio_util.dart'; +import 'package:see_our_sounds/src/models/audio_tagging_model.dart'; +import 'package:see_our_sounds/src/screens/home/audio_waveform.dart'; +import 'package:see_our_sounds/src/screens/home/decibel_history_chart.dart'; import 'package:sound_stream/sound_stream.dart'; import 'package:speech_to_text/speech_to_text.dart'; @@ -28,13 +26,17 @@ class _AudioStreamState extends State { LocalNotification localNotification = LocalNotification(); late SpeechToText speechToText; RecorderStream recorder = RecorderStream(); + late NoiseMeter noiseMeter; List audioChunks = []; + List decibelHistory = []; bool isRecording = false; bool isSTTEnable = false; + StreamSubscription? noiseSubscription; StreamSubscription? recorderStatus; StreamSubscription? audioStream; double confidence = 1.0; String text = ''; + double currentDecibel = 0; Future openRecord() async { recorderStatus = recorder.status.listen((status) async { @@ -56,48 +58,14 @@ class _AudioStreamState extends State { await Future.wait([recorder.initialize()]); } - Uint8List toWAV(List audioChunks) { - var data = audioChunks.expand((i) => i).toList(); - var channels = 1; - int sampleRate = 16000; - int byteRate = (16 * sampleRate * channels) ~/ 8; - int totalAudioLen = data.length; - int totalDataLen = totalAudioLen + 36; - - Uint8List header = Uint8List.fromList([ - ...utf8.encode('RIFF'), - (totalDataLen & 0xff), - ((totalDataLen >> 8) & 0xff), - ((totalDataLen >> 16) & 0xff), - ((totalDataLen >> 24) & 0xff), - ...utf8.encode('WAVEfmt '), - // 4 bytes: size of 'fmt ' chunk - 16, 0, 0, 0, - // type of fmt - 1, 0, channels, 0, - (sampleRate & 0xff), - ((sampleRate >> 8) & 0xff), - ((sampleRate >> 16) & 0xff), - ((sampleRate >> 24) & 0xff), - (byteRate & 0xff), - ((byteRate >> 8) & 0xff), - ((byteRate >> 16) & 0xff), - ((byteRate >> 24) & 0xff), - ((16 * channels) ~/ 8), // block align - 0, 16, 0, // bit size - ...utf8.encode('data'), - (totalAudioLen & 0xff), - ((totalAudioLen >> 8) & 0xff), - ((totalAudioLen >> 16) & 0xff), - ((totalAudioLen >> 24) & 0xff), - ...data - ]); - - return header; + void onError(Object error) { + isRecording = false; + throw Exception(error.toString()); } Future getPong() async { - var request = http.Request('GET', Uri.parse(uriBase + uriPing)); + var request = + http.Request('GET', Uri.parse(AppUri.uriBase + AppUri.uriPing)); http.StreamedResponse response = await request.send(); @@ -108,39 +76,16 @@ class _AudioStreamState extends State { } } - Stream audioTagging() async* { - final audioStreamController = StreamController(); - Timer? timer; - StreamSubscription? audioStreamSubscription; - - timer = Timer.periodic(const Duration(seconds: 1), (timer) { - audioStreamController.add(DateTime.now()); - }); - - audioStreamSubscription = - audioStreamController.stream.listen((event) async { - if (!isRecording) { - timer?.cancel(); - await audioStreamController.close(); - await audioStreamSubscription?.cancel(); - } else { - Uint8List wav = toWAV(audioChunks); - AudioTaggingModel audioTaggingModel = await postAudio(wav); - } - }); - } - Stream audioTaggingStream() async* { - await for (final value in recorder.audioStream) { - print('확인 : $value'); - Uint8List wav = toWAV(audioChunks); + await for (final _ in recorder.audioStream) { + Uint8List wav = AudioUtil.toWAV(audioChunks); AudioTaggingModel audioTaggingModel = await postAudio(wav); yield audioTaggingModel; } } Future postAudio(Uint8List data) async { - final uri = Uri.parse(uriBase + uriUint); + final uri = Uri.parse(AppUri.uriBase + AppUri.uriUint); var response = await http.post(uri, body: data); if (response.statusCode == 200) { var responseBody = jsonDecode(response.body); @@ -172,6 +117,27 @@ class _AudioStreamState extends State { } } + void onData(NoiseReading noiseReading) { + setState(() { + if (isRecording) { + var decibel = noiseReading.meanDecibel; + currentDecibel = decibel; + if (decibel < 30) { + decibelHistory.add(30); + } else if (decibel > 110) { + decibelHistory.add(110); + } else { + decibelHistory.add(decibel); + } + if (decibelHistory.length > 80) { + decibelHistory.removeAt(0); + } + } else { + currentDecibel = 0; + } + }); + } + @override void initState() { // TODO: implement initState @@ -180,6 +146,8 @@ class _AudioStreamState extends State { audioTaggingStream(); speechToText = SpeechToText(); localNotification.initNotification(); + noiseMeter = NoiseMeter(onError); + noiseSubscription = noiseMeter.noiseStream.listen(onData); } @override @@ -190,74 +158,93 @@ class _AudioStreamState extends State { audioStream?.cancel(); recorder.stop(); audioChunks.clear(); + noiseSubscription?.cancel(); } @override Widget build(BuildContext context) { return Scaffold( - body: StreamBuilder( - stream: audioTaggingStream(), - builder: (context, snapshot) { - if (snapshot.hasData) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(speechToText.isListening - ? text - : isSTTEnable - ? 'Click button' - : 'Speech not available'), - Text( - snapshot.data!.isAlert.toString(), - style: TextStyle(fontSize: 30), - ), - Text( - snapshot.data!.label, - style: TextStyle(fontSize: 30), + body: Column( + children: [ + // SfRadialGauge( + // axes: [ + // RadialAxis( + // minimum: 0, + // maximum: 100, + // ranges: [ + // GaugeRange(startValue: 0, endValue: 50, color: Colors.green), + // GaugeRange( + // startValue: 50, endValue: 100, color: Colors.orange), + // GaugeRange(startValue: 100, endValue: 150, color: Colors.red) + // ], + // pointers: [ + // NeedlePointer( + // value: 90, tailStyle: TailStyle(width: 0, length: 0), needleLength: 1, + // ) + // ], + // annotations: [ + // GaugeAnnotation( + // widget: Container( + // child: Text('90'), + // ), + // angle: 90, + // positionFactor: .5, + // ) + // ], + // ) + // ], + // ), + Text(currentDecibel.round().toString()), + // DecibelHistoryChart(decibelHistory: decibelHistory), + // AudioWaveform(decibelHistory: decibelHistory), + StreamBuilder( + stream: audioTaggingStream(), + builder: (context, snapshot) { + if (snapshot.hasData) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + snapshot.data!.isAlert.toString(), + style: TextStyle(fontSize: 30), + ), + Text( + snapshot.data!.label, + style: TextStyle(fontSize: 30), + ), + Text( + snapshot.data!.taggingRate.toString(), + style: TextStyle(fontSize: 30), + ), + Text( + snapshot.data!.date, + style: TextStyle(fontSize: 30), + ), + ], ), - Text( - snapshot.data!.taggingRate.toString(), - style: TextStyle(fontSize: 30), - ), - ], - ), - ); - } - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'No Data', - style: TextStyle(fontSize: 30), - ), - ], - ), - ); - }), - floatingActionButtonLocation: FloatingActionButtonLocation.centerTop, - floatingActionButton: AvatarGlow( - animate: isRecording, - glowColor: Theme.of(context).primaryColor, - endRadius: 80, - duration: const Duration(milliseconds: 4000), - repeatPauseDuration: const Duration(milliseconds: 200), - repeat: true, - child: FloatingActionButton( - child: Icon(isRecording ? Icons.mic : Icons.mic_off), - onPressed: () async { - _listen(); - if (isRecording) { - recorder.stop(); - audioChunks.clear(); - } else { - recorder.start(); - getPong(); - } - _listen(); - }, - ), + ); + } + return Center( + child: const SizedBox(), + ); + }), + ], + ), + floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, + floatingActionButton: FloatingActionButton( + child: Icon(isRecording ? Icons.mic : Icons.mic_off), + onPressed: () async { + _listen(); + if (isRecording) { + recorder.stop(); + audioChunks.clear(); + } else { + recorder.start(); + getPong(); + } + _listen(); + }, ), ); } diff --git a/lib/main.dart b/lib/main.dart index 6261b3c..86f7abb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:see_our_sounds/audio_record.dart'; -import 'package:see_our_sounds/src/screen/home/home_screen.dart'; -import 'package:see_our_sounds/test.dart'; +import 'package:see_our_sounds/src/screens/history_screen.dart'; + void main() { runApp(ProviderScope(child: const MyApp())); @@ -14,12 +14,13 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - title: 'Flutter Demo', + title: 'HearSitter', debugShowCheckedModeBanner: false, theme: ThemeData( primarySwatch: Colors.blue, + fontFamily: 'NotoSansKR' ), - home: const AudioStream(), + home: const HistoryScreen(), ); } } diff --git a/lib/src/config/app_assets.dart b/lib/src/config/app_assets.dart deleted file mode 100644 index 243b509..0000000 --- a/lib/src/config/app_assets.dart +++ /dev/null @@ -1,4 +0,0 @@ -class AppAssets { - static const String logo = ""; - -} \ No newline at end of file diff --git a/lib/src/config/app_constants.dart b/lib/src/config/app_constants.dart deleted file mode 100644 index 0e488a9..0000000 --- a/lib/src/config/app_constants.dart +++ /dev/null @@ -1,37 +0,0 @@ -final String uriBase = 'http://watch.jimmy0006.site:3000'; -final String uriPing = '/ping'; -final String uriUint = '/uint'; - -enum GameCategoryType { - CALCULATOR, - GUESS_SIGN, - SQUARE_ROOT, - MATH_PAIRS, - CORRECT_ANSWER, - MAGIC_TRIANGLE, - MENTAL_ARITHMETIC, - QUICK_CALCULATION, - MATH_GRID, - PICTURE_PUZZLE, - NUMBER_PYRAMID -} - -enum PuzzleType { MATH_PUZZLE, MEMORY_PUZZLE, BRAIN_PUZZLE } - -class KeyUtil { - static const IS_DARK_MODE = "isDarkMode"; - - static const String splash = 'Splash'; - static const String dashboard = 'Dashboard'; - static const String home = 'Home'; - - static const String calculator = 'Calculator'; - static const String guessSign = 'GuessSign'; - static const String correctAnswer = 'CorrectAnswer'; - static const String quickCalculation = 'QuickCalculation'; - static const String mentalArithmetic = 'MentalArithmetic'; - static const String squareRoot = 'SquareRoot'; - static const String mathPairs = 'MathPairs'; - static const String magicTriangle = 'MagicTriangle'; - static const String picturePuzzle = 'PicturePuzzle'; -} diff --git a/lib/src/config/config.dart b/lib/src/config/config.dart deleted file mode 100644 index 267041f..0000000 --- a/lib/src/config/config.dart +++ /dev/null @@ -1,4 +0,0 @@ -export 'app_theme.dart'; -export 'app_constants.dart'; -export 'app_assets.dart'; -export 'app_routes.dart'; \ No newline at end of file diff --git a/lib/src/core/app_assets.dart b/lib/src/core/app_assets.dart new file mode 100644 index 0000000..2f93163 --- /dev/null +++ b/lib/src/core/app_assets.dart @@ -0,0 +1,22 @@ +class AppAssets { + AppAssets._(); + static const String logo = ""; + static const String announcementIconDark = 'assets/icon_dark/announcement_dark.png'; + static const String announcementIconLight = 'assets/icon_light/announcement_light.png'; + static const String bicycleIconDark = 'assets/icon_dark/bicycle_dark.png'; + static const String bicycleIconLight = 'assets/icon_light/bicycle_light.png'; + static const String carHornIconDark = 'assets/icon_dark/carhorn_dark.png'; + static const String carHornIconLight = 'assets/icon_light/carhorn_light.png'; + static const String crackIconDark = 'assets/icon_dark/crack_dark.png'; + static const String crackIconLight = 'assets/icon_light/crack_light.png'; + static const String fireAlarmIconDark = 'assets/icon_dark/firealarm_dark.png'; + static const String fireAlarmIconLight = 'assets/icon_light/firealarm_light.png'; + static const String gunIconDark = 'assets/icon_dark/gun_dark.png'; + static const String gunIconLight = 'assets/icon_light/gun_light.png'; + static const String infantCryingIconDark = 'assets/icon_dark/infantcrying_dark.png'; + static const String infantCryingIconLight = 'assets/icon_light/infantcrying_light.png'; + static const String mamaPapaIconDark = 'assets/icon_dark/mamapapa_dark.png'; + static const String mamaPapaIconLight = 'assets/icon_light/mamapapa_light.png'; + static const String nameIconDark = 'assets/icon_dark/name_dark.png'; + static const String nameIconLight = 'assets/icon_light/name_light.png'; +} diff --git a/lib/src/core/app_constants.dart b/lib/src/core/app_constants.dart new file mode 100644 index 0000000..49c430e --- /dev/null +++ b/lib/src/core/app_constants.dart @@ -0,0 +1,54 @@ +import 'dart:ui'; + +class AppUri { + AppUri._(); + static const String uriBase = 'http://watch.jimmy0006.site:3000'; + static const String uriPing = '/ping'; + static const String uriUint = '/uint'; +} + +class AppColor { + AppColor._(); + static const primaryColor = Color(0xff4285f4); + static const errorColor = Color(0xffea4335); + static const grayColor = Color(0xffa1a1a1); + static const lightGrayColor = Color(0xffd9d9d9); + static const accentColor = Color(0xff00164e); +} + +class AppDatabase{ + static const String tableName = 'history'; + static const int version = 1; // database version +} + +enum SoundCategory { + INFANT_CRYING, + CRACK_SOUND, + FIRE_ALARM, + GUN_SHOT, + CAR_HORN, + ANNOUNCEMNET, + NAME, + MAMAM_PAPA, + BICYCLE_BELL, +} + +enum PuzzleType { MATH_PUZZLE, MEMORY_PUZZLE, BRAIN_PUZZLE } + +class KeyUtil { + static const IS_DARK_MODE = "isDarkMode"; + + static const String splash = 'Splash'; + static const String dashboard = 'Dashboard'; + static const String home = 'Home'; + + static const String calculator = 'Calculator'; + static const String guessSign = 'GuessSign'; + static const String correctAnswer = 'CorrectAnswer'; + static const String quickCalculation = 'QuickCalculation'; + static const String mentalArithmetic = 'MentalArithmetic'; + static const String squareRoot = 'SquareRoot'; + static const String mathPairs = 'MathPairs'; + static const String magicTriangle = 'MagicTriangle'; + static const String picturePuzzle = 'PicturePuzzle'; +} diff --git a/lib/src/config/app_routes.dart b/lib/src/core/app_routes.dart similarity index 100% rename from lib/src/config/app_routes.dart rename to lib/src/core/app_routes.dart diff --git a/lib/src/config/app_theme.dart b/lib/src/core/app_themes.dart similarity index 100% rename from lib/src/config/app_theme.dart rename to lib/src/core/app_themes.dart diff --git a/lib/src/core/core.dart b/lib/src/core/core.dart index 2405e53..fc785b5 100644 --- a/lib/src/core/core.dart +++ b/lib/src/core/core.dart @@ -1 +1,4 @@ -export './notification/local_notification.dart'; \ No newline at end of file +export 'app_themes.dart'; +export 'app_constants.dart'; +export 'app_assets.dart'; +export 'app_routes.dart'; \ No newline at end of file diff --git a/lib/src/core/utils/audio_util.dart b/lib/src/core/utils/audio_util.dart new file mode 100644 index 0000000..1e1a1b9 --- /dev/null +++ b/lib/src/core/utils/audio_util.dart @@ -0,0 +1,57 @@ +import 'dart:convert'; +import 'dart:math'; +import 'dart:typed_data'; + +class AudioUtil{ + + static Uint8List toWAV(List audioChunks) { + var data = audioChunks.expand((i) => i).toList(); + var channels = 1; + int sampleRate = 16000; + int byteRate = (16 * sampleRate * channels) ~/ 8; + int totalAudioLen = data.length; + int totalDataLen = totalAudioLen + 36; + + Uint8List header = Uint8List.fromList([ + ...utf8.encode('RIFF'), + (totalDataLen & 0xff), + ((totalDataLen >> 8) & 0xff), + ((totalDataLen >> 16) & 0xff), + ((totalDataLen >> 24) & 0xff), + ...utf8.encode('WAVEfmt '), + // 4 bytes: size of 'fmt ' chunk + 16, 0, 0, 0, + // type of fmt + 1, 0, channels, 0, + (sampleRate & 0xff), + ((sampleRate >> 8) & 0xff), + ((sampleRate >> 16) & 0xff), + ((sampleRate >> 24) & 0xff), + (byteRate & 0xff), + ((byteRate >> 8) & 0xff), + ((byteRate >> 16) & 0xff), + ((byteRate >> 24) & 0xff), + ((16 * channels) ~/ 8), // block align + 0, 16, 0, // bit size + ...utf8.encode('data'), + (totalAudioLen & 0xff), + ((totalAudioLen >> 8) & 0xff), + ((totalAudioLen >> 16) & 0xff), + ((totalAudioLen >> 24) & 0xff), + ...data + ]); + + return header; + } + + static double calculateDecibel(Uint8List audioChunks){ + final sum = audioChunks.fold(0, (acc, audioChunk) => acc + audioChunk * audioChunk); + + // Root Mean Square (RMS) + final rms = sqrt(sum / audioChunks.length); + final db = 20 * _log10(rms); + return db; + } + + static double _log10(double x) => log(x) / log(10); +} \ No newline at end of file diff --git a/lib/src/core/utils/database_util.dart b/lib/src/core/utils/database_util.dart new file mode 100644 index 0000000..daca202 --- /dev/null +++ b/lib/src/core/utils/database_util.dart @@ -0,0 +1,81 @@ +import 'package:intl/intl.dart'; +import 'package:path/path.dart'; // db에 접근하기 위해 +import 'package:see_our_sounds/src/core/app_constants.dart'; +import 'package:see_our_sounds/src/models/audio_tagging_model.dart'; +import 'package:sqflite/sqflite.dart'; + +class DatabaseUtil { + final String tableName = AppDatabase.tableName; + final int version = AppDatabase.version; + static Database? _database; //SQLite database + + static final DatabaseUtil db = DatabaseUtil._internal(); + + // 처음 인스턴스 만들 때 실행되는 코드로 1회 발생 (초기화 코드) + DatabaseUtil._internal(); + + // singleton 패턴 구현에 사용 + // 새로운 인스턴스를 생성하지 않고, 클래스의 인스턴스 반환 (무조건 하나의 인스턴스) + factory DatabaseUtil() => db; + + Future get database async => + // db가 null인 경우 openDatabase를 통해 데이터베이스 열어야함 + _database ??= await _initDB('$tableName.db'); + + Future _initDB(String filePath) async { + final dbPath = await getDatabasesPath(); + final path = join(dbPath, filePath); + + // onCreate 버전이 다른 경우, 지정된 경로의 데이터베이스를 찾을 수 없을 경우 + return await openDatabase(path, version: version, onCreate: _createDB); + } + + // table 생성 + Future _createDB(Database database, int version) async { + const idType = 'INTEGER PRIMARY KEY AUTOINCREMENT'; + const textType = 'TEXT'; + const realType = 'REAL'; + const boolType = 'BOOLEAN'; + + await database.execute( + '''CREATE TABLE $tableName (id $idType, isAlert $boolType, label $textType, taggingRate $realType, date $textType)'''); + } + + Future getAllHistory() async { + final db = await database; + + const orderBy = 'id DESC'; + + var res = await db.query(tableName, orderBy: orderBy); + + if (res.isEmpty) { + return []; + } + return res.map((e) => AudioTaggingModel.fromJson(e)).toList(); + } + + Future addHistory( + AudioTaggingModel audioTagging, int currentDecibel) async { + final db = await database; + + audioTagging = audioTagging.copyWith( + date: DateFormat('dd/MM/yyyy HH:mm').format(DateTime.now()), + decibel: currentDecibel); + + db.insert(tableName, audioTagging.toJson(), + conflictAlgorithm: ConflictAlgorithm.replace); + } + + Future deleteHistory(int id) async { + final db = await database; + await db.rawDelete("DELETE FROM $tableName WHERE id = ?", [id]); + } + +// 앱이 종료되면, DB 자동으로 닫히지만, +// 원할 때 종료하고 싶은 경우 사용됨. + Future close() async { + final db = await database; + + db.close(); + } +} diff --git a/lib/src/data/repositories/audio_tagging_repo_impl.dart b/lib/src/data/repositories/audio_tagging_repo_impl.dart deleted file mode 100644 index 9aec197..0000000 --- a/lib/src/data/repositories/audio_tagging_repo_impl.dart +++ /dev/null @@ -1,109 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'dart:typed_data'; - -import 'package:flutter/cupertino.dart'; -import 'package:permission_handler/permission_handler.dart'; -import 'package:provider/provider.dart'; -import 'package:riverpod/riverpod.dart'; -import 'package:see_our_sounds/audio_record.dart'; -import 'package:see_our_sounds/src/domain/repositories/audio_tagging_repo.dart'; -import 'package:sound_stream/sound_stream.dart'; - -class AudioTaggingRepoImpl implements AudioTaggingRepo { - @override - List audioChunks = []; - - @override - StreamSubscription? audioStream; - - @override - bool isRecording = false; - - @override - RecorderStream recorder = RecorderStream(); - - @override - StreamSubscription? recorderStatus; - - @override - late Uint8List wav; - - @override - Future openRecord() async { - recorderStatus = recorder.status.listen((status) async { - var permissionStatus = await Permission.microphone.status; - if (permissionStatus.isGranted) { - recorder.start(); - isRecording = status == SoundStreamStatus.Playing; - } - }); - - createAudioChunks(); - - await Future.wait([ - recorder.initialize(), - ]); - } - - void createAudioChunks() { - audioStream = recorder.audioStream.listen((data) { - audioChunks.add(data); - // 대략 약 4초씩 - if (audioChunks.length > 60) { - audioChunks.removeAt(0); - } - }); - } - - @override - Future stopRecord() async { - await recorder.stop(); - await recorderStatus?.cancel(); - await audioStream?.cancel(); - audioChunks.clear(); - isRecording = false; - } - - @override - Uint8List toWAV(List audioChunks) { - var data = audioChunks.expand((i) => i).toList(); - var channels = 1; - int sampleRate = 16000; - int byteRate = (16 * sampleRate * channels) ~/ 8; - int totalAudioLen = data.length; - int totalDataLen = totalAudioLen + 36; - - Uint8List header = Uint8List.fromList([ - ...utf8.encode('RIFF'), - (totalDataLen & 0xff), - ((totalDataLen >> 8) & 0xff), - ((totalDataLen >> 16) & 0xff), - ((totalDataLen >> 24) & 0xff), - ...utf8.encode('WAVEfmt '), - // 4 bytes: size of 'fmt ' chunk - 16, 0, 0, 0, - // type of fmt - 1, 0, channels, 0, - (sampleRate & 0xff), - ((sampleRate >> 8) & 0xff), - ((sampleRate >> 16) & 0xff), - ((sampleRate >> 24) & 0xff), - (byteRate & 0xff), - ((byteRate >> 8) & 0xff), - ((byteRate >> 16) & 0xff), - ((byteRate >> 24) & 0xff), - ((16 * channels) ~/ 8), // block align - 0, 16, 0, // bit size - ...utf8.encode('data'), - (totalAudioLen & 0xff), - ((totalAudioLen >> 8) & 0xff), - ((totalAudioLen >> 16) & 0xff), - ((totalAudioLen >> 24) & 0xff), - ...data - ]); - - return header; - } -} diff --git a/lib/src/data/services/remote/audio_tagging_service.dart b/lib/src/data/services/remote/audio_tagging_service.dart deleted file mode 100644 index 5695553..0000000 --- a/lib/src/data/services/remote/audio_tagging_service.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:typed_data'; -import 'package:flutter/cupertino.dart'; -import 'package:http/http.dart' as http; -import 'package:see_our_sounds/src/config/config.dart'; -import 'package:see_our_sounds/src/data/models/audio_tagging_model.dart'; - -class AudioTaggingService { - - - Future getPong() async { - var request = http.Request('GET', Uri.parse(uriBase + uriPing)); - - http.StreamedResponse response = await request.send(); - - if (response.statusCode == 200) { - print(await response.stream.bytesToString()); - } else { - throw Exception(response.reasonPhrase); - } - } - - Future postAudio(Uint8List data) async { - final uri = Uri.parse(uriBase + uriUint); - var response = await http.post(uri, body: data); - if (response.statusCode == 200) { - var responseBody = jsonDecode(response.body); - return AudioTaggingModel.fromJson(responseBody); - } else { - throw Exception(response.reasonPhrase); - } - } - - Future audioTagging({required bool isRecording, required Uint8List data}) async { - final audioStreamController = StreamController(); - Timer? timer; - StreamSubscription? audioStreamSubscription; - - timer = Timer.periodic(const Duration(seconds: 1), (timer) { - audioStreamController.add(DateTime.now()); - }); - - audioStreamSubscription = - audioStreamController.stream.listen((event) async { - if (!isRecording) { - timer?.cancel(); - await audioStreamController.close(); - await audioStreamSubscription?.cancel(); - } else { - await postAudio(data); - } - }); - } - - // Stream audioTaggingStream(Uint8List audioChunks) async* { - // while (isRecording && audioChunks.isNotEmpty) { - // Uint8List wav = toWAV(audioChunks); - // AudioTaggingModel audioTaggingModel = await postAudio(wav); - // yield audioTaggingModel; - // } - // } -} diff --git a/lib/src/domain/repositories/audio_tagging_repo.dart b/lib/src/domain/repositories/audio_tagging_repo.dart deleted file mode 100644 index d9457d5..0000000 --- a/lib/src/domain/repositories/audio_tagging_repo.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'dart:async'; -import 'dart:typed_data'; - -import 'package:sound_stream/sound_stream.dart'; - -abstract class AudioTaggingRepo{ - late RecorderStream recorder; - StreamSubscription? recorderStatus; - StreamSubscription? audioStream; - late bool isRecording; - late List audioChunks; - late Uint8List wav; - - Uint8List toWAV(List audioChunks); - Future openRecord(); - Future stopRecord(); -} \ No newline at end of file diff --git a/lib/src/data/models/audio_tagging_model.dart b/lib/src/models/audio_tagging_model.dart similarity index 68% rename from lib/src/data/models/audio_tagging_model.dart rename to lib/src/models/audio_tagging_model.dart index 8047dfc..a9c560a 100644 --- a/lib/src/data/models/audio_tagging_model.dart +++ b/lib/src/models/audio_tagging_model.dart @@ -1,4 +1,7 @@ +import 'dart:ui'; + import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:intl/intl.dart'; part 'audio_tagging_model.freezed.dart'; @@ -6,15 +9,19 @@ part 'audio_tagging_model.g.dart'; @freezed class AudioTaggingModel with _$AudioTaggingModel { + // const AudioTaggingModel._(); + const factory AudioTaggingModel({ + @Default(null) int? id, @JsonKey(name: 'Alarm') required bool isAlert, @JsonKey(name: 'Label') required String label, @JsonKey(name: 'Tagging_rate') required double taggingRate, + @Default('') String date, + @Default(0) int decibel, }) = _AudioTaggingModel; + // String get date => DateFormat('dd/MM/yyyy HH:mm').format(DateTime.now()); + factory AudioTaggingModel.fromJson(Map json) => _$AudioTaggingModelFromJson(json); } - -// label: json['Label'], taggingRate: json['Tagging_rate']); -// List get props => [label, taggingRate]; diff --git a/lib/src/data/models/audio_tagging_model.freezed.dart b/lib/src/models/audio_tagging_model.freezed.dart similarity index 69% rename from lib/src/data/models/audio_tagging_model.freezed.dart rename to lib/src/models/audio_tagging_model.freezed.dart index 07033a9..446f887 100644 --- a/lib/src/data/models/audio_tagging_model.freezed.dart +++ b/lib/src/models/audio_tagging_model.freezed.dart @@ -20,12 +20,15 @@ AudioTaggingModel _$AudioTaggingModelFromJson(Map json) { /// @nodoc mixin _$AudioTaggingModel { + int? get id => throw _privateConstructorUsedError; @JsonKey(name: 'Alarm') bool get isAlert => throw _privateConstructorUsedError; @JsonKey(name: 'Label') String get label => throw _privateConstructorUsedError; @JsonKey(name: 'Tagging_rate') double get taggingRate => throw _privateConstructorUsedError; + String get date => throw _privateConstructorUsedError; + int get decibel => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -40,9 +43,12 @@ abstract class $AudioTaggingModelCopyWith<$Res> { _$AudioTaggingModelCopyWithImpl<$Res, AudioTaggingModel>; @useResult $Res call( - {@JsonKey(name: 'Alarm') bool isAlert, + {int? id, + @JsonKey(name: 'Alarm') bool isAlert, @JsonKey(name: 'Label') String label, - @JsonKey(name: 'Tagging_rate') double taggingRate}); + @JsonKey(name: 'Tagging_rate') double taggingRate, + String date, + int decibel}); } /// @nodoc @@ -58,11 +64,18 @@ class _$AudioTaggingModelCopyWithImpl<$Res, $Val extends AudioTaggingModel> @pragma('vm:prefer-inline') @override $Res call({ + Object? id = freezed, Object? isAlert = null, Object? label = null, Object? taggingRate = null, + Object? date = null, + Object? decibel = null, }) { return _then(_value.copyWith( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int?, isAlert: null == isAlert ? _value.isAlert : isAlert // ignore: cast_nullable_to_non_nullable @@ -75,6 +88,14 @@ class _$AudioTaggingModelCopyWithImpl<$Res, $Val extends AudioTaggingModel> ? _value.taggingRate : taggingRate // ignore: cast_nullable_to_non_nullable as double, + date: null == date + ? _value.date + : date // ignore: cast_nullable_to_non_nullable + as String, + decibel: null == decibel + ? _value.decibel + : decibel // ignore: cast_nullable_to_non_nullable + as int, ) as $Val); } } @@ -88,9 +109,12 @@ abstract class _$$_AudioTaggingModelCopyWith<$Res> @override @useResult $Res call( - {@JsonKey(name: 'Alarm') bool isAlert, + {int? id, + @JsonKey(name: 'Alarm') bool isAlert, @JsonKey(name: 'Label') String label, - @JsonKey(name: 'Tagging_rate') double taggingRate}); + @JsonKey(name: 'Tagging_rate') double taggingRate, + String date, + int decibel}); } /// @nodoc @@ -104,11 +128,18 @@ class __$$_AudioTaggingModelCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ + Object? id = freezed, Object? isAlert = null, Object? label = null, Object? taggingRate = null, + Object? date = null, + Object? decibel = null, }) { return _then(_$_AudioTaggingModel( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int?, isAlert: null == isAlert ? _value.isAlert : isAlert // ignore: cast_nullable_to_non_nullable @@ -121,6 +152,14 @@ class __$$_AudioTaggingModelCopyWithImpl<$Res> ? _value.taggingRate : taggingRate // ignore: cast_nullable_to_non_nullable as double, + date: null == date + ? _value.date + : date // ignore: cast_nullable_to_non_nullable + as String, + decibel: null == decibel + ? _value.decibel + : decibel // ignore: cast_nullable_to_non_nullable + as int, )); } } @@ -129,13 +168,19 @@ class __$$_AudioTaggingModelCopyWithImpl<$Res> @JsonSerializable() class _$_AudioTaggingModel implements _AudioTaggingModel { const _$_AudioTaggingModel( - {@JsonKey(name: 'Alarm') required this.isAlert, + {this.id = null, + @JsonKey(name: 'Alarm') required this.isAlert, @JsonKey(name: 'Label') required this.label, - @JsonKey(name: 'Tagging_rate') required this.taggingRate}); + @JsonKey(name: 'Tagging_rate') required this.taggingRate, + this.date = '', + this.decibel = 0}); factory _$_AudioTaggingModel.fromJson(Map json) => _$$_AudioTaggingModelFromJson(json); + @override + @JsonKey() + final int? id; @override @JsonKey(name: 'Alarm') final bool isAlert; @@ -145,10 +190,16 @@ class _$_AudioTaggingModel implements _AudioTaggingModel { @override @JsonKey(name: 'Tagging_rate') final double taggingRate; + @override + @JsonKey() + final String date; + @override + @JsonKey() + final int decibel; @override String toString() { - return 'AudioTaggingModel(isAlert: $isAlert, label: $label, taggingRate: $taggingRate)'; + return 'AudioTaggingModel(id: $id, isAlert: $isAlert, label: $label, taggingRate: $taggingRate, date: $date, decibel: $decibel)'; } @override @@ -156,15 +207,19 @@ class _$_AudioTaggingModel implements _AudioTaggingModel { return identical(this, other) || (other.runtimeType == runtimeType && other is _$_AudioTaggingModel && + (identical(other.id, id) || other.id == id) && (identical(other.isAlert, isAlert) || other.isAlert == isAlert) && (identical(other.label, label) || other.label == label) && (identical(other.taggingRate, taggingRate) || - other.taggingRate == taggingRate)); + other.taggingRate == taggingRate) && + (identical(other.date, date) || other.date == date) && + (identical(other.decibel, decibel) || other.decibel == decibel)); } @JsonKey(ignore: true) @override - int get hashCode => Object.hash(runtimeType, isAlert, label, taggingRate); + int get hashCode => + Object.hash(runtimeType, id, isAlert, label, taggingRate, date, decibel); @JsonKey(ignore: true) @override @@ -183,14 +238,18 @@ class _$_AudioTaggingModel implements _AudioTaggingModel { abstract class _AudioTaggingModel implements AudioTaggingModel { const factory _AudioTaggingModel( - {@JsonKey(name: 'Alarm') required final bool isAlert, - @JsonKey(name: 'Label') required final String label, - @JsonKey(name: 'Tagging_rate') required final double taggingRate}) = - _$_AudioTaggingModel; + {final int? id, + @JsonKey(name: 'Alarm') required final bool isAlert, + @JsonKey(name: 'Label') required final String label, + @JsonKey(name: 'Tagging_rate') required final double taggingRate, + final String date, + final int decibel}) = _$_AudioTaggingModel; factory _AudioTaggingModel.fromJson(Map json) = _$_AudioTaggingModel.fromJson; + @override + int? get id; @override @JsonKey(name: 'Alarm') bool get isAlert; @@ -201,6 +260,10 @@ abstract class _AudioTaggingModel implements AudioTaggingModel { @JsonKey(name: 'Tagging_rate') double get taggingRate; @override + String get date; + @override + int get decibel; + @override @JsonKey(ignore: true) _$$_AudioTaggingModelCopyWith<_$_AudioTaggingModel> get copyWith => throw _privateConstructorUsedError; diff --git a/lib/src/data/models/audio_tagging_model.g.dart b/lib/src/models/audio_tagging_model.g.dart similarity index 77% rename from lib/src/data/models/audio_tagging_model.g.dart rename to lib/src/models/audio_tagging_model.g.dart index 1d04bd6..a733f33 100644 --- a/lib/src/data/models/audio_tagging_model.g.dart +++ b/lib/src/models/audio_tagging_model.g.dart @@ -8,15 +8,21 @@ part of 'audio_tagging_model.dart'; _$_AudioTaggingModel _$$_AudioTaggingModelFromJson(Map json) => _$_AudioTaggingModel( + id: json['id'] as int? ?? null, isAlert: json['Alarm'] as bool, label: json['Label'] as String, taggingRate: (json['Tagging_rate'] as num).toDouble(), + date: json['date'] as String? ?? '', + decibel: json['decibel'] as int? ?? 0, ); Map _$$_AudioTaggingModelToJson( _$_AudioTaggingModel instance) => { + 'id': instance.id, 'Alarm': instance.isAlert, 'Label': instance.label, 'Tagging_rate': instance.taggingRate, + 'date': instance.date, + 'decibel': instance.decibel, }; diff --git a/lib/src/screen/provider/provider.dart b/lib/src/providers/audio_tagging_api_provider.dart similarity index 100% rename from lib/src/screen/provider/provider.dart rename to lib/src/providers/audio_tagging_api_provider.dart diff --git a/lib/src/providers/audio_tagging_db_provider.dart b/lib/src/providers/audio_tagging_db_provider.dart new file mode 100644 index 0000000..f474353 --- /dev/null +++ b/lib/src/providers/audio_tagging_db_provider.dart @@ -0,0 +1,6 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:see_our_sounds/src/core/utils/database_util.dart'; +import 'package:see_our_sounds/src/repositories/audio_tagging_repository.dart'; + +final audioTaggingDBProvider = + ChangeNotifierProvider((ref) => AudioTaggingRepositoryImpl(DatabaseUtil())); diff --git a/lib/src/providers/audio_tagging_provider.dart b/lib/src/providers/audio_tagging_provider.dart new file mode 100644 index 0000000..b999183 --- /dev/null +++ b/lib/src/providers/audio_tagging_provider.dart @@ -0,0 +1,17 @@ +// import 'dart:async'; +// import 'dart:typed_data'; +// +// import 'package:flutter_riverpod/flutter_riverpod.dart'; +// import 'package:permission_handler/permission_handler.dart'; +// import 'package:see_our_sounds/src/models/audio_tagging_model.dart'; +// import 'package:sound_stream/sound_stream.dart'; +// +// final audioTaggingProvider = StreamProvider((ref) { +// RecorderStream recorder = RecorderStream(); +// +// recorder.initialize(); +// +// recorder.status.li +// +// +// }); diff --git a/lib/src/providers/stt_provider.dart b/lib/src/providers/stt_provider.dart new file mode 100644 index 0000000..378cd8c --- /dev/null +++ b/lib/src/providers/stt_provider.dart @@ -0,0 +1,9 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:see_our_sounds/src/repositories/stt_repository.dart'; +import 'package:speech_to_text/speech_to_text.dart'; + +final sttProvider = ChangeNotifierProvider((ref){ + final stt = SpeechToText(); + return STTRepositoryImpl(speechToText: stt); +}); + diff --git a/lib/src/repositories/audio_tagging_repository.dart b/lib/src/repositories/audio_tagging_repository.dart new file mode 100644 index 0000000..27514f0 --- /dev/null +++ b/lib/src/repositories/audio_tagging_repository.dart @@ -0,0 +1,54 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:intl/intl.dart'; +import 'package:see_our_sounds/src/core/app_constants.dart'; +import 'package:see_our_sounds/src/core/utils/database_util.dart'; +import 'package:see_our_sounds/src/models/audio_tagging_model.dart'; + +abstract class AudioTaggingRepository { + Future getAllHistory(); + + Future addHistory(AudioTaggingModel audioTagging, int currentDecibel); + + Future deleteHistory(int id); +} + +class AudioTaggingRepositoryImpl extends ChangeNotifier + implements AudioTaggingRepository { + AudioTaggingRepositoryImpl(this._databaseUtil); + + final databaseUtil = DatabaseUtil(); + final DatabaseUtil _databaseUtil; + List _history = []; + + List get history => _history; + + @override + Future getAllHistory() async { + return _databaseUtil.getAllHistory().then((value) { + _history = value; + }).whenComplete(notifyListeners); + } + + @override + Future addHistory( + AudioTaggingModel audioTagging, int currentDecibel) async { + return _databaseUtil + .addHistory(audioTagging, currentDecibel) + .whenComplete(() { + audioTagging = audioTagging.copyWith( + date: DateFormat('dd/MM/yyyy HH:mm').format(DateTime.now()), + decibel: currentDecibel); + _history!.add(audioTagging); + notifyListeners(); + }); + } + + @override + Future deleteHistory(int id) async { + return _databaseUtil.deleteHistory(id).whenComplete(() { + _history!.removeAt(id); + notifyListeners(); + }); + } +} diff --git a/lib/src/repositories/stt_repository.dart b/lib/src/repositories/stt_repository.dart new file mode 100644 index 0000000..71d08b3 --- /dev/null +++ b/lib/src/repositories/stt_repository.dart @@ -0,0 +1,43 @@ +import 'package:flutter/cupertino.dart'; +import 'package:speech_to_text/speech_to_text.dart'; + +abstract class STTRepository { + Future initialize(); + + Future startListening(); + + Future stopListening(); + + Future cancelListening(); +} + +class STTRepositoryImpl extends ChangeNotifier implements STTRepository { + STTRepositoryImpl({required this.speechToText}) { + initialize(); + } + + SpeechToText speechToText; + + @override + Future initialize() async { + await speechToText.initialize(); + } + + @override + Future startListening() async { + speechToText.listen(onResult: (res) => notifyListeners()); + notifyListeners(); + } + + @override + Future stopListening() async { + await speechToText.stop(); + notifyListeners(); + } + + @override + Future cancelListening() async { + await speechToText.cancel(); + notifyListeners(); + } +} diff --git a/lib/src/screen/home/home_screen.dart b/lib/src/screen/home/home_screen.dart deleted file mode 100644 index 857adc2..0000000 --- a/lib/src/screen/home/home_screen.dart +++ /dev/null @@ -1,34 +0,0 @@ -// import 'package:flutter_riverpod/flutter_riverpod.dart'; -// import 'package:flutter/material.dart'; -// import 'package:see_our_sounds/src/data/repositories/audio_tagging_repo_impl.dart'; -// import 'package:see_our_sounds/src/screen/provider/audio_tagging_provider.dart'; -// -// class HomeScreen extends ConsumerWidget { -// const HomeScreen({Key? key}) : super(key: key); -// -// @override -// Widget build(BuildContext context, WidgetRef ref) { -// bool isRecording = AudioTaggingRepoImpl().isRecording; -// final openRecord = ref.watch(openRecordProvider); -// final stopRecord = ref.watch(stopRecordProvider); -// final label = ref.read(labelProvider); -// final taggingRate = ref.read(taggingRateProvider); -// return Scaffold( -// body: Column( -// children: [ -// IconButton( -// iconSize: 90, -// icon: Icon(isRecording ? Icons.mic : Icons.mic_off), -// onPressed: () async { -// if (isRecording) { -// openRecord; -// } else { -// stopRecord; -// } -// }, -// ), -// ], -// ), -// ); -// } -// } diff --git a/lib/src/screen/provider/audio_tagging_provider.dart b/lib/src/screen/provider/audio_tagging_provider.dart deleted file mode 100644 index 610dd99..0000000 --- a/lib/src/screen/provider/audio_tagging_provider.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'dart:async'; -import 'dart:typed_data'; - -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:see_our_sounds/src/data/models/audio_tagging_model.dart'; -import 'package:see_our_sounds/src/data/repositories/audio_tagging_repo_impl.dart'; -import 'package:see_our_sounds/src/data/services/remote/audio_tagging_service.dart'; -import 'package:see_our_sounds/src/domain/repositories/audio_tagging_repo.dart'; - -final auidoTaggingProvider = - Provider((ref) => AudioTaggingRepoImpl()); - -final audioTaggingServiceProvider = - Provider((ref) => AudioTaggingService()); - - - -final openRecordProvider = FutureProvider((ref) { - final audioTaggingRepo = ref.watch(auidoTaggingProvider); - return audioTaggingRepo.openRecord(); -}); - -// final provider = -// StreamProvider.autoDispose((ref) { -// final controller = StreamController(); -// -// ref.onDispose(() { -// controller.close(); -// }); -// }); diff --git a/lib/src/screens/history_screen.dart b/lib/src/screens/history_screen.dart new file mode 100644 index 0000000..d35748c --- /dev/null +++ b/lib/src/screens/history_screen.dart @@ -0,0 +1,26 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter/material.dart'; +import 'package:see_our_sounds/src/models/audio_tagging_model.dart'; +import 'package:see_our_sounds/src/providers/audio_tagging_db_provider.dart'; + +class HistoryScreen extends ConsumerWidget { + const HistoryScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + List history = ref.watch(audioTaggingDBProvider).history; + + // Let's render the todos in a scrollable list view + return Scaffold( + body: SafeArea( + child: ListView.builder( + itemCount: history.length, + itemBuilder: (context, idx) { + return ListTile( + title: Text(history[idx].label), + ); + }), + ), + ); + } +} diff --git a/lib/src/screens/home/audio_waveform.dart b/lib/src/screens/home/audio_waveform.dart new file mode 100644 index 0000000..6ea340c --- /dev/null +++ b/lib/src/screens/home/audio_waveform.dart @@ -0,0 +1,29 @@ +import 'package:audio_wave/audio_wave.dart'; +import 'package:flutter/material.dart'; +import 'package:see_our_sounds/src/core/app_constants.dart'; + +class AudioWaveform extends StatelessWidget { + final List decibelHistory; + + const AudioWaveform({Key? key, required this.decibelHistory}) + : super(key: key); + + @override + Widget build(BuildContext context) { + Size size = MediaQuery.of(context).size; + return decibelHistory.isNotEmpty + ? AudioWave( + height: 160, + width: size.width * .9, + spacing: decibelHistory.length > 10 ? 2.5 : 20, + animationLoop: 1, + beatRate: const Duration(milliseconds: 30), + bars: List.generate( + decibelHistory.length, + (index) => AudioWaveBar( + heightFactor: decibelHistory[index] * 0.005, + color: AppColor.primaryColor)), + ) + : const SizedBox(); + } +} diff --git a/lib/src/screens/home/decibel_history_chart.dart b/lib/src/screens/home/decibel_history_chart.dart new file mode 100644 index 0000000..4f1f558 --- /dev/null +++ b/lib/src/screens/home/decibel_history_chart.dart @@ -0,0 +1,41 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +class DecibelHistoryChart extends StatelessWidget { + final List decibelHistory; + + const DecibelHistoryChart({Key? key, required this.decibelHistory}) + : super(key: key); + + @override + Widget build(BuildContext context) { + Size size = MediaQuery.of(context).size; + return decibelHistory.isNotEmpty + ? SizedBox( + height: size.height * .2, + width: size.width * .9, + child: LineChart(LineChartData( + minX: 0, + maxX: 70, + maxY: 130, + minY: 5, + titlesData: FlTitlesData(show: false), + borderData: FlBorderData(show: false), + gridData: FlGridData(show: false), + lineBarsData: [ + LineChartBarData( + spots: decibelHistory + .asMap() + .entries + .map((e) => FlSpot(e.key.toDouble(), e.value.floor().toDouble() - 20)) + .toList(), + isCurved: true, + dotData: FlDotData(show: false), + barWidth: 4, + color: Color(0xff4285f4)), + ])), + ) + : const SizedBox(); + } +} diff --git a/lib/src/screens/home/home_screen.dart b/lib/src/screens/home/home_screen.dart new file mode 100644 index 0000000..54ed8f0 --- /dev/null +++ b/lib/src/screens/home/home_screen.dart @@ -0,0 +1,19 @@ +// import 'package:flutter_riverpod/flutter_riverpod.dart'; +// import 'package:flutter/material.dart'; +// import 'package:see_our_sounds/src/data/repositories/audio_tagging_repo_impl.dart'; +// import 'package:see_our_sounds/src/screen/provider/audio_tagging_provider.dart'; +// +// class HomeScreen extends ConsumerWidget { +// const HomeScreen({Key? key}) : super(key: key); +// +// @override +// Widget build(BuildContext context, WidgetRef ref) { +// return Scaffold( +// body: Column( +// children: [ +// +// ], +// ), +// ); +// } +// } diff --git a/lib/src/screen/setting/audio_setting_screen.dart b/lib/src/screens/setting/audio_setting_screen.dart similarity index 100% rename from lib/src/screen/setting/audio_setting_screen.dart rename to lib/src/screens/setting/audio_setting_screen.dart diff --git a/lib/src/screen/setting/setting_gridview_card.dart b/lib/src/screens/setting/setting_gridview_card.dart similarity index 100% rename from lib/src/screen/setting/setting_gridview_card.dart rename to lib/src/screens/setting/setting_gridview_card.dart diff --git a/lib/src/services/audio_tagging_service.dart b/lib/src/services/audio_tagging_service.dart new file mode 100644 index 0000000..a6ceced --- /dev/null +++ b/lib/src/services/audio_tagging_service.dart @@ -0,0 +1,50 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:typed_data'; +import 'package:flutter/cupertino.dart'; +import 'package:http/http.dart' as http; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:record/record.dart'; +import 'package:see_our_sounds/src/core/core.dart'; +import 'package:see_our_sounds/src/core/app_constants.dart'; +import 'package:see_our_sounds/src/models/audio_tagging_model.dart'; +import 'package:sound_stream/sound_stream.dart'; + +final audioTaggingServiceProvider = + Provider((ref) => AudioTaggingServiceImpl()); + +abstract class AudioTaggingService { + Future getPong(); + + Future postAudio(Uint8List data); +} + +class AudioTaggingServiceImpl extends AudioTaggingService { + RecorderStream recorderStream = RecorderStream(); + + @override + Future getPong() async { + var request = + http.Request('GET', Uri.parse(AppUri.uriBase + AppUri.uriPing)); + + http.StreamedResponse response = await request.send(); + + if (response.statusCode == 200) { + debugPrint(await response.stream.bytesToString()); + } else { + throw Exception(response.reasonPhrase); + } + } + + @override + Future postAudio(Uint8List data) async { + final uri = Uri.parse(AppUri.uriBase + AppUri.uriUint); + var response = await http.post(uri, body: data); + if (response.statusCode == 200) { + var responseBody = jsonDecode(response.body); + return AudioTaggingModel.fromJson(responseBody); + } else { + throw Exception(response.reasonPhrase); + } + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index f09ea53..bd4e31a 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,9 +8,11 @@ import Foundation import flutter_local_notifications import record_macos import speech_to_text_macos +import sqflite func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) RecordMacosPlugin.register(with: registry.registrar(forPlugin: "RecordMacosPlugin")) SpeechToTextMacosPlugin.register(with: registry.registrar(forPlugin: "SpeechToTextMacosPlugin")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) } diff --git a/pubspec.lock b/pubspec.lock index df8cfe0..8f92bb8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -29,13 +29,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.9.0" - avatar_glow: + audio_streamer: + dependency: transitive + description: + name: audio_streamer + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + audio_wave: dependency: "direct main" description: - name: avatar_glow + name: audio_wave url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "0.1.5" boolean_selector: dependency: transitive description: @@ -169,6 +176,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.7.8" + equatable: + dependency: transitive + description: + name: equatable + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: @@ -197,6 +211,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.1" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + url: "https://pub.dartlang.org" + source: hosted + version: "0.61.0" flutter: dependency: "direct main" description: flutter @@ -268,6 +289,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.2.0" + gauge_indicator: + dependency: "direct main" + description: + name: gauge_indicator + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.3" glob: dependency: transitive description: @@ -303,6 +331,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.0.2" + intl: + dependency: "direct main" + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.18.0" io: dependency: transitive description: @@ -380,6 +415,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + noise_meter: + dependency: "direct main" + description: + name: noise_meter + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" package_config: dependency: transitive description: @@ -388,7 +430,7 @@ packages: source: hosted version: "2.1.0" path: - dependency: transitive + dependency: "direct main" description: name: path url: "https://pub.dartlang.org" @@ -609,6 +651,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" + sqflite: + dependency: "direct main" + description: + name: sqflite + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.6" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.3" stack_trace: dependency: transitive description: @@ -644,6 +700,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.1" + syncfusion_flutter_core: + dependency: transitive + description: + name: syncfusion_flutter_core + url: "https://pub.dartlang.org" + source: hosted + version: "20.4.54" + syncfusion_flutter_gauges: + dependency: "direct main" + description: + name: syncfusion_flutter_gauges + url: "https://pub.dartlang.org" + source: hosted + version: "20.4.54" + synchronized: + dependency: transitive + description: + name: synchronized + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" term_glyph: dependency: transitive description: @@ -723,4 +800,4 @@ packages: version: "3.1.1" sdks: dart: ">=2.18.2 <3.0.0" - flutter: ">=3.0.0" + flutter: ">=3.3.0" diff --git a/pubspec.yaml b/pubspec.yaml index 47eb41b..e198b05 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -26,59 +26,42 @@ dependencies: riverpod: ^2.2.0 flutter_riverpod: ^2.2.0 speech_to_text: ^6.1.1 - avatar_glow: ^2.0.2 + noise_meter: ^3.1.0 + fl_chart: ^0.61.0 + intl: ^0.18.0 + audio_wave: ^0.1.5 + gauge_indicator: ^0.3.3 + syncfusion_flutter_gauges: ^20.4.54 + sqflite: ^2.2.6 + path: ^1.8.2 dev_dependencies: flutter_test: sdk: flutter - - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. flutter_lints: ^2.0.0 build_runner: ^2.3.3 json_serializable: ^6.6.1 -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec -# The following section is specific to Flutter packages. flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true + assets: + - assets/ + - assets/icon_dark/ + - assets/icon_light/ - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages + fonts: + - family: NotoSansKR + fonts: + - asset: assets/fonts/NotoSansKR-Black.otf + weight: 900 + - asset: assets/fonts/NotoSansKR-Bold.otf + weight: 700 + - asset: assets/fonts/NotoSansKR-Medium.otf + weight: 500 + - asset: assets/fonts/NotoSansKR-Regular.otf + weight: 400 + - asset: assets/fonts/NotoSansKR-Light.otf + weight: 300 + - asset: assets/fonts/NotoSansKR-Thin.otf + weight: 100