parent
a6d962f9ba
commit
bf2f412de9
@ -1,26 +0,0 @@
|
||||
pluginManagement {
|
||||
def flutterSdkPath = {
|
||||
def properties = new Properties()
|
||||
file("local.properties").withInputStream { properties.load(it) }
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
return flutterSdkPath
|
||||
}()
|
||||
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "8.7.3" apply false
|
||||
id "org.jetbrains.kotlin.android" version "2.1.10" apply false
|
||||
id "com.google.gms.google-services" version "4.3.8" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
@ -1,3 +0,0 @@
|
||||
Hermes.AppDir
|
||||
*.AppImage
|
||||
*.AppImage.zsync
|
||||
@ -1,4 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
exec ./hermes
|
||||
@ -1,9 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Version=1.0
|
||||
Name=Hermes
|
||||
Comment=Matrix Client. Chat with your friends
|
||||
Exec=AppRun
|
||||
Icon=hermes
|
||||
Terminal=false
|
||||
Categories=Network;Chat;InstantMessaging;X-Matrix;
|
||||
@ -1,24 +0,0 @@
|
||||
# Hermes AppImage
|
||||
|
||||
Hermes is provided as AppImage too. To Download, visit hermes.im.
|
||||
|
||||
## Building
|
||||
|
||||
- Ensure you install `appimagetool`
|
||||
|
||||
```shell
|
||||
flutter build linux
|
||||
|
||||
# copy binaries to appimage dir
|
||||
cp -r build/linux/{x64,arm64}/release/bundle appimage/Hermes.AppDir
|
||||
cd appimage
|
||||
|
||||
# prepare AppImage files
|
||||
cp Hermes.desktop Hermes.AppDir/
|
||||
mkdir -p Hermes.AppDir/usr/share/icons
|
||||
cp ../assets/logo.svg Hermes.AppDir/hermes.svg
|
||||
cp AppRun Hermes.AppDir
|
||||
|
||||
# build the AppImage
|
||||
appimagetool Hermes.AppDir
|
||||
```
|
||||
@ -1,2 +1,2 @@
|
||||
# im.hermes.Hermes
|
||||
# im.pantheon.Hermes
|
||||
8b25b37b-f160-4350-b4f6-9a04554e8f9e
|
||||
|
||||
@ -1,255 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:path/path.dart' as path_lib;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:record/record.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
import 'package:hermes/l10n/l10n.dart';
|
||||
import 'package:hermes/config/app_config.dart';
|
||||
import 'package:hermes/config/setting_keys.dart';
|
||||
import 'package:hermes/utils/platform_infos.dart';
|
||||
import 'package:hermes/widgets/matrix.dart';
|
||||
import 'events/audio_player.dart';
|
||||
|
||||
class RecordingDialog extends StatefulWidget {
|
||||
const RecordingDialog({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
RecordingDialogState createState() => RecordingDialogState();
|
||||
}
|
||||
|
||||
class RecordingDialogState extends State<RecordingDialog> {
|
||||
Timer? _recorderSubscription;
|
||||
Duration _duration = Duration.zero;
|
||||
|
||||
bool error = false;
|
||||
|
||||
final _audioRecorder = AudioRecorder();
|
||||
final List<double> amplitudeTimeline = [];
|
||||
|
||||
String? fileName;
|
||||
|
||||
Future<void> startRecording() async {
|
||||
final store = Matrix.of(context).store;
|
||||
try {
|
||||
final codec = kIsWeb
|
||||
// Web seems to create webm instead of ogg when using opus encoder
|
||||
// which does not play on iOS right now. So we use wav for now:
|
||||
? AudioEncoder.wav
|
||||
// Everywhere else we use opus if supported by the platform:
|
||||
: await _audioRecorder.isEncoderSupported(AudioEncoder.opus)
|
||||
? AudioEncoder.opus
|
||||
: AudioEncoder.aacLc;
|
||||
fileName =
|
||||
'recording${DateTime.now().microsecondsSinceEpoch}.${codec.fileExtension}';
|
||||
String? path;
|
||||
if (!kIsWeb) {
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
path = path_lib.join(tempDir.path, fileName);
|
||||
}
|
||||
|
||||
final result = await _audioRecorder.hasPermission();
|
||||
if (result != true) {
|
||||
setState(() => error = true);
|
||||
return;
|
||||
}
|
||||
await WakelockPlus.enable();
|
||||
|
||||
await _audioRecorder.start(
|
||||
RecordConfig(
|
||||
bitRate: AppSettings.audioRecordingBitRate.getItem(store),
|
||||
sampleRate: AppSettings.audioRecordingSamplingRate.getItem(store),
|
||||
numChannels: AppSettings.audioRecordingNumChannels.getItem(store),
|
||||
autoGain: AppSettings.audioRecordingAutoGain.getItem(store),
|
||||
echoCancel: AppSettings.audioRecordingEchoCancel.getItem(store),
|
||||
noiseSuppress: AppSettings.audioRecordingNoiseSuppress.getItem(store),
|
||||
encoder: codec,
|
||||
),
|
||||
path: path ?? '',
|
||||
);
|
||||
setState(() => _duration = Duration.zero);
|
||||
_recorderSubscription?.cancel();
|
||||
_recorderSubscription =
|
||||
Timer.periodic(const Duration(milliseconds: 100), (_) async {
|
||||
final amplitude = await _audioRecorder.getAmplitude();
|
||||
var value = 100 + amplitude.current * 2;
|
||||
value = value < 1 ? 1 : value;
|
||||
amplitudeTimeline.add(value);
|
||||
setState(() {
|
||||
_duration += const Duration(milliseconds: 100);
|
||||
});
|
||||
});
|
||||
} catch (_) {
|
||||
setState(() => error = true);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
startRecording();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WakelockPlus.disable();
|
||||
_recorderSubscription?.cancel();
|
||||
_audioRecorder.stop();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _stopAndSend() async {
|
||||
_recorderSubscription?.cancel();
|
||||
final path = await _audioRecorder.stop();
|
||||
|
||||
if (path == null) throw ('Recording failed!');
|
||||
const waveCount = AudioPlayerWidget.wavesCount;
|
||||
final step = amplitudeTimeline.length < waveCount
|
||||
? 1
|
||||
: (amplitudeTimeline.length / waveCount).round();
|
||||
final waveform = <int>[];
|
||||
for (var i = 0; i < amplitudeTimeline.length; i += step) {
|
||||
waveform.add((amplitudeTimeline[i] / 100 * 1024).round());
|
||||
}
|
||||
Navigator.of(context, rootNavigator: false).pop<RecordingResult>(
|
||||
RecordingResult(
|
||||
path: path,
|
||||
duration: _duration.inMilliseconds,
|
||||
waveform: waveform,
|
||||
fileName: fileName,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
const maxDecibalWidth = 64.0;
|
||||
final time =
|
||||
'${_duration.inMinutes.toString().padLeft(2, '0')}:${(_duration.inSeconds % 60).toString().padLeft(2, '0')}';
|
||||
final content = error
|
||||
? Text(L10n.of(context).oopsSomethingWentWrong)
|
||||
: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 16,
|
||||
height: 16,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: amplitudeTimeline.reversed
|
||||
.take(26)
|
||||
.toList()
|
||||
.reversed
|
||||
.map(
|
||||
(amplitude) => Container(
|
||||
margin: const EdgeInsets.only(left: 2),
|
||||
width: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primary,
|
||||
borderRadius:
|
||||
BorderRadius.circular(AppConfig.borderRadius),
|
||||
),
|
||||
height: maxDecibalWidth * (amplitude / 100),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
SizedBox(
|
||||
width: 48,
|
||||
child: Text(time),
|
||||
),
|
||||
],
|
||||
);
|
||||
if (PlatformInfos.isCupertinoStyle) {
|
||||
return CupertinoAlertDialog(
|
||||
content: content,
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
onPressed: () => Navigator.of(context, rootNavigator: false).pop(),
|
||||
child: Text(
|
||||
L10n.of(context).cancel,
|
||||
style: TextStyle(
|
||||
color: theme.textTheme.bodyMedium?.color?.withAlpha(150),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (error != true)
|
||||
CupertinoDialogAction(
|
||||
onPressed: _stopAndSend,
|
||||
child: Text(L10n.of(context).send),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return AlertDialog(
|
||||
content: content,
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context, rootNavigator: false).pop(),
|
||||
child: Text(
|
||||
L10n.of(context).cancel,
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (error != true)
|
||||
TextButton(
|
||||
onPressed: _stopAndSend,
|
||||
child: Text(L10n.of(context).send),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RecordingResult {
|
||||
final String path;
|
||||
final int duration;
|
||||
final List<int> waveform;
|
||||
final String? fileName;
|
||||
|
||||
const RecordingResult({
|
||||
required this.path,
|
||||
required this.duration,
|
||||
required this.waveform,
|
||||
required this.fileName,
|
||||
});
|
||||
}
|
||||
|
||||
extension on AudioEncoder {
|
||||
String get fileExtension {
|
||||
switch (this) {
|
||||
case AudioEncoder.aacLc:
|
||||
case AudioEncoder.aacEld:
|
||||
case AudioEncoder.aacHe:
|
||||
return 'm4a';
|
||||
case AudioEncoder.opus:
|
||||
return 'ogg';
|
||||
case AudioEncoder.wav:
|
||||
return 'wav';
|
||||
case AudioEncoder.amrNb:
|
||||
case AudioEncoder.amrWb:
|
||||
case AudioEncoder.flac:
|
||||
case AudioEncoder.pcm16bits:
|
||||
throw UnsupportedError('Not yet used');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,144 +0,0 @@
|
||||
diff --git a/android/app/build.gradle b/android/app/build.gradle
|
||||
index bb8e015cd..3ff4a7579 100644
|
||||
--- a/android/app/build.gradle
|
||||
+++ b/android/app/build.gradle
|
||||
@@ -2,7 +2,7 @@ plugins {
|
||||
id "com.android.application"
|
||||
id "kotlin-android"
|
||||
id "dev.flutter.flutter-gradle-plugin"
|
||||
- //id "com.google.gms.google-services"
|
||||
+ id "com.google.gms.google-services"
|
||||
}
|
||||
|
||||
def localProperties = new Properties()
|
||||
@@ -97,11 +97,12 @@ flutter {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
- //implementation 'com.google.firebase:firebase-messaging:19.0.1' // Workaround for https://github.com/microg/android_packages_apps_GmsCore/issues/313#issuecomment-617651698
|
||||
+ implementation 'com.google.firebase:firebase-messaging:19.0.1'
|
||||
+ // Workaround for https://github.com/microg/android_packages_apps_GmsCore/issues/313#issuecomment-617651698
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
- exclude group: 'com.google.android.gms'
|
||||
+ //exclude group: 'com.google.android.gms'
|
||||
}
|
||||
\ No newline at end of file
|
||||
diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro
|
||||
index d0e0fbc9..0a546da0 100644
|
||||
--- a/android/app/proguard-rules.pro
|
||||
+++ b/android/app/proguard-rules.pro
|
||||
@@ -1 +1,42 @@
|
||||
--keep class net.sqlcipher.** { *; }
|
||||
\ No newline at end of file
|
||||
+-optimizationpasses 5
|
||||
+## Flutter wrapper
|
||||
+-keep class net.sqlcipher.** { *; }
|
||||
+-keep class io.flutter.app.** { *; }
|
||||
+-keep class io.flutter.plugin.** { *; }
|
||||
+-keep class io.flutter.util.** { *; }
|
||||
+-keep class io.flutter.view.** { *; }
|
||||
+-keep class io.flutter.** { *; }
|
||||
+-keep class io.flutter.plugins.** { *; }
|
||||
+-dontwarn io.flutter.embedding.**
|
||||
+
|
||||
+##---------------Begin: proguard configuration for Gson (Needed for flutter_local_notifications) ----------
|
||||
+# Gson uses generic type information stored in a class file when working with fields. Proguard
|
||||
+# removes such information by default, so configure it to keep all of it.
|
||||
+-keepattributes Signature
|
||||
+
|
||||
+# For using GSON @Expose annotation
|
||||
+-keepattributes *Annotation*
|
||||
+
|
||||
+# Gson specific classes
|
||||
+-dontwarn sun.misc.**
|
||||
+
|
||||
+# Application classes that will be serialized/deserialized over Gson
|
||||
+-keep class com.google.gson.examples.android.model.** { <fields>; }
|
||||
+
|
||||
+# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
|
||||
+# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
|
||||
+-keep class * extends com.google.gson.TypeAdapter
|
||||
+-keep class * implements com.google.gson.TypeAdapterFactory
|
||||
+-keep class * implements com.google.gson.JsonSerializer
|
||||
+-keep class * implements com.google.gson.JsonDeserializer
|
||||
+
|
||||
+# Prevent R8 from leaving Data object members always null
|
||||
+-keepclassmembers,allowobfuscation class * {
|
||||
+ @com.google.gson.annotations.SerializedName <fields>;
|
||||
+}
|
||||
+
|
||||
+# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
|
||||
+-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
|
||||
+-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
|
||||
+
|
||||
+##---------------End: proguard configuration for Gson (Needed for flutter_local_notifications) ----------
|
||||
\ No newline at end of file
|
||||
diff --git a/android/app/src/main/kotlin/chat/pantheon/hermes/FcmPushService.kt b/android/app/src/main/kotlin/chat/pantheon/hermes/FcmPushService.kt
|
||||
index d9930f55..510e9845 100644
|
||||
--- a/android/app/src/main/kotlin/chat/pantheon/hermes/FcmPushService.kt
|
||||
+++ b/android/app/src/main/kotlin/chat/pantheon/hermes/FcmPushService.kt
|
||||
@@ -1,4 +1,4 @@
|
||||
-/*package chat.pantheon.hermes
|
||||
+package chat.pantheon.hermes
|
||||
|
||||
import com.famedly.fcm_shared_isolate.FcmSharedIsolateService
|
||||
|
||||
@@ -33,4 +33,3 @@ class FcmPushService : FcmSharedIsolateService() {
|
||||
}
|
||||
}
|
||||
}
|
||||
-*/
|
||||
\ No newline at end of file
|
||||
diff --git a/android/settings.gradle b/android/settings.gradle
|
||||
index b2fd960a..fdb01a4d 100644
|
||||
--- a/android/settings.gradle
|
||||
+++ b/android/settings.gradle
|
||||
@@ -20,7 +20,7 @@ plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "8.7.3" apply false
|
||||
id "org.jetbrains.kotlin.android" version "2.1.10" apply false
|
||||
- // id "com.google.gms.google-services" version "4.3.8" apply false
|
||||
+ id "com.google.gms.google-services" version "4.3.8" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
\ No newline at end of file
|
||||
diff --git a/lib/utils/background_push.dart b/lib/utils/background_push.dart
|
||||
index 1ba2659a..989f458e 100644
|
||||
--- a/lib/utils/background_push.dart
|
||||
+++ b/lib/utils/background_push.dart
|
||||
@@ -39,7 +39,7 @@ import '../config/setting_keys.dart';
|
||||
import '../widgets/matrix.dart';
|
||||
import 'platform_infos.dart';
|
||||
|
||||
-//import 'package:fcm_shared_isolate/fcm_shared_isolate.dart';
|
||||
+import 'package:fcm_shared_isolate/fcm_shared_isolate.dart';
|
||||
|
||||
class NoTokenException implements Exception {
|
||||
String get cause => 'Cannot get firebase token';
|
||||
@@ -64,7 +64,7 @@ class BackgroundPush {
|
||||
|
||||
final pendingTests = <String, Completer<void>>{};
|
||||
|
||||
- final dynamic firebase = null; //FcmSharedIsolate();
|
||||
+ final dynamic firebase = FcmSharedIsolate();
|
||||
|
||||
DateTime? lastReceivedPush;
|
||||
|
||||
diff --git a/pubspec.yaml b/pubspec.yaml
|
||||
index fb3e3ca4..039b2ccc 100644
|
||||
--- a/pubspec.yaml
|
||||
+++ b/pubspec.yaml
|
||||
@@ -25,7 +25,7 @@ dependencies:
|
||||
dynamic_color: ^1.7.0
|
||||
emoji_picker_flutter: ^4.3.0
|
||||
emojis: ^0.9.9
|
||||
- #fcm_shared_isolate: ^0.2.0
|
||||
+ fcm_shared_isolate: ^0.2.0
|
||||
file_picker: ^8.1.2
|
||||
file_selector: ^1.0.3
|
||||
flutter:
|
||||
Loading…
Reference in New Issue