Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Audio message recorder and player functionality implemented #615

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/qaul/flutter/windows.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
## Prerequisits
### Install Visual Studio

In order to develop for the windows desktop platform you need to have 'Microsoft Visual Studio 2019' or 'Microsoft Visual Studio 2022' installed.
In order to develop for the windows desktop platform you need to have 'Microsoft Visual Studio 2022' installed.

<https://visualstudio.microsoft.com/downloads/>

In Microsoft Visual Studio 2019/2022, you need to have 'Desktop development with C++' workload installed.
In Microsoft Visual Studio 2022, you need to have 'Desktop development with C++' workload installed.

### Install Git for Windows

Expand Down Expand Up @@ -73,7 +73,7 @@ cargo build
# copy libqaul.dll to flutter runner
## this step is required in order to run flutter app
## create the location folder if it does not yet exist
cp .\target\debug\libqaul.dll ..\qaul_ui\build\windows\runner\Debug\
cp .\target\debug\libqaul.dll ..\qaul_ui\build\windows\x64\runner\Debug\
```

### Build and Run Windows Desktop App
Expand Down
2 changes: 1 addition & 1 deletion qaul_ui/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ group = "net.qaul.app"
version = flutterVersionName

android {
compileSdkVersion 33
compileSdkVersion 34

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
Expand Down
5 changes: 5 additions & 0 deletions qaul_ui/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>

<!-- Audio messages support -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- Optional: Add this permission if you want to use bluetooth telephony device like headset/earbuds (min SDK: 23) -->
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

<!-- The queries element is required to open URLs, starting on Android 11 (API 30) -->
<queries>
<intent>
Expand Down
15 changes: 14 additions & 1 deletion qaul_ui/ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
PODS:
- audioplayers_darwin (0.0.1):
- Flutter
- better_open_file (0.0.1):
- Flutter
- device_info_plus (0.0.1):
Expand Down Expand Up @@ -53,6 +55,9 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- record_darwin (1.0.0):
- Flutter
- FlutterMacOS
- SDWebImage (5.15.2):
- SDWebImage/Core (= 5.15.2)
- SDWebImage/Core (5.15.2)
Expand All @@ -66,6 +71,7 @@ PODS:
- Flutter

DEPENDENCIES:
- audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/ios`)
- better_open_file (from `.symlinks/plugins/better_open_file/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
Expand All @@ -77,6 +83,7 @@ DEPENDENCIES:
- integration_test (from `.symlinks/plugins/integration_test/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- record_darwin (from `.symlinks/plugins/record_darwin/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- uni_links (from `.symlinks/plugins/uni_links/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
Expand All @@ -89,6 +96,8 @@ SPEC REPOS:
- SwiftyGif

EXTERNAL SOURCES:
audioplayers_darwin:
:path: ".symlinks/plugins/audioplayers_darwin/ios"
better_open_file:
:path: ".symlinks/plugins/better_open_file/ios"
device_info_plus:
Expand All @@ -111,6 +120,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
record_darwin:
:path: ".symlinks/plugins/record_darwin/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
uni_links:
Expand All @@ -119,6 +130,7 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/url_launcher_ios/ios"

SPEC CHECKSUMS:
audioplayers_darwin: 877d9a4d06331c5c374595e46e16453ac7eafa40
better_open_file: 03cf320415d4d3f46b6e00adc4a567d76c1a399d
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
Expand All @@ -132,6 +144,7 @@ SPEC CHECKSUMS:
integration_test: 13825b8a9334a850581300559b8839134b124670
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
record_darwin: 1f6619f2abac4d1ca91d3eeab038c980d76f1517
SDWebImage: 8ab87d4b3e5cc4927bd47f78db6ceb0b94442577
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
Expand All @@ -140,4 +153,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: c4c93c5f6502fe2754f48404d3594bf779584011

COCOAPODS: 1.14.3
COCOAPODS: 1.15.2
2 changes: 2 additions & 0 deletions qaul_ui/ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,7 @@
<string>You can send photos as attachments to your peers. For that, please grant access to the application.</string>
<key>NSCameraUsageDescription</key>
<string>You can take pictures and send them as attachments to your peers. For that, please grant access to the application.</string>
<key>NSMicrophoneUsageDescription</key>
<string>You can send audio messages to your peers. For that, please grant access to the application.</string>
</dict>
</plist>
8 changes: 8 additions & 0 deletions qaul_ui/lib/qaul_app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ class QaulApp extends PlatformAwareBuilder {
return AdaptiveTheme(
light: ThemeData(
brightness: Brightness.light,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.lightBlue,
brightness: Brightness.light,
),
primarySwatch: Colors.lightBlue,
scaffoldBackgroundColor: Colors.white,
navigationBarTheme: const NavigationBarThemeData(
Expand Down Expand Up @@ -70,6 +74,10 @@ class QaulApp extends PlatformAwareBuilder {
),
dark: ThemeData(
brightness: Brightness.dark,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.lightBlue,
brightness: Brightness.dark,
),
primarySwatch: Colors.lightBlue,
visualDensity: VisualDensity.adaptivePlatformDensity,
iconTheme: const IconThemeData(color: Colors.white),
Expand Down
182 changes: 182 additions & 0 deletions qaul_ui/lib/screens/home/tabs/chat/widgets/audio_message_widget.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
part of 'chat.dart';

class AudioMessageWidget extends StatefulWidget {
const AudioMessageWidget({
Key? key,
required this.message,
required this.messageWidth,
this.isDefaultUser = false,
}) : super(key: key);

final types.AudioMessage message;

final int messageWidth;

final bool isDefaultUser;

@override
State<AudioMessageWidget> createState() => _AudioMessageWidgetState();
}

class _AudioMessageWidgetState extends State<AudioMessageWidget> {
double get _controlSize => (widget.messageWidth.toDouble()) / 10;

final _audioPlayer = AudioPlayer()..setReleaseMode(ReleaseMode.stop);

Duration? _position;

Duration? _duration;

String? audioPath;

late StreamSubscription<void> _playerStateChangedSubscription;

late StreamSubscription<Duration?> _durationChangedSubscription;

late StreamSubscription<Duration> _positionChangedSubscription;

Color get primaryColor => Theme.of(context).colorScheme.primary;

Color get containerColor => Theme.of(context).colorScheme.primaryContainer;

Color get backgroundColor => Theme.of(context).colorScheme.background;

@override
void initState() {
_playerStateChangedSubscription =
_audioPlayer.onPlayerComplete.listen((state) async {
await stop();
});
_positionChangedSubscription = _audioPlayer.onPositionChanged.listen(
(position) => setState(() {
_position = position;
}),
);
_durationChangedSubscription = _audioPlayer.onDurationChanged.listen(
(duration) => setState(() {
_duration = duration;
}),
);

audioPath = widget.message.uri;
_getDuration();
_audioPlayer.setSource(_source);
super.initState();
}

@override
void dispose() {
_playerStateChangedSubscription.cancel();
_positionChangedSubscription.cancel();
_durationChangedSubscription.cancel();
_audioPlayer.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
final ttheme = Theme.of(context).textTheme;

return Container(
padding: const EdgeInsetsDirectional.fromSTEB(16, 4, 8, 8),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(height: _controlSize / 2),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
audioControls(),
Expanded(child: audioSlider()),
],
),
Padding(
padding: const EdgeInsetsDirectional.only(end: 16),
child: Text(
'${_duration?.inSeconds ?? 0.0} Seconds',
style: ttheme.labelLarge?.copyWith(
color: backgroundColor,
fontStyle: FontStyle.italic,
),
),
),
],
),
);
}

Widget audioControls() {
return ClipOval(
child: Material(
color: containerColor,
child: InkWell(
child: SizedBox(
width: _controlSize,
height: _controlSize,
child: Icon(
_audioPlayer.state == PlayerState.playing
? Icons.pause
: Icons.play_arrow,
color: primaryColor,
),
),
onTap: () {
if (_audioPlayer.state == PlayerState.playing) {
pause();
} else {
play();
}
},
),
),
);
}

Widget audioSlider() {
bool canSetValue = false;
final duration = _duration;
final position = _position;

if (duration != null && position != null) {
canSetValue = position.inMilliseconds >= 0;
canSetValue &= position.inMilliseconds < duration.inMilliseconds;
}

return Slider(
activeColor: primaryColor,
inactiveColor: backgroundColor,
onChanged: (v) {
if (duration != null) {
final position = v * duration.inMilliseconds;
_audioPlayer.seek(Duration(milliseconds: position.round()));
}
},
value: canSetValue && duration != null && position != null
? position.inMilliseconds / duration.inMilliseconds
: 0.0,
);
}

Future<void> play() => _audioPlayer.play(_source);

Future<void> pause() async {
await _audioPlayer.pause();
setState(() {});
}

Future<void> stop() async {
await _audioPlayer.stop();
setState(() {});
}

Future<void> _getDuration() async {
await _audioPlayer.setSourceUrl(audioPath!);
final duration = await _audioPlayer.getDuration();
_duration = duration;
}

Source get _source => DeviceFileSource(audioPath!);
}
Loading
Loading