pull from upstream
This commit is contained in:
parent
1dae348415
commit
b4fa4a7208
|
|
@ -2,6 +2,10 @@
|
||||||
"@@locale": "en",
|
"@@locale": "en",
|
||||||
"@@last_modified": "2025-06-05 12:38:37.885451",
|
"@@last_modified": "2025-06-05 12:38:37.885451",
|
||||||
"noSendPermission": "You can't send messages here",
|
"noSendPermission": "You can't send messages here",
|
||||||
|
"noMessagesYet": "No messages yet",
|
||||||
|
"longPressToRecordVoiceMessage": "Long press to record voice message.",
|
||||||
|
"pause": "Pause",
|
||||||
|
"resume": "Resume",
|
||||||
"@noSendPermission": {},
|
"@noSendPermission": {},
|
||||||
"alwaysUse24HourFormat": "false",
|
"alwaysUse24HourFormat": "false",
|
||||||
"@alwaysUse24HourFormat": {
|
"@alwaysUse24HourFormat": {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,10 @@
|
||||||
"@@last_modified": "2021-08-14 12:41:09.903021",
|
"@@last_modified": "2021-08-14 12:41:09.903021",
|
||||||
"noSendPermission": "Вы не можете отправлять сообщения",
|
"noSendPermission": "Вы не можете отправлять сообщения",
|
||||||
"@noSendPermission": {},
|
"@noSendPermission": {},
|
||||||
|
"noMessagesYet": "Нет сообщений",
|
||||||
|
"longPressToRecordVoiceMessage": "Зажмите, чтобы записать голосовое сообщение.",
|
||||||
|
"pause": "Пауза",
|
||||||
|
"resume": "Продолжить",
|
||||||
"alwaysUse24HourFormat": "нет",
|
"alwaysUse24HourFormat": "нет",
|
||||||
"@alwaysUse24HourFormat": {
|
"@alwaysUse24HourFormat": {
|
||||||
"description": "Set to true to always display time of day in 24 hour format."
|
"description": "Set to true to always display time of day in 24 hour format."
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ abstract class FluffyThemes {
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
Brightness brightness, [
|
Brightness brightness, [
|
||||||
Color? seed,
|
Color? seed,
|
||||||
bool? pureBlack,
|
bool? pureBlack,
|
||||||
]) {
|
]) {
|
||||||
final extraDarkColors = (brightness == Brightness.dark && pureBlack == true)
|
final extraDarkColors = (brightness == Brightness.dark && pureBlack == true)
|
||||||
? {
|
? {
|
||||||
|
|
@ -118,6 +118,8 @@ abstract class FluffyThemes {
|
||||||
isColumnMode ? colorScheme.surfaceContainer.withAlpha(128) : null,
|
isColumnMode ? colorScheme.surfaceContainer.withAlpha(128) : null,
|
||||||
surfaceTintColor: isColumnMode ? colorScheme.surface : null,
|
surfaceTintColor: isColumnMode ? colorScheme.surface : null,
|
||||||
backgroundColor: isColumnMode ? colorScheme.surface : null,
|
backgroundColor: isColumnMode ? colorScheme.surface : null,
|
||||||
|
actionsPadding:
|
||||||
|
isColumnMode ? const EdgeInsets.symmetric(horizontal: 16.0) : null,
|
||||||
systemOverlayStyle: SystemUiOverlayStyle(
|
systemOverlayStyle: SystemUiOverlayStyle(
|
||||||
statusBarColor: Colors.transparent,
|
statusBarColor: Colors.transparent,
|
||||||
statusBarIconBrightness: brightness.reversed,
|
statusBarIconBrightness: brightness.reversed,
|
||||||
|
|
@ -140,6 +142,7 @@ abstract class FluffyThemes {
|
||||||
),
|
),
|
||||||
snackBarTheme: isColumnMode
|
snackBarTheme: isColumnMode
|
||||||
? const SnackBarThemeData(
|
? const SnackBarThemeData(
|
||||||
|
showCloseIcon: true,
|
||||||
behavior: SnackBarBehavior.floating,
|
behavior: SnackBarBehavior.floating,
|
||||||
width: FluffyThemes.columnWidth * 1.5,
|
width: FluffyThemes.columnWidth * 1.5,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -199,6 +199,30 @@ abstract class L10n {
|
||||||
/// **'You can\'t send messages here'**
|
/// **'You can\'t send messages here'**
|
||||||
String get noSendPermission;
|
String get noSendPermission;
|
||||||
|
|
||||||
|
/// No description provided for @noMessagesYet.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'No messages yet'**
|
||||||
|
String get noMessagesYet;
|
||||||
|
|
||||||
|
/// No description provided for @longPressToRecordVoiceMessage.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Long press to record voice message.'**
|
||||||
|
String get longPressToRecordVoiceMessage;
|
||||||
|
|
||||||
|
/// No description provided for @pause.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Pause'**
|
||||||
|
String get pause;
|
||||||
|
|
||||||
|
/// No description provided for @resume.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Resume'**
|
||||||
|
String get resume;
|
||||||
|
|
||||||
/// Set to true to always display time of day in 24 hour format.
|
/// Set to true to always display time of day in 24 hour format.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nAr extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nBe extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nBn extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nBo extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nCa extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'true';
|
String get alwaysUse24HourFormat => 'true';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nCs extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'Vypnuto';
|
String get alwaysUse24HourFormat => 'Vypnuto';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nDe extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'true';
|
String get alwaysUse24HourFormat => 'true';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nEl extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nEn extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nEo extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nEs extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'falso';
|
String get alwaysUse24HourFormat => 'falso';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nEt extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nEu extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nFa extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nFi extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nFil extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nFr extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'true';
|
String get alwaysUse24HourFormat => 'true';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nGa extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'bréagach';
|
String get alwaysUse24HourFormat => 'bréagach';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nGl extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'falso';
|
String get alwaysUse24HourFormat => 'falso';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nHe extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nHi extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nHr extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'true';
|
String get alwaysUse24HourFormat => 'true';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nHu extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'true';
|
String get alwaysUse24HourFormat => 'true';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nIa extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nId extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'tidak';
|
String get alwaysUse24HourFormat => 'tidak';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nIe extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nIt extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'disattivato';
|
String get alwaysUse24HourFormat => 'disattivato';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nJa extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nKa extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nKo extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nLt extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nLv extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'nē';
|
String get alwaysUse24HourFormat => 'nē';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nNb extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nNl extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'true';
|
String get alwaysUse24HourFormat => 'true';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nPl extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nPt extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nRo extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nRu extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'Вы не можете отправлять сообщения';
|
String get noSendPermission => 'Вы не можете отправлять сообщения';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'Нет сообщений';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Зажмите, чтобы записать голосовое сообщение.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Пауза';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Продолжить';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'нет';
|
String get alwaysUse24HourFormat => 'нет';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nSk extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nSl extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nSr extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nSv extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nTa extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'தவறு';
|
String get alwaysUse24HourFormat => 'தவறு';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nTe extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'తప్పుడు';
|
String get alwaysUse24HourFormat => 'తప్పుడు';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nTh extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nTr extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nUk extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'ні';
|
String get alwaysUse24HourFormat => 'ні';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nVi extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'Không';
|
String get alwaysUse24HourFormat => 'Không';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,19 @@ class L10nZh extends L10n {
|
||||||
@override
|
@override
|
||||||
String get noSendPermission => 'You can\'t send messages here';
|
String get noSendPermission => 'You can\'t send messages here';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get noMessagesYet => 'No messages yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get longPressToRecordVoiceMessage =>
|
||||||
|
'Long press to record voice message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get pause => 'Pause';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get resume => 'Resume';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alwaysUse24HourFormat => 'false';
|
String get alwaysUse24HourFormat => 'false';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import 'dart:io';
|
||||||
|
|
||||||
import 'package:extera_next/pages/chat/recovered_event_dialog.dart';
|
import 'package:extera_next/pages/chat/recovered_event_dialog.dart';
|
||||||
import 'package:extera_next/pages/chat/translated_event_dialog.dart';
|
import 'package:extera_next/pages/chat/translated_event_dialog.dart';
|
||||||
import 'package:extera_next/utils/file_description.dart';
|
|
||||||
import 'package:extera_next/utils/matrix_sdk_extensions/synapse_admin_extension.dart';
|
import 'package:extera_next/utils/matrix_sdk_extensions/synapse_admin_extension.dart';
|
||||||
import 'package:extera_next/utils/translator.dart';
|
import 'package:extera_next/utils/translator.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
@ -18,7 +17,6 @@ import 'package:extera_next/generated/l10n/l10n.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
import 'package:record/record.dart';
|
|
||||||
import 'package:scroll_to_index/scroll_to_index.dart';
|
import 'package:scroll_to_index/scroll_to_index.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:universal_html/html.dart' as html;
|
import 'package:universal_html/html.dart' as html;
|
||||||
|
|
@ -28,7 +26,6 @@ import 'package:extera_next/config/setting_keys.dart';
|
||||||
import 'package:extera_next/config/themes.dart';
|
import 'package:extera_next/config/themes.dart';
|
||||||
import 'package:extera_next/pages/chat/chat_view.dart';
|
import 'package:extera_next/pages/chat/chat_view.dart';
|
||||||
import 'package:extera_next/pages/chat/event_info_dialog.dart';
|
import 'package:extera_next/pages/chat/event_info_dialog.dart';
|
||||||
import 'package:extera_next/pages/chat/recording_dialog.dart';
|
|
||||||
import 'package:extera_next/pages/chat_details/chat_details.dart';
|
import 'package:extera_next/pages/chat_details/chat_details.dart';
|
||||||
import 'package:extera_next/utils/error_reporter.dart';
|
import 'package:extera_next/utils/error_reporter.dart';
|
||||||
import 'package:extera_next/utils/file_selector.dart';
|
import 'package:extera_next/utils/file_selector.dart';
|
||||||
|
|
@ -619,45 +616,39 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void voiceMessageAction() async {
|
Future<void> onVoiceMessageSend(
|
||||||
|
String path,
|
||||||
|
int duration,
|
||||||
|
List<int> waveform,
|
||||||
|
String? fileName,
|
||||||
|
) async {
|
||||||
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||||
if (PlatformInfos.isAndroid) {
|
final audioFile = XFile(path);
|
||||||
final info = await DeviceInfoPlugin().androidInfo;
|
|
||||||
if (info.version.sdkInt < 19) {
|
|
||||||
showOkAlertDialog(
|
|
||||||
context: context,
|
|
||||||
title: L10n.of(context).unsupportedAndroidVersion,
|
|
||||||
message: L10n.of(context).unsupportedAndroidVersionLong,
|
|
||||||
okLabel: L10n.of(context).close,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await AudioRecorder().hasPermission() == false) return;
|
final bytesResult = await showFutureLoadingDialog(
|
||||||
final result = await showDialog<RecordingResult>(
|
|
||||||
context: context,
|
context: context,
|
||||||
barrierDismissible: false,
|
future: audioFile.readAsBytes,
|
||||||
builder: (c) => const RecordingDialog(),
|
|
||||||
);
|
);
|
||||||
if (result == null) return;
|
final bytes = bytesResult.result;
|
||||||
final audioFile = XFile(result.path);
|
if (bytes == null) return;
|
||||||
|
|
||||||
final file = MatrixAudioFile(
|
final file = MatrixAudioFile(
|
||||||
bytes: await audioFile.readAsBytes(),
|
bytes: bytes,
|
||||||
name: result.fileName ?? audioFile.path,
|
name: fileName ?? audioFile.path,
|
||||||
);
|
);
|
||||||
|
|
||||||
await room.sendFileEvent(
|
await room.sendFileEvent(
|
||||||
file,
|
file,
|
||||||
inReplyTo: replyEvent,
|
inReplyTo: replyEvent,
|
||||||
extraContent: {
|
extraContent: {
|
||||||
'info': {
|
'info': {
|
||||||
...file.info,
|
...file.info,
|
||||||
'duration': result.duration,
|
'duration': duration,
|
||||||
},
|
},
|
||||||
'org.matrix.msc3245.voice': {},
|
'org.matrix.msc3245.voice': {},
|
||||||
'org.matrix.msc1767.audio': {
|
'org.matrix.msc1767.audio': {
|
||||||
'duration': result.duration,
|
'duration': duration,
|
||||||
'waveform': result.waveform,
|
'waveform': waveform,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
).catchError((e) {
|
).catchError((e) {
|
||||||
|
|
@ -737,8 +728,11 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final event = selectedEvents.single;
|
final event = selectedEvents.single;
|
||||||
await mx.client.reportEvent(roomId, event.eventId,
|
await mx.client.reportEvent(
|
||||||
reason: "Extera (Next) Redacted Event Recover");
|
roomId,
|
||||||
|
event.eventId,
|
||||||
|
reason: "Extera (Next) Redacted Event Recover",
|
||||||
|
);
|
||||||
|
|
||||||
final reports = await mx.client.getEventReports();
|
final reports = await mx.client.getEventReports();
|
||||||
final report = reports.firstWhere(
|
final report = reports.firstWhere(
|
||||||
|
|
@ -753,11 +747,14 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
}
|
}
|
||||||
|
|
||||||
Navigator.of(context).push(new MaterialPageRoute(
|
Navigator.of(context).push(new MaterialPageRoute(
|
||||||
builder: (BuildContext ctx) {
|
builder: (BuildContext ctx) {
|
||||||
return RecoveredEventDialog(
|
return RecoveredEventDialog(
|
||||||
event: recoveredEvent!, timeline: timeline!);
|
event: recoveredEvent,
|
||||||
},
|
timeline: timeline!,
|
||||||
fullscreenDialog: true));
|
);
|
||||||
|
},
|
||||||
|
fullscreenDialog: true,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
void translateEventAction() async {
|
void translateEventAction() async {
|
||||||
|
|
@ -769,12 +766,6 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
}
|
}
|
||||||
final event = selectedEvents.single;
|
final event = selectedEvents.single;
|
||||||
var text = event.isRichMessage ? event.formattedText : event.text;
|
var text = event.isRichMessage ? event.formattedText : event.text;
|
||||||
if (text == null) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text(L10n.of(context).errorTranslatingMessage)),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var content = {...event.content};
|
var content = {...event.content};
|
||||||
try {
|
try {
|
||||||
text = await Translator.translate(
|
text = await Translator.translate(
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:animations/animations.dart';
|
import 'package:animations/animations.dart';
|
||||||
import 'package:extera_next/generated/l10n/l10n.dart';
|
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
||||||
import 'package:extera_next/config/app_config.dart';
|
import 'package:extera_next/config/app_config.dart';
|
||||||
import 'package:extera_next/utils/other_party_can_receive.dart';
|
import 'package:extera_next/generated/l10n/l10n.dart';
|
||||||
|
import 'package:extera_next/pages/chat/recording_input_row.dart';
|
||||||
|
import 'package:extera_next/pages/chat/recording_view_model.dart';
|
||||||
import 'package:extera_next/utils/platform_infos.dart';
|
import 'package:extera_next/utils/platform_infos.dart';
|
||||||
import 'package:extera_next/widgets/avatar.dart';
|
import 'package:extera_next/widgets/avatar.dart';
|
||||||
import 'package:extera_next/widgets/matrix.dart';
|
import 'package:extera_next/widgets/matrix.dart';
|
||||||
|
|
@ -21,325 +22,331 @@ class ChatInputRow extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
if (controller.showEmojiPicker &&
|
|
||||||
controller.emojiPickerType == EmojiPickerType.reaction) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
const height = 48.0;
|
const height = 48.0;
|
||||||
|
|
||||||
if (!controller.room.canSendDefaultMessages) {
|
final selectedTextButtonStyle = TextButton.styleFrom(
|
||||||
return Center(
|
foregroundColor: theme.colorScheme.onTertiaryContainer,
|
||||||
child: Padding(
|
);
|
||||||
padding: const EdgeInsets.all(12.0),
|
|
||||||
child: Text(
|
|
||||||
L10n.of(context).noSendPermission,
|
|
||||||
style: theme.textTheme.bodySmall,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (!controller.room.otherPartyCanReceiveMessages) {
|
return RecordingViewModel(
|
||||||
// return Center(
|
builder: (context, recordingViewModel) {
|
||||||
// child: Padding(
|
if (recordingViewModel.isRecording) {
|
||||||
// padding: const EdgeInsets.all(12.0),
|
return RecordingInputRow(
|
||||||
// child: Text(
|
state: recordingViewModel,
|
||||||
// L10n.of(context).otherPartyNotLoggedIn,
|
onSend: controller.onVoiceMessageSend,
|
||||||
// style: theme.textTheme.bodySmall,
|
);
|
||||||
// textAlign: TextAlign.center,
|
}
|
||||||
// ),
|
return Row(
|
||||||
// ),
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
// );
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
// }
|
children: controller.selectMode
|
||||||
|
? <Widget>[
|
||||||
|
if (controller.selectedEvents
|
||||||
|
.every((event) => event.status == EventStatus.error))
|
||||||
return Row(
|
SizedBox(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
height: height,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
child: TextButton(
|
||||||
children: controller.selectMode
|
style: TextButton.styleFrom(
|
||||||
? <Widget>[
|
foregroundColor: theme.colorScheme.error,
|
||||||
if (controller.selectedEvents
|
),
|
||||||
.every((event) => event.status == EventStatus.error))
|
onPressed: controller.deleteErrorEventsAction,
|
||||||
SizedBox(
|
child: Row(
|
||||||
height: height,
|
children: <Widget>[
|
||||||
child: TextButton(
|
const Icon(Icons.delete_forever_outlined),
|
||||||
style: TextButton.styleFrom(
|
Text(L10n.of(context).delete),
|
||||||
foregroundColor: theme.colorScheme.error,
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
SizedBox(
|
||||||
|
height: height,
|
||||||
|
child: TextButton(
|
||||||
|
style: selectedTextButtonStyle,
|
||||||
|
onPressed: controller.forwardEventsAction,
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
const Icon(Icons.keyboard_arrow_left_outlined),
|
||||||
|
Text(L10n.of(context).forward),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onPressed: controller.deleteErrorEventsAction,
|
controller.selectedEvents.length == 1
|
||||||
child: Row(
|
? controller.selectedEvents.first
|
||||||
children: <Widget>[
|
.getDisplayEvent(controller.timeline!)
|
||||||
const Icon(Icons.delete),
|
.status
|
||||||
Text(L10n.of(context).delete),
|
.isSent
|
||||||
|
? SizedBox(
|
||||||
|
height: height,
|
||||||
|
child: TextButton(
|
||||||
|
style: selectedTextButtonStyle,
|
||||||
|
onPressed: controller.replyAction,
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Text(L10n.of(context).reply),
|
||||||
|
const Icon(Icons.keyboard_arrow_right),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: SizedBox(
|
||||||
|
height: height,
|
||||||
|
child: TextButton(
|
||||||
|
style: selectedTextButtonStyle,
|
||||||
|
onPressed: controller.sendAgainAction,
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Text(L10n.of(context).tryToSendAgain),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
const Icon(Icons.send_outlined, size: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
]
|
||||||
|
: <Widget>[
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
AnimatedContainer(
|
||||||
|
duration: FluffyThemes.animationDuration,
|
||||||
|
curve: FluffyThemes.animationCurve,
|
||||||
|
width:
|
||||||
|
controller.sendController.text.isNotEmpty ? 0 : height,
|
||||||
|
height: height,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: const BoxDecoration(),
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
child: PopupMenuButton<String>(
|
||||||
|
useRootNavigator: true,
|
||||||
|
icon: const Icon(Icons.add_circle_outline),
|
||||||
|
iconColor: theme.colorScheme.onPrimaryContainer,
|
||||||
|
onSelected: controller.onAddPopupMenuButtonSelected,
|
||||||
|
itemBuilder: (BuildContext context) =>
|
||||||
|
<PopupMenuEntry<String>>[
|
||||||
|
if (PlatformInfos.isMobile)
|
||||||
|
PopupMenuItem<String>(
|
||||||
|
value: 'location',
|
||||||
|
child: ListTile(
|
||||||
|
leading: CircleAvatar(
|
||||||
|
backgroundColor:
|
||||||
|
theme.colorScheme.onPrimaryContainer,
|
||||||
|
foregroundColor:
|
||||||
|
theme.colorScheme.primaryContainer,
|
||||||
|
child: const Icon(Icons.gps_fixed_outlined),
|
||||||
|
),
|
||||||
|
title: Text(L10n.of(context).shareLocation),
|
||||||
|
contentPadding: const EdgeInsets.all(0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// PopupMenuItem<String>(
|
||||||
|
// value: 'image',
|
||||||
|
// child: ListTile(
|
||||||
|
// leading: CircleAvatar(
|
||||||
|
// backgroundColor:
|
||||||
|
// theme.colorScheme.onPrimaryContainer,
|
||||||
|
// foregroundColor:
|
||||||
|
// theme.colorScheme.primaryContainer,
|
||||||
|
// child: const Icon(Icons.photo_outlined),
|
||||||
|
// ),
|
||||||
|
// title: Text(L10n.of(context).sendImage),
|
||||||
|
// contentPadding: const EdgeInsets.all(0),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// PopupMenuItem<String>(
|
||||||
|
// value: 'video',
|
||||||
|
// child: ListTile(
|
||||||
|
// leading: CircleAvatar(
|
||||||
|
// backgroundColor:
|
||||||
|
// theme.colorScheme.onPrimaryContainer,
|
||||||
|
// foregroundColor:
|
||||||
|
// theme.colorScheme.primaryContainer,
|
||||||
|
// child:
|
||||||
|
// const Icon(Icons.video_camera_back_outlined),
|
||||||
|
// ),
|
||||||
|
// title: Text(L10n.of(context).sendVideo),
|
||||||
|
// contentPadding: const EdgeInsets.all(0),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
PopupMenuItem<String>(
|
||||||
|
value: 'file',
|
||||||
|
child: ListTile(
|
||||||
|
leading: CircleAvatar(
|
||||||
|
backgroundColor:
|
||||||
|
theme.colorScheme.onPrimaryContainer,
|
||||||
|
foregroundColor:
|
||||||
|
theme.colorScheme.primaryContainer,
|
||||||
|
child: const Icon(Icons.attachment_outlined),
|
||||||
|
),
|
||||||
|
title: Text(L10n.of(context).sendFile),
|
||||||
|
contentPadding: const EdgeInsets.all(0),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
if (PlatformInfos.isMobile)
|
||||||
else
|
AnimatedContainer(
|
||||||
SizedBox(
|
duration: FluffyThemes.animationDuration,
|
||||||
height: height,
|
curve: FluffyThemes.animationCurve,
|
||||||
child: TextButton(
|
width: controller.sendController.text.isNotEmpty
|
||||||
onPressed: controller.forwardEventsAction,
|
? 0
|
||||||
child: Row(
|
: height,
|
||||||
children: <Widget>[
|
height: height,
|
||||||
const Icon(Icons.keyboard_arrow_left_outlined),
|
alignment: Alignment.center,
|
||||||
Text(L10n.of(context).forward),
|
decoration: const BoxDecoration(),
|
||||||
],
|
clipBehavior: Clip.hardEdge,
|
||||||
),
|
child: PopupMenuButton(
|
||||||
),
|
useRootNavigator: true,
|
||||||
),
|
icon: const Icon(Icons.camera_alt_outlined),
|
||||||
controller.selectedEvents.length == 1
|
onSelected: controller.onAddPopupMenuButtonSelected,
|
||||||
? controller.selectedEvents.first
|
iconColor: theme.colorScheme.onPrimaryContainer,
|
||||||
.getDisplayEvent(controller.timeline!)
|
itemBuilder: (context) => [
|
||||||
.status
|
PopupMenuItem<String>(
|
||||||
.isSent
|
value: 'camera-video',
|
||||||
? SizedBox(
|
child: ListTile(
|
||||||
height: height,
|
leading: CircleAvatar(
|
||||||
child: TextButton(
|
backgroundColor:
|
||||||
onPressed: controller.replyAction,
|
theme.colorScheme.onPrimaryContainer,
|
||||||
child: Row(
|
foregroundColor:
|
||||||
children: <Widget>[
|
theme.colorScheme.primaryContainer,
|
||||||
Text(L10n.of(context).reply),
|
child: const Icon(Icons.videocam_outlined),
|
||||||
const Icon(Icons.keyboard_arrow_right),
|
),
|
||||||
],
|
title: Text(L10n.of(context).recordAVideo),
|
||||||
|
contentPadding: const EdgeInsets.all(0),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
PopupMenuItem<String>(
|
||||||
: SizedBox(
|
value: 'camera',
|
||||||
height: height,
|
child: ListTile(
|
||||||
child: TextButton(
|
leading: CircleAvatar(
|
||||||
onPressed: controller.sendAgainAction,
|
backgroundColor:
|
||||||
child: Row(
|
theme.colorScheme.onPrimaryContainer,
|
||||||
children: <Widget>[
|
foregroundColor:
|
||||||
Text(L10n.of(context).tryToSendAgain),
|
theme.colorScheme.primaryContainer,
|
||||||
const SizedBox(width: 4),
|
child: const Icon(Icons.camera_alt_outlined),
|
||||||
const Icon(Icons.send_outlined, size: 16),
|
),
|
||||||
],
|
title: Text(L10n.of(context).takeAPhoto),
|
||||||
|
contentPadding: const EdgeInsets.all(0),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
],
|
||||||
: const SizedBox.shrink(),
|
|
||||||
]
|
|
||||||
: <Widget>[
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
AnimatedContainer(
|
|
||||||
duration: FluffyThemes.animationDuration,
|
|
||||||
curve: FluffyThemes.animationCurve,
|
|
||||||
width: controller.sendController.text.isNotEmpty ? 0 : height,
|
|
||||||
height: height,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
decoration: const BoxDecoration(),
|
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
child: PopupMenuButton<String>(
|
|
||||||
icon: const Icon(Icons.add_circle_outline),
|
|
||||||
iconColor: theme.colorScheme.onPrimaryContainer,
|
|
||||||
onSelected: controller.onAddPopupMenuButtonSelected,
|
|
||||||
itemBuilder: (BuildContext context) =>
|
|
||||||
<PopupMenuEntry<String>>[
|
|
||||||
if (PlatformInfos.isMobile)
|
|
||||||
PopupMenuItem<String>(
|
|
||||||
value: 'location',
|
|
||||||
child: ListTile(
|
|
||||||
leading: CircleAvatar(
|
|
||||||
backgroundColor:
|
|
||||||
theme.colorScheme.onPrimaryContainer,
|
|
||||||
foregroundColor: theme.colorScheme.primaryContainer,
|
|
||||||
child: const Icon(Icons.gps_fixed_outlined),
|
|
||||||
),
|
|
||||||
title: Text(L10n.of(context).shareLocation),
|
|
||||||
contentPadding: const EdgeInsets.all(0),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
PopupMenuItem<String>(
|
|
||||||
value: 'image',
|
|
||||||
child: ListTile(
|
|
||||||
leading: CircleAvatar(
|
|
||||||
backgroundColor: theme.colorScheme.onPrimaryContainer,
|
|
||||||
foregroundColor: theme.colorScheme.primaryContainer,
|
|
||||||
child: const Icon(Icons.photo_outlined),
|
|
||||||
),
|
|
||||||
title: Text(L10n.of(context).sendImage),
|
|
||||||
contentPadding: const EdgeInsets.all(0),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PopupMenuItem<String>(
|
Container(
|
||||||
value: 'video',
|
height: height,
|
||||||
child: ListTile(
|
width: height,
|
||||||
leading: CircleAvatar(
|
alignment: Alignment.center,
|
||||||
backgroundColor: theme.colorScheme.onPrimaryContainer,
|
child: IconButton(
|
||||||
foregroundColor: theme.colorScheme.primaryContainer,
|
tooltip: L10n.of(context).emojis,
|
||||||
child: const Icon(Icons.video_camera_back_outlined),
|
color: theme.colorScheme.onPrimaryContainer,
|
||||||
),
|
icon: PageTransitionSwitcher(
|
||||||
title: Text(L10n.of(context).sendVideo),
|
transitionBuilder: (
|
||||||
contentPadding: const EdgeInsets.all(0),
|
Widget child,
|
||||||
),
|
Animation<double> primaryAnimation,
|
||||||
),
|
Animation<double> secondaryAnimation,
|
||||||
PopupMenuItem<String>(
|
) {
|
||||||
value: 'file',
|
return SharedAxisTransition(
|
||||||
child: ListTile(
|
animation: primaryAnimation,
|
||||||
leading: CircleAvatar(
|
secondaryAnimation: secondaryAnimation,
|
||||||
backgroundColor: theme.colorScheme.onPrimaryContainer,
|
transitionType: SharedAxisTransitionType.scaled,
|
||||||
foregroundColor: theme.colorScheme.primaryContainer,
|
fillColor: Colors.transparent,
|
||||||
child: const Icon(Icons.attachment_outlined),
|
child: child,
|
||||||
),
|
);
|
||||||
title: Text(L10n.of(context).sendFile),
|
},
|
||||||
contentPadding: const EdgeInsets.all(0),
|
child: Icon(
|
||||||
),
|
controller.showEmojiPicker
|
||||||
),
|
? Icons.keyboard
|
||||||
],
|
: Icons.add_reaction_outlined,
|
||||||
),
|
key: ValueKey(controller.showEmojiPicker),
|
||||||
),
|
|
||||||
if (PlatformInfos.isMobile)
|
|
||||||
AnimatedContainer(
|
|
||||||
duration: FluffyThemes.animationDuration,
|
|
||||||
curve: FluffyThemes.animationCurve,
|
|
||||||
width: controller.sendController.text.isNotEmpty ? 0 : height,
|
|
||||||
height: height,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
decoration: const BoxDecoration(),
|
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
child: PopupMenuButton(
|
|
||||||
icon: const Icon(Icons.camera_alt_outlined),
|
|
||||||
onSelected: controller.onAddPopupMenuButtonSelected,
|
|
||||||
iconColor: theme.colorScheme.onPrimaryContainer,
|
|
||||||
itemBuilder: (context) => [
|
|
||||||
PopupMenuItem<String>(
|
|
||||||
value: 'camera-video',
|
|
||||||
child: ListTile(
|
|
||||||
leading: CircleAvatar(
|
|
||||||
backgroundColor:
|
|
||||||
theme.colorScheme.onPrimaryContainer,
|
|
||||||
foregroundColor: theme.colorScheme.primaryContainer,
|
|
||||||
child: const Icon(Icons.videocam_outlined),
|
|
||||||
),
|
|
||||||
title: Text(L10n.of(context).recordAVideo),
|
|
||||||
contentPadding: const EdgeInsets.all(0),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PopupMenuItem<String>(
|
onPressed: controller.emojiPickerAction,
|
||||||
value: 'camera',
|
|
||||||
child: ListTile(
|
|
||||||
leading: CircleAvatar(
|
|
||||||
backgroundColor:
|
|
||||||
theme.colorScheme.onPrimaryContainer,
|
|
||||||
foregroundColor: theme.colorScheme.primaryContainer,
|
|
||||||
child: const Icon(Icons.camera_alt_outlined),
|
|
||||||
),
|
|
||||||
title: Text(L10n.of(context).takeAPhoto),
|
|
||||||
contentPadding: const EdgeInsets.all(0),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
height: height,
|
|
||||||
width: height,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: IconButton(
|
|
||||||
tooltip: L10n.of(context).emojis,
|
|
||||||
color: theme.colorScheme.onPrimaryContainer,
|
|
||||||
icon: PageTransitionSwitcher(
|
|
||||||
transitionBuilder: (
|
|
||||||
Widget child,
|
|
||||||
Animation<double> primaryAnimation,
|
|
||||||
Animation<double> secondaryAnimation,
|
|
||||||
) {
|
|
||||||
return SharedAxisTransition(
|
|
||||||
animation: primaryAnimation,
|
|
||||||
secondaryAnimation: secondaryAnimation,
|
|
||||||
transitionType: SharedAxisTransitionType.scaled,
|
|
||||||
fillColor: Colors.transparent,
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Icon(
|
|
||||||
controller.showEmojiPicker
|
|
||||||
? Icons.keyboard
|
|
||||||
: Icons.add_reaction_outlined,
|
|
||||||
key: ValueKey(controller.showEmojiPicker),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: controller.emojiPickerAction,
|
if (Matrix.of(context).isMultiAccount &&
|
||||||
),
|
Matrix.of(context).hasComplexBundles &&
|
||||||
),
|
Matrix.of(context).currentBundle!.length > 1)
|
||||||
if (Matrix.of(context).isMultiAccount &&
|
Container(
|
||||||
Matrix.of(context).hasComplexBundles &&
|
height: height,
|
||||||
Matrix.of(context).currentBundle!.length > 1)
|
width: height,
|
||||||
Container(
|
alignment: Alignment.center,
|
||||||
width: height,
|
child: _ChatAccountPicker(controller),
|
||||||
height: height,
|
),
|
||||||
alignment: Alignment.center,
|
Expanded(
|
||||||
child: _ChatAccountPicker(controller),
|
child: Padding(
|
||||||
),
|
padding: const EdgeInsets.symmetric(vertical: 0.0),
|
||||||
Expanded(
|
child: InputBar(
|
||||||
child: Padding(
|
room: controller.room,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 0.0),
|
minLines: 1,
|
||||||
child: InputBar(
|
maxLines: 8,
|
||||||
room: controller.room,
|
autofocus: !PlatformInfos.isMobile,
|
||||||
minLines: 1,
|
keyboardType: TextInputType.multiline,
|
||||||
maxLines: 8,
|
textInputAction: AppConfig.sendOnEnter == true &&
|
||||||
autofocus: !PlatformInfos.isMobile,
|
PlatformInfos.isMobile
|
||||||
keyboardType: TextInputType.multiline,
|
|
||||||
textInputAction:
|
|
||||||
AppConfig.sendOnEnter == true && PlatformInfos.isMobile
|
|
||||||
? TextInputAction.send
|
? TextInputAction.send
|
||||||
: null,
|
: null,
|
||||||
onSubmitted: controller.onInputBarSubmitted,
|
onSubmitted: controller.onInputBarSubmitted,
|
||||||
onSubmitImage: controller.sendImageFromClipBoard,
|
onSubmitImage: controller.sendImageFromClipBoard,
|
||||||
focusNode: controller.inputFocus,
|
focusNode: controller.inputFocus,
|
||||||
controller: controller.sendController,
|
controller: controller.sendController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
contentPadding: const EdgeInsets.only(
|
contentPadding: const EdgeInsets.only(
|
||||||
left: 6.0,
|
left: 6.0,
|
||||||
right: 6.0,
|
right: 6.0,
|
||||||
bottom: 6.0,
|
bottom: 6.0,
|
||||||
top: 3.0,
|
top: 3.0,
|
||||||
|
),
|
||||||
|
counter: const SizedBox.shrink(),
|
||||||
|
hintText: L10n.of(context).writeAMessage,
|
||||||
|
hintMaxLines: 1,
|
||||||
|
border: InputBorder.none,
|
||||||
|
enabledBorder: InputBorder.none,
|
||||||
|
filled: false,
|
||||||
|
),
|
||||||
|
onChanged: controller.onInputBarChanged,
|
||||||
),
|
),
|
||||||
hintText: L10n.of(context).writeAMessage,
|
|
||||||
hintMaxLines: 1,
|
|
||||||
border: InputBorder.none,
|
|
||||||
enabledBorder: InputBorder.none,
|
|
||||||
filled: false,
|
|
||||||
),
|
),
|
||||||
onChanged: controller.onInputBarChanged,
|
|
||||||
),
|
),
|
||||||
),
|
Container(
|
||||||
),
|
height: height,
|
||||||
Container(
|
width: height,
|
||||||
height: height,
|
alignment: Alignment.center,
|
||||||
width: height,
|
child: PlatformInfos.platformCanRecord &&
|
||||||
alignment: Alignment.center,
|
controller.sendController.text.isEmpty
|
||||||
child: PlatformInfos.platformCanRecord &&
|
? IconButton(
|
||||||
controller.sendController.text.isEmpty
|
tooltip: L10n.of(context).voiceMessage,
|
||||||
? FloatingActionButton.small(
|
onPressed: () =>
|
||||||
tooltip: L10n.of(context).voiceMessage,
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
onPressed: controller.voiceMessageAction,
|
SnackBar(
|
||||||
elevation: 0,
|
content: Text(
|
||||||
heroTag: null,
|
L10n.of(context)
|
||||||
shape: RoundedRectangleBorder(
|
.longPressToRecordVoiceMessage,
|
||||||
borderRadius: BorderRadius.circular(height),
|
),
|
||||||
),
|
),
|
||||||
backgroundColor: theme.bubbleColor,
|
),
|
||||||
foregroundColor: theme.onBubbleColor,
|
onLongPress: () => recordingViewModel
|
||||||
child: const Icon(Icons.mic_none_outlined),
|
.startRecording(controller.room),
|
||||||
)
|
style: IconButton.styleFrom(
|
||||||
: FloatingActionButton.small(
|
backgroundColor: theme.bubbleColor,
|
||||||
tooltip: L10n.of(context).send,
|
foregroundColor: theme.onBubbleColor,
|
||||||
onPressed: controller.send,
|
),
|
||||||
elevation: 0,
|
icon: const Icon(Icons.mic_none_outlined),
|
||||||
heroTag: null,
|
)
|
||||||
shape: RoundedRectangleBorder(
|
: IconButton(
|
||||||
borderRadius: BorderRadius.circular(height),
|
tooltip: L10n.of(context).send,
|
||||||
),
|
onPressed: controller.send,
|
||||||
backgroundColor: theme.bubbleColor,
|
style: IconButton.styleFrom(
|
||||||
foregroundColor: theme.onBubbleColor,
|
backgroundColor: theme.bubbleColor,
|
||||||
child: const Icon(Icons.send_outlined),
|
foregroundColor: theme.onBubbleColor,
|
||||||
),
|
),
|
||||||
),
|
icon: const Icon(Icons.send_outlined),
|
||||||
],
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -368,6 +375,7 @@ class _ChatAccountPicker extends StatelessWidget {
|
||||||
child: FutureBuilder<Profile>(
|
child: FutureBuilder<Profile>(
|
||||||
future: controller.sendingClient.fetchOwnProfile(),
|
future: controller.sendingClient.fetchOwnProfile(),
|
||||||
builder: (context, snapshot) => PopupMenuButton<String>(
|
builder: (context, snapshot) => PopupMenuButton<String>(
|
||||||
|
useRootNavigator: true,
|
||||||
onSelected: (mxid) => _popupMenuButtonSelected(mxid, context),
|
onSelected: (mxid) => _popupMenuButtonSelected(mxid, context),
|
||||||
itemBuilder: (BuildContext context) => clients
|
itemBuilder: (BuildContext context) => clients
|
||||||
.map(
|
.map(
|
||||||
|
|
@ -399,4 +407,4 @@ class _ChatAccountPicker extends StatelessWidget {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -15,7 +15,6 @@ import 'package:extera_next/pages/chat/chat_app_bar_title.dart';
|
||||||
import 'package:extera_next/pages/chat/chat_event_list.dart';
|
import 'package:extera_next/pages/chat/chat_event_list.dart';
|
||||||
import 'package:extera_next/pages/chat/encryption_button.dart';
|
import 'package:extera_next/pages/chat/encryption_button.dart';
|
||||||
import 'package:extera_next/pages/chat/pinned_events.dart';
|
import 'package:extera_next/pages/chat/pinned_events.dart';
|
||||||
import 'package:extera_next/pages/chat/reactions_picker.dart';
|
|
||||||
import 'package:extera_next/pages/chat/reply_display.dart';
|
import 'package:extera_next/pages/chat/reply_display.dart';
|
||||||
import 'package:extera_next/utils/account_config.dart';
|
import 'package:extera_next/utils/account_config.dart';
|
||||||
import 'package:extera_next/utils/localized_exception_extension.dart';
|
import 'package:extera_next/utils/localized_exception_extension.dart';
|
||||||
|
|
|
||||||
|
|
@ -1,256 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:extera_next/generated/l10n/l10n.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:extera_next/config/app_config.dart';
|
|
||||||
import 'package:extera_next/config/setting_keys.dart';
|
|
||||||
import 'package:extera_next/utils/platform_infos.dart';
|
|
||||||
import 'package:extera_next/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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:extera_next/config/themes.dart';
|
||||||
|
|
||||||
|
import 'package:extera_next/generated/l10n/l10n.dart';
|
||||||
|
|
||||||
|
import 'package:extera_next/pages/chat/recording_view_model.dart';
|
||||||
|
|
||||||
|
class RecordingInputRow extends StatelessWidget {
|
||||||
|
final RecordingViewModelState state;
|
||||||
|
|
||||||
|
final Future<void> Function(String, int, List<int>, String?) onSend;
|
||||||
|
|
||||||
|
const RecordingInputRow({
|
||||||
|
required this.state,
|
||||||
|
required this.onSend,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
const maxDecibalWidth = 36.0;
|
||||||
|
|
||||||
|
final time =
|
||||||
|
'${state.duration.inMinutes.toString().padLeft(2, '0')}:${(state.duration.inSeconds % 60).toString().padLeft(2, '0')}';
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
tooltip: L10n.of(context).cancel,
|
||||||
|
icon: const Icon(Icons.delete_outlined),
|
||||||
|
color: theme.colorScheme.error,
|
||||||
|
onPressed: state.cancel,
|
||||||
|
),
|
||||||
|
if (state.isPaused)
|
||||||
|
IconButton(
|
||||||
|
tooltip: L10n.of(context).resume,
|
||||||
|
icon: const Icon(Icons.play_circle_outline_outlined),
|
||||||
|
onPressed: state.resume,
|
||||||
|
)
|
||||||
|
else
|
||||||
|
IconButton(
|
||||||
|
tooltip: L10n.of(context).pause,
|
||||||
|
icon: const Icon(Icons.pause_circle_outline_outlined),
|
||||||
|
onPressed: state.pause,
|
||||||
|
),
|
||||||
|
Text(time),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
const width = 4;
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: state.amplitudeTimeline.reversed
|
||||||
|
.take((constraints.maxWidth / (width + 2)).floor())
|
||||||
|
.toList()
|
||||||
|
.reversed
|
||||||
|
.map(
|
||||||
|
(amplitude) => Container(
|
||||||
|
margin: const EdgeInsets.only(left: 2),
|
||||||
|
width: width.toDouble(),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
borderRadius: BorderRadius.circular(2),
|
||||||
|
),
|
||||||
|
height: maxDecibalWidth * (amplitude / 100),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
style: IconButton.styleFrom(
|
||||||
|
disabledBackgroundColor: theme.bubbleColor.withAlpha(128),
|
||||||
|
backgroundColor: theme.bubbleColor,
|
||||||
|
foregroundColor: theme.onBubbleColor,
|
||||||
|
),
|
||||||
|
tooltip: L10n.of(context).sendAudio,
|
||||||
|
icon: state.isSending
|
||||||
|
? const SizedBox.square(
|
||||||
|
dimension: 24,
|
||||||
|
child: CircularProgressIndicator.adaptive(),
|
||||||
|
)
|
||||||
|
: const Icon(Icons.send_outlined),
|
||||||
|
onPressed: state.isSending ? null : () => state.stopAndSend(onSend),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,230 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
|
import 'package:matrix/matrix.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:extera_next/config/setting_keys.dart';
|
||||||
|
import 'package:extera_next/generated/l10n/l10n.dart';
|
||||||
|
import 'package:extera_next/utils/platform_infos.dart';
|
||||||
|
import 'package:extera_next/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||||
|
import 'package:extera_next/widgets/matrix.dart';
|
||||||
|
import 'events/audio_player.dart';
|
||||||
|
|
||||||
|
class RecordingViewModel extends StatefulWidget {
|
||||||
|
final Widget Function(BuildContext, RecordingViewModelState) builder;
|
||||||
|
|
||||||
|
const RecordingViewModel({
|
||||||
|
required this.builder,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
RecordingViewModelState createState() => RecordingViewModelState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class RecordingViewModelState extends State<RecordingViewModel> {
|
||||||
|
Timer? _recorderSubscription;
|
||||||
|
Duration duration = Duration.zero;
|
||||||
|
|
||||||
|
bool error = false;
|
||||||
|
bool isSending = false;
|
||||||
|
|
||||||
|
bool get isRecording => _audioRecorder != null;
|
||||||
|
|
||||||
|
AudioRecorder? _audioRecorder;
|
||||||
|
final List<double> amplitudeTimeline = [];
|
||||||
|
|
||||||
|
String? fileName;
|
||||||
|
|
||||||
|
bool isPaused = false;
|
||||||
|
|
||||||
|
Future<void> startRecording(Room room) async {
|
||||||
|
room.client.getConfig(); // Preload server file configuration.
|
||||||
|
if (PlatformInfos.isAndroid) {
|
||||||
|
final info = await DeviceInfoPlugin().androidInfo;
|
||||||
|
if (info.version.sdkInt < 19) {
|
||||||
|
showOkAlertDialog(
|
||||||
|
context: context,
|
||||||
|
title: L10n.of(context).unsupportedAndroidVersion,
|
||||||
|
message: L10n.of(context).unsupportedAndroidVersionLong,
|
||||||
|
okLabel: L10n.of(context).close,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add permission request
|
||||||
|
if (await AudioRecorder().hasPermission() == false) return;
|
||||||
|
|
||||||
|
final store = Matrix.of(context).store;
|
||||||
|
|
||||||
|
final audioRecorder = _audioRecorder ??= AudioRecorder();
|
||||||
|
setState(() {});
|
||||||
|
|
||||||
|
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);
|
||||||
|
_subscribe();
|
||||||
|
} catch (_) {
|
||||||
|
setState(() => error = true);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_reset();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _subscribe() {
|
||||||
|
_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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _reset() {
|
||||||
|
WakelockPlus.disable();
|
||||||
|
_recorderSubscription?.cancel();
|
||||||
|
_audioRecorder?.stop();
|
||||||
|
_audioRecorder = null;
|
||||||
|
isSending = false;
|
||||||
|
error = false;
|
||||||
|
fileName = null;
|
||||||
|
duration = Duration.zero;
|
||||||
|
amplitudeTimeline.clear();
|
||||||
|
isPaused = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancel() {
|
||||||
|
setState(() {
|
||||||
|
_reset();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void pause() {
|
||||||
|
_audioRecorder?.pause();
|
||||||
|
_recorderSubscription?.cancel();
|
||||||
|
setState(() {
|
||||||
|
isPaused = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void resume() {
|
||||||
|
_audioRecorder?.resume();
|
||||||
|
_subscribe();
|
||||||
|
setState(() {
|
||||||
|
isPaused = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopAndSend(
|
||||||
|
Future<void> Function(
|
||||||
|
String path,
|
||||||
|
int duration,
|
||||||
|
List<int> waveform,
|
||||||
|
String? fileName,
|
||||||
|
) onSend,
|
||||||
|
) 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
isSending = true;
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await onSend(path, duration.inMilliseconds, waveform, fileName);
|
||||||
|
} catch (e, s) {
|
||||||
|
Logs().e('Unable to send voice message', e, s);
|
||||||
|
setState(() {
|
||||||
|
isSending = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => widget.builder(context, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,6 +1,5 @@
|
||||||
import 'package:extera_next/config/themes.dart';
|
import 'package:extera_next/config/themes.dart';
|
||||||
import 'package:extera_next/pages/chat/events/message.dart';
|
import 'package:extera_next/pages/chat/events/message.dart';
|
||||||
import 'package:extera_next/widgets/adaptive_dialogs/adaptive_dialog_action.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import 'package:extera_next/generated/l10n/l10n.dart';
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
import 'package:mime/mime.dart';
|
import 'package:mime/mime.dart';
|
||||||
|
|
||||||
import 'package:extera_next/config/app_config.dart';
|
|
||||||
import 'package:extera_next/utils/localized_exception_extension.dart';
|
import 'package:extera_next/utils/localized_exception_extension.dart';
|
||||||
import 'package:extera_next/utils/matrix_sdk_extensions/matrix_file_extension.dart';
|
import 'package:extera_next/utils/matrix_sdk_extensions/matrix_file_extension.dart';
|
||||||
import 'package:extera_next/utils/other_party_can_receive.dart';
|
import 'package:extera_next/utils/other_party_can_receive.dart';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import 'package:extera_next/config/themes.dart';
|
import 'package:extera_next/config/themes.dart';
|
||||||
import 'package:extera_next/pages/chat/events/message.dart';
|
import 'package:extera_next/pages/chat/events/message.dart';
|
||||||
import 'package:extera_next/widgets/adaptive_dialogs/adaptive_dialog_action.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -207,8 +207,9 @@ class ChatDetailsView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Divider(color: theme.dividerColor),
|
if (room.canChangeStateEvent(EventTypes.RoomTopic) ||
|
||||||
if (!room.canChangeStateEvent(EventTypes.RoomTopic))
|
room.topic.isNotEmpty) ...[
|
||||||
|
Divider(color: theme.dividerColor),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
L10n.of(context).chatDescription,
|
L10n.of(context).chatDescription,
|
||||||
|
|
@ -217,53 +218,46 @@ class ChatDetailsView extends StatelessWidget {
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
trailing:
|
||||||
else
|
room.canChangeStateEvent(EventTypes.RoomTopic)
|
||||||
|
? IconButton(
|
||||||
|
onPressed: controller.setTopicAction,
|
||||||
|
tooltip:
|
||||||
|
L10n.of(context).setChatDescription,
|
||||||
|
icon: const Icon(Icons.edit_outlined),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.symmetric(
|
||||||
child: TextButton.icon(
|
horizontal: 16.0,
|
||||||
onPressed: controller.setTopicAction,
|
),
|
||||||
label: Text(L10n.of(context).setChatDescription),
|
child: SelectableLinkify(
|
||||||
icon: const Icon(Icons.edit_outlined),
|
text: room.topic.isEmpty
|
||||||
style: TextButton.styleFrom(
|
? L10n.of(context).noChatDescriptionYet
|
||||||
iconColor:
|
: room.topic,
|
||||||
theme.colorScheme.onSecondaryContainer,
|
textScaleFactor:
|
||||||
backgroundColor:
|
MediaQuery.textScalerOf(context).scale(1),
|
||||||
theme.colorScheme.secondaryContainer,
|
options: const LinkifyOptions(humanize: false),
|
||||||
foregroundColor:
|
linkStyle: const TextStyle(
|
||||||
theme.colorScheme.onSecondaryContainer,
|
color: Colors.blueAccent,
|
||||||
|
decorationColor: Colors.blueAccent,
|
||||||
),
|
),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontStyle: room.topic.isEmpty
|
||||||
|
? FontStyle.italic
|
||||||
|
: FontStyle.normal,
|
||||||
|
color: theme.textTheme.bodyMedium!.color,
|
||||||
|
decorationColor:
|
||||||
|
theme.textTheme.bodyMedium!.color,
|
||||||
|
),
|
||||||
|
onOpen: (url) =>
|
||||||
|
UrlLauncher(context, url.url).launchUrl(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
const SizedBox(height: 16),
|
||||||
padding: const EdgeInsets.symmetric(
|
],
|
||||||
horizontal: 16.0,
|
|
||||||
),
|
|
||||||
child: SelectableLinkify(
|
|
||||||
text: room.topic.isEmpty
|
|
||||||
? L10n.of(context).noChatDescriptionYet
|
|
||||||
: room.topic,
|
|
||||||
textScaleFactor:
|
|
||||||
MediaQuery.textScalerOf(context).scale(1),
|
|
||||||
options: const LinkifyOptions(humanize: false),
|
|
||||||
linkStyle: const TextStyle(
|
|
||||||
color: Colors.blueAccent,
|
|
||||||
decorationColor: Colors.blueAccent,
|
|
||||||
),
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
fontStyle: room.topic.isEmpty
|
|
||||||
? FontStyle.italic
|
|
||||||
: FontStyle.normal,
|
|
||||||
color: theme.textTheme.bodyMedium!.color,
|
|
||||||
decorationColor:
|
|
||||||
theme.textTheme.bodyMedium!.color,
|
|
||||||
),
|
|
||||||
onOpen: (url) =>
|
|
||||||
UrlLauncher(context, url.url).launchUrl(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Divider(color: theme.dividerColor),
|
Divider(color: theme.dividerColor),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: CircleAvatar(
|
leading: CircleAvatar(
|
||||||
|
|
@ -366,4 +360,4 @@ class ChatDetailsView extends StatelessWidget {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:extera_next/config/themes.dart';
|
||||||
|
|
||||||
|
class AvatarPageHeader extends StatelessWidget {
|
||||||
|
final Widget avatar;
|
||||||
|
|
||||||
|
final void Function()? onAvatarEdit;
|
||||||
|
|
||||||
|
final Widget? textButtonLeft, textButtonRight;
|
||||||
|
|
||||||
|
final List<Widget> iconButtons;
|
||||||
|
|
||||||
|
const AvatarPageHeader({
|
||||||
|
super.key,
|
||||||
|
required this.avatar,
|
||||||
|
this.onAvatarEdit,
|
||||||
|
this.iconButtons = const [],
|
||||||
|
this.textButtonLeft,
|
||||||
|
this.textButtonRight,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
final onAvatarEdit = this.onAvatarEdit;
|
||||||
|
|
||||||
|
return Center(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: FluffyThemes.columnWidth),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
spacing: 8.0,
|
||||||
|
children: [
|
||||||
|
Stack(
|
||||||
|
children: [
|
||||||
|
avatar,
|
||||||
|
if (onAvatarEdit != null)
|
||||||
|
Positioned(
|
||||||
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
|
child: FloatingActionButton.small(
|
||||||
|
elevation: 2,
|
||||||
|
onPressed: onAvatarEdit,
|
||||||
|
heroTag: null,
|
||||||
|
child: const Icon(Icons.camera_alt_outlined),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
TextButtonTheme(
|
||||||
|
data: TextButtonThemeData(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
disabledForegroundColor: theme.colorScheme.onSurface,
|
||||||
|
foregroundColor: theme.colorScheme.onSurface,
|
||||||
|
textStyle: const TextStyle(fontWeight: FontWeight.normal),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: constraints.maxWidth / 2,
|
||||||
|
),
|
||||||
|
child: textButtonLeft,
|
||||||
|
),
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: constraints.maxWidth / 2,
|
||||||
|
),
|
||||||
|
child: textButtonRight,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButtonTheme(
|
||||||
|
data: IconButtonThemeData(
|
||||||
|
style: IconButton.styleFrom(
|
||||||
|
backgroundColor: theme.colorScheme.surfaceContainer,
|
||||||
|
iconSize: 24,
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: iconButtons,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 0.0),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,7 +10,7 @@ import 'package:extera_next/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog
|
||||||
import 'package:extera_next/widgets/future_loading_dialog.dart';
|
import 'package:extera_next/widgets/future_loading_dialog.dart';
|
||||||
import 'matrix.dart';
|
import 'matrix.dart';
|
||||||
|
|
||||||
enum ChatPopupMenuActions { details, mute, unmute, leave, search }
|
enum ChatPopupMenuActions { details, mute, unmute, emote, leave, search }
|
||||||
|
|
||||||
class ChatSettingsPopupMenu extends StatefulWidget {
|
class ChatSettingsPopupMenu extends StatefulWidget {
|
||||||
final Room room;
|
final Room room;
|
||||||
|
|
@ -31,6 +31,17 @@ class ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void goToEmoteSettings() async {
|
||||||
|
final room = widget.room;
|
||||||
|
if ((room.states['im.ponies.room_emotes'] ?? <String, Event>{})
|
||||||
|
.keys
|
||||||
|
.any((String s) => s.isNotEmpty)) {
|
||||||
|
context.push('/rooms/${room.id}/details/multiple_emotes');
|
||||||
|
} else {
|
||||||
|
context.push('/rooms/${room.id}/details/emotes');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
notificationChangeSub ??= Matrix.of(context)
|
notificationChangeSub ??= Matrix.of(context)
|
||||||
|
|
@ -73,6 +84,9 @@ class ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case ChatPopupMenuActions.emote:
|
||||||
|
goToEmoteSettings();
|
||||||
|
break;
|
||||||
case ChatPopupMenuActions.mute:
|
case ChatPopupMenuActions.mute:
|
||||||
await showFutureLoadingDialog(
|
await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue