chore: update formatting to use 120 char line length

This commit is contained in:
Garrett Watson 2023-11-13 12:14:17 -05:00
parent 0cf8bde724
commit 96d95f528d
12 changed files with 171 additions and 388 deletions

View file

@ -18,8 +18,7 @@ class AudioCacheClient {
final file = File("${directory.path}/$fileName.ogg"); final file = File("${directory.path}/$fileName.ogg");
if (!await file.exists()) { if (!await file.exists()) {
await file.create(recursive: true); await file.create(recursive: true);
final response = final response = await http.get(Uri.parse(Aux.resdbToHttp(clip.assetUri)));
await http.get(Uri.parse(Aux.resdbToHttp(clip.assetUri)));
ApiClient.checkResponseCode(response); ApiClient.checkResponseCode(response);
await file.writeAsBytes(response.bodyBytes); await file.writeAsBytes(response.bodyBytes);
} }
@ -31,8 +30,7 @@ class AudioCacheClient {
} }
if (!wavFileExists) { if (!wavFileExists) {
await wavFile.create(recursive: true); await wavFile.create(recursive: true);
await FFmpegKit.executeAsync( await FFmpegKit.executeAsync("-y -acodec libvorbis -i ${file.path} -acodec pcm_s16le ${wavFile.path}");
"-y -acodec libvorbis -i ${file.path} -acodec pcm_s16le ${wavFile.path}");
} }
return wavFile; return wavFile;
} }

View file

@ -1,8 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart' import 'package:flutter_local_notifications/flutter_local_notifications.dart' as fln;
as fln;
import 'package:recon/auxiliary.dart'; import 'package:recon/auxiliary.dart';
import 'package:recon/models/message.dart'; import 'package:recon/models/message.dart';
import 'package:recon/models/session.dart'; import 'package:recon/models/session.dart';
@ -12,8 +11,7 @@ class NotificationChannel {
final String name; final String name;
final String description; final String description;
const NotificationChannel( const NotificationChannel({required this.name, required this.id, required this.description});
{required this.name, required this.id, required this.description});
} }
class NotificationClient { class NotificationClient {
@ -23,18 +21,15 @@ class NotificationClient {
description: "Messages received from your friends", description: "Messages received from your friends",
); );
final fln.FlutterLocalNotificationsPlugin _notifier = final fln.FlutterLocalNotificationsPlugin _notifier = fln.FlutterLocalNotificationsPlugin()
fln.FlutterLocalNotificationsPlugin() ..initialize(const fln.InitializationSettings(
..initialize(const fln.InitializationSettings( android: fln.AndroidInitializationSettings("ic_notification"),
android: fln.AndroidInitializationSettings("ic_notification"), iOS: fln.DarwinInitializationSettings(),
iOS: fln.DarwinInitializationSettings(), macOS: fln.DarwinInitializationSettings(),
macOS: fln.DarwinInitializationSettings(), linux: fln.LinuxInitializationSettings(defaultActionName: "Open ReCon"),
linux: ));
fln.LinuxInitializationSettings(defaultActionName: "Open ReCon"),
));
Future<void> showUnreadMessagesNotification( Future<void> showUnreadMessagesNotification(Iterable<Message> messages) async {
Iterable<Message> messages) async {
if (messages.isEmpty) return; if (messages.isEmpty) return;
final bySender = groupBy(messages, (p0) => p0.senderId); final bySender = groupBy(messages, (p0) => p0.senderId);
@ -73,8 +68,7 @@ class NotificationClient {
break; break;
case MessageType.sessionInvite: case MessageType.sessionInvite:
try { try {
final session = final session = Session.fromMap(jsonDecode(message.content));
Session.fromMap(jsonDecode(message.content));
content = "Session Invite to ${session.name}"; content = "Session Invite to ${session.name}";
} catch (e) { } catch (e) {
content = "Session Invite"; content = "Session Invite";

View file

@ -38,23 +38,19 @@ void main() async {
), ),
); );
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge, SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge, overlays: [SystemUiOverlay.top]);
overlays: [SystemUiOverlay.top]);
await Hive.initFlutter(); await Hive.initFlutter();
final dateFormat = DateFormat.Hms(); final dateFormat = DateFormat.Hms();
Logger.root.onRecord.listen((event) => log( Logger.root.onRecord.listen(
"${dateFormat.format(event.time)}: ${event.message}", (event) => log("${dateFormat.format(event.time)}: ${event.message}", name: event.loggerName, time: event.time));
name: event.loggerName,
time: event.time));
final settingsClient = SettingsClient(); final settingsClient = SettingsClient();
await settingsClient.loadSettings(); await settingsClient.loadSettings();
final newSettings = settingsClient.currentSettings.copyWith( final newSettings =
machineId: settingsClient.currentSettings.machineId.valueOrDefault); settingsClient.currentSettings.copyWith(machineId: settingsClient.currentSettings.machineId.valueOrDefault);
await settingsClient await settingsClient.changeSettings(newSettings); // Save generated machineId to disk
.changeSettings(newSettings); // Save generated machineId to disk
AuthenticationData cachedAuth = AuthenticationData.unauthenticated(); AuthenticationData cachedAuth = AuthenticationData.unauthenticated();
try { try {
@ -63,15 +59,11 @@ void main() async {
// Ignore // Ignore
} }
runApp( runApp(ReCon(settingsClient: settingsClient, cachedAuthentication: cachedAuth));
ReCon(settingsClient: settingsClient, cachedAuthentication: cachedAuth));
} }
class ReCon extends StatefulWidget { class ReCon extends StatefulWidget {
const ReCon( const ReCon({required this.settingsClient, required this.cachedAuthentication, super.key});
{required this.settingsClient,
required this.cachedAuthentication,
super.key});
final SettingsClient settingsClient; final SettingsClient settingsClient;
final AuthenticationData cachedAuthentication; final AuthenticationData cachedAuthentication;
@ -81,8 +73,7 @@ class ReCon extends StatefulWidget {
} }
class _ReConState extends State<ReCon> { class _ReConState extends State<ReCon> {
final Typography _typography = final Typography _typography = Typography.material2021(platform: defaultTargetPlatform);
Typography.material2021(platform: defaultTargetPlatform);
final ReceivePort _port = ReceivePort(); final ReceivePort _port = ReceivePort();
late AuthenticationData _authData = widget.cachedAuthentication; late AuthenticationData _authData = widget.cachedAuthentication;
bool _checkedForUpdate = false; bool _checkedForUpdate = false;
@ -105,8 +96,7 @@ class _ReConState extends State<ReCon> {
} }
try { try {
lastDismissedSem = SemVer.fromString( lastDismissedSem = SemVer.fromString(settings.currentSettings.lastDismissedVersion.valueOrDefault);
settings.currentSettings.lastDismissedVersion.valueOrDefault);
} catch (_) { } catch (_) {
lastDismissedSem = SemVer.zero(); lastDismissedSem = SemVer.zero();
} }
@ -121,9 +111,7 @@ class _ReConState extends State<ReCon> {
return; return;
} }
if (remoteSem > currentSem && if (remoteSem > currentSem && navigator.overlay?.context != null && context.mounted) {
navigator.overlay?.context != null &&
context.mounted) {
showDialog( showDialog(
context: navigator.overlay!.context, context: navigator.overlay!.context,
builder: (context) { builder: (context) {
@ -141,8 +129,7 @@ class _ReConState extends State<ReCon> {
void initState() { void initState() {
super.initState(); super.initState();
IsolateNameServer.registerPortWithName( IsolateNameServer.registerPortWithName(_port.sendPort, 'downloader_send_port');
_port.sendPort, 'downloader_send_port');
_port.listen((dynamic data) { _port.listen((dynamic data) {
// Not useful yet? idk... // Not useful yet? idk...
// String id = data[0]; // String id = data[0];
@ -176,26 +163,21 @@ class _ReConState extends State<ReCon> {
Phoenix.rebirth(context); Phoenix.rebirth(context);
}, },
child: DynamicColorBuilder( child: DynamicColorBuilder(
builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) => builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) => MaterialApp(
MaterialApp(
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
title: 'ReCon', title: 'ReCon',
theme: ThemeData( theme: ThemeData(
useMaterial3: true, useMaterial3: true,
textTheme: _typography.black, textTheme: _typography.black,
colorScheme: lightDynamic ?? colorScheme:
ColorScheme.fromSeed( lightDynamic ?? ColorScheme.fromSeed(seedColor: Colors.purple, brightness: Brightness.light),
seedColor: Colors.purple, brightness: Brightness.light),
), ),
darkTheme: ThemeData( darkTheme: ThemeData(
useMaterial3: true, useMaterial3: true,
textTheme: _typography.white, textTheme: _typography.white,
colorScheme: darkDynamic ?? colorScheme: darkDynamic ?? ColorScheme.fromSeed(seedColor: Colors.purple, brightness: Brightness.dark),
ColorScheme.fromSeed(
seedColor: Colors.purple, brightness: Brightness.dark),
), ),
themeMode: ThemeMode.values[widget themeMode: ThemeMode.values[widget.settingsClient.currentSettings.themeMode.valueOrDefault],
.settingsClient.currentSettings.themeMode.valueOrDefault],
home: Builder( home: Builder(
// Builder is necessary here since we need a context which has access to the ClientHolder // Builder is necessary here since we need a context which has access to the ClientHolder
builder: (context) { builder: (context) {
@ -208,8 +190,7 @@ class _ReConState extends State<ReCon> {
create: (context) => MessagingClient( create: (context) => MessagingClient(
apiClient: clientHolder.apiClient, apiClient: clientHolder.apiClient,
settingsClient: clientHolder.settingsClient, settingsClient: clientHolder.settingsClient,
notificationClient: notificationClient: clientHolder.notificationClient,
clientHolder.notificationClient,
), ),
), ),
ChangeNotifierProvider( ChangeNotifierProvider(
@ -226,15 +207,13 @@ class _ReConState extends State<ReCon> {
], ],
child: AnnotatedRegion<SystemUiOverlayStyle>( child: AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle( value: SystemUiOverlayStyle(
statusBarColor: statusBarColor: Theme.of(context).colorScheme.surfaceVariant,
Theme.of(context).colorScheme.surfaceVariant,
), ),
child: const Home(), child: const Home(),
), ),
) )
: LoginScreen( : LoginScreen(
onLoginSuccessful: onLoginSuccessful: (AuthenticationData authData) async {
(AuthenticationData authData) async {
if (authData.isAuthenticated) { if (authData.isAuthenticated) {
setState(() { setState(() {
_authData = authData; _authData = authData;

View file

@ -28,8 +28,7 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
final id = event.task.taskId; final id = event.task.taskId;
final status = event is TaskStatusUpdate ? event.status : null; final status = event is TaskStatusUpdate ? event.status : null;
final progress = event is TaskProgressUpdate ? event.progress : null; final progress = event is TaskProgressUpdate ? event.progress : null;
final SendPort? send = final SendPort? send = IsolateNameServer.lookupPortByName('downloader_send_port');
IsolateNameServer.lookupPortByName('downloader_send_port');
send?.send([id, status, progress]); send?.send([id, status, progress]);
} }
@ -74,9 +73,7 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
style: TextStyle( style: TextStyle(
color: iClient.sortReverse == false color: iClient.sortReverse == false
? Theme.of(context).colorScheme.primary ? Theme.of(context).colorScheme.primary
: Theme.of(context) : Theme.of(context).colorScheme.onSurface,
.colorScheme
.onSurface,
), ),
), ),
], ],
@ -89,9 +86,7 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
Icon(Icons.arrow_downward, Icon(Icons.arrow_downward,
color: iClient.sortReverse == true color: iClient.sortReverse == true
? Theme.of(context).colorScheme.primary ? Theme.of(context).colorScheme.primary
: Theme.of(context) : Theme.of(context).colorScheme.onSurface),
.colorScheme
.onSurface),
const SizedBox( const SizedBox(
width: 8, width: 8,
), ),
@ -100,9 +95,7 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
style: TextStyle( style: TextStyle(
color: iClient.sortReverse == true color: iClient.sortReverse == true
? Theme.of(context).colorScheme.primary ? Theme.of(context).colorScheme.primary
: Theme.of(context) : Theme.of(context).colorScheme.onSurface,
.colorScheme
.onSurface,
), ),
), ),
], ],
@ -128,27 +121,18 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
Icon( Icon(
e.icon, e.icon,
color: iClient.sortMode == e color: iClient.sortMode == e
? Theme.of(context) ? Theme.of(context).colorScheme.primary
.colorScheme : Theme.of(context).colorScheme.onSurface,
.primary
: Theme.of(context)
.colorScheme
.onSurface,
), ),
const SizedBox( const SizedBox(
width: 8, width: 8,
), ),
Text( Text(
toBeginningOfSentenceCase(e.name) ?? toBeginningOfSentenceCase(e.name) ?? e.name,
e.name,
style: TextStyle( style: TextStyle(
color: iClient.sortMode == e color: iClient.sortMode == e
? Theme.of(context) ? Theme.of(context).colorScheme.primary
.colorScheme : Theme.of(context).colorScheme.onSurface,
.primary
: Theme.of(context)
.colorScheme
.onSurface,
), ),
) )
], ],
@ -173,8 +157,7 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
actions: [ actions: [
if (iClient.selectedRecordCount == 1 && if (iClient.selectedRecordCount == 1 &&
(iClient.selectedRecords.firstOrNull?.isLink == true || (iClient.selectedRecords.firstOrNull?.isLink == true ||
iClient.selectedRecords.firstOrNull?.isItem == iClient.selectedRecords.firstOrNull?.isItem == true))
true))
IconButton( IconButton(
onPressed: () { onPressed: () {
Share.share(iClient.selectedRecords.first.assetUri); Share.share(iClient.selectedRecords.first.assetUri);
@ -186,12 +169,8 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
onPressed: () async { onPressed: () async {
final selectedRecords = iClient.selectedRecords; final selectedRecords = iClient.selectedRecords;
final assetUris = selectedRecords final assetUris = selectedRecords.map((record) => record.assetUri).toList();
.map((record) => record.assetUri) final thumbUris = selectedRecords.map((record) => record.thumbnailUri).toList();
.toList();
final thumbUris = selectedRecords
.map((record) => record.thumbnailUri)
.toList();
final selectedUris = await showDialog<List<String>>( final selectedUris = await showDialog<List<String>>(
context: context, context: context,
@ -232,8 +211,7 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
); );
if (selectedUris == null) return; if (selectedUris == null) return;
final directory = await FilePicker.platform final directory = await FilePicker.platform.getDirectoryPath(dialogTitle: "Download to...");
.getDirectoryPath(dialogTitle: "Download to...");
if (directory == null) { if (directory == null) {
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
@ -248,17 +226,14 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(
content: content: Text("Selected directory is invalid"),
Text("Selected directory is invalid"),
), ),
); );
} }
return; return;
} }
for (var record in selectedRecords) { for (var record in selectedRecords) {
final uri = selectedUris == thumbUris final uri = selectedUris == thumbUris ? record.thumbnailUri : record.assetUri;
? record.thumbnailUri
: record.assetUri;
final filename = final filename =
"${record.id.split("-")[1]}-${record.formattedName.toString()}${extension(uri)}"; "${record.id.split("-")[1]}-${record.formattedName.toString()}${extension(uri)}";
final downloadTask = DownloadTask( final downloadTask = DownloadTask(
@ -268,22 +243,18 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
filename: filename, filename: filename,
updates: Updates.statusAndProgress, updates: Updates.statusAndProgress,
); );
await FileDownloader() await FileDownloader().enqueue(downloadTask).then((b) {
.enqueue(downloadTask)
.then((b) {
if (context.mounted) { if (context.mounted) {
if (b) { if (b) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text( content: Text("Downloaded ${record.formattedName.toString()}"),
"Downloaded ${record.formattedName.toString()}"),
), ),
); );
} else { } else {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text( content: Text("Failed to download ${record.formattedName.toString()}"),
"Failed to download ${record.formattedName.toString()}"),
), ),
); );
} }
@ -317,10 +288,8 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
title: Text(iClient.selectedRecordCount == 1 title: Text(iClient.selectedRecordCount == 1
? "Really delete this Record?" ? "Really delete this Record?"
: "Really delete ${iClient.selectedRecordCount} Records?"), : "Really delete ${iClient.selectedRecordCount} Records?"),
content: const Text( content: const Text("This action cannot be undone!"),
"This action cannot be undone!"), actionsAlignment: MainAxisAlignment.spaceBetween,
actionsAlignment:
MainAxisAlignment.spaceBetween,
actions: [ actions: [
TextButton( TextButton(
onPressed: loading onPressed: loading
@ -336,8 +305,7 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
if (loading) if (loading)
const SizedBox.square( const SizedBox.square(
dimension: 16, dimension: 16,
child: CircularProgressIndicator( child: CircularProgressIndicator(strokeWidth: 2),
strokeWidth: 2),
), ),
const SizedBox( const SizedBox(
width: 4, width: 4,
@ -350,16 +318,12 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
loading = true; loading = true;
}); });
try { try {
await iClient await iClient.deleteSelectedRecords();
.deleteSelectedRecords();
} catch (e) { } catch (e) {
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of( ScaffoldMessenger.of(context).showSnackBar(
context)
.showSnackBar(
SnackBar( SnackBar(
content: Text( content: Text("Failed to delete one or more records: $e"),
"Failed to delete one or more records: $e"),
), ),
); );
} }
@ -368,16 +332,12 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
}); });
} }
if (context.mounted) { if (context.mounted) {
Navigator.of(context) Navigator.of(context).pop(true);
.pop(true);
} }
iClient iClient.reloadCurrentDirectory();
.reloadCurrentDirectory();
}, },
style: TextButton.styleFrom( style: TextButton.styleFrom(
foregroundColor: Theme.of(context) foregroundColor: Theme.of(context).colorScheme.error,
.colorScheme
.error,
), ),
child: const Text("Delete"), child: const Text("Delete"),
), ),

View file

@ -83,10 +83,8 @@ class _LoginScreenState extends State<LoginScreen> {
_error = "Please enter your 2FA-Code"; _error = "Please enter your 2FA-Code";
_totpFocusNode.requestFocus(); _totpFocusNode.requestFocus();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) { WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_scrollController.animateTo( _scrollController.animateTo(_scrollController.position.maxScrollExtent,
_scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 400), curve: Curves.easeOutCirc);
duration: const Duration(milliseconds: 400),
curve: Curves.easeOutCirc);
}); });
} else { } else {
_error = "The given 2FA code is not valid."; _error = "The given 2FA code is not valid.";
@ -115,16 +113,14 @@ class _LoginScreenState extends State<LoginScreen> {
context: context, context: context,
builder: (context) { builder: (context) {
return AlertDialog( return AlertDialog(
title: const Text( title: const Text("This app needs to ask your permission to send background notifications."),
"This app needs to ask your permission to send background notifications."),
content: const Text("Are you okay with that?"), content: const Text("Are you okay with that?"),
actions: [ actions: [
TextButton( TextButton(
onPressed: () async { onPressed: () async {
Navigator.of(context).pop(); Navigator.of(context).pop();
await settingsClient.changeSettings(settingsClient await settingsClient
.currentSettings .changeSettings(settingsClient.currentSettings.copyWith(notificationsDenied: true));
.copyWith(notificationsDenied: true));
}, },
child: const Text("No"), child: const Text("No"),
), ),
@ -133,31 +129,21 @@ class _LoginScreenState extends State<LoginScreen> {
Navigator.of(context).pop(); Navigator.of(context).pop();
final requestResult = switch (Platform.operatingSystem) { final requestResult = switch (Platform.operatingSystem) {
"android" => await notificationManager "android" => await notificationManager
.resolvePlatformSpecificImplementation< .resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
AndroidFlutterLocalNotificationsPlugin>()
?.requestNotificationsPermission(), ?.requestNotificationsPermission(),
"fuschia" => "fuschia" => null, // "fuschia" is not supported by flutter_local_notifications
null, // "fuschia" is not supported by flutter_local_notifications
"ios" => await notificationManager "ios" => await notificationManager
.resolvePlatformSpecificImplementation< .resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()
IOSFlutterLocalNotificationsPlugin>() ?.requestPermissions(alert: true, badge: true, sound: true),
?.requestPermissions(
alert: true, badge: true, sound: true),
"linux" => null, // don't want to deal with this right now "linux" => null, // don't want to deal with this right now
"macos" => await notificationManager "macos" => await notificationManager
.resolvePlatformSpecificImplementation< .resolvePlatformSpecificImplementation<MacOSFlutterLocalNotificationsPlugin>()
MacOSFlutterLocalNotificationsPlugin>() ?.requestPermissions(alert: true, badge: true, sound: true),
?.requestPermissions( "windows" => null, // also don't want to deal with this right now
alert: true, badge: true, sound: true),
"windows" =>
null, // also don't want to deal with this right now
_ => null, _ => null,
}; };
await settingsClient.changeSettings( await settingsClient.changeSettings(settingsClient.currentSettings
settingsClient.currentSettings.copyWith( .copyWith(notificationsDenied: requestResult == null ? false : !requestResult));
notificationsDenied: requestResult == null
? false
: !requestResult));
}, },
child: const Text("Yes"), child: const Text("Yes"),
) )
@ -183,8 +169,7 @@ class _LoginScreenState extends State<LoginScreen> {
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 64), padding: const EdgeInsets.symmetric(vertical: 64),
child: Center( child: Center(
child: Text("Sign In", child: Text("Sign In", style: Theme.of(context).textTheme.headlineMedium),
style: Theme.of(context).textTheme.headlineMedium),
), ),
), ),
Padding( Padding(
@ -193,8 +178,7 @@ class _LoginScreenState extends State<LoginScreen> {
controller: _usernameController, controller: _usernameController,
onEditingComplete: () => _passwordFocusNode.requestFocus(), onEditingComplete: () => _passwordFocusNode.requestFocus(),
decoration: InputDecoration( decoration: InputDecoration(
contentPadding: contentPadding: const EdgeInsets.symmetric(vertical: 20, horizontal: 24),
const EdgeInsets.symmetric(vertical: 20, horizontal: 24),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(32), borderRadius: BorderRadius.circular(32),
), ),
@ -210,26 +194,22 @@ class _LoginScreenState extends State<LoginScreen> {
onEditingComplete: submit, onEditingComplete: submit,
obscureText: true, obscureText: true,
decoration: InputDecoration( decoration: InputDecoration(
contentPadding: contentPadding: const EdgeInsets.symmetric(vertical: 20, horizontal: 24),
const EdgeInsets.symmetric(vertical: 20, horizontal: 24), border: OutlineInputBorder(borderRadius: BorderRadius.circular(32)),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(32)),
labelText: 'Password', labelText: 'Password',
), ),
), ),
), ),
if (_needsTotp) if (_needsTotp)
Padding( Padding(
padding: padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 64),
const EdgeInsets.symmetric(vertical: 16, horizontal: 64),
child: TextField( child: TextField(
controller: _totpController, controller: _totpController,
focusNode: _totpFocusNode, focusNode: _totpFocusNode,
onEditingComplete: submit, onEditingComplete: submit,
obscureText: false, obscureText: false,
decoration: InputDecoration( decoration: InputDecoration(
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(vertical: 20, horizontal: 24),
vertical: 20, horizontal: 24),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(32), borderRadius: BorderRadius.circular(32),
), ),
@ -252,13 +232,8 @@ class _LoginScreenState extends State<LoginScreen> {
opacity: _errorOpacity, opacity: _errorOpacity,
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
child: Padding( child: Padding(
padding: padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 64),
const EdgeInsets.symmetric(vertical: 16, horizontal: 64), child: Text(_error, style: Theme.of(context).textTheme.labelMedium?.copyWith(color: Colors.red)),
child: Text(_error,
style: Theme.of(context)
.textTheme
.labelMedium
?.copyWith(color: Colors.red)),
), ),
), ),
) )

View file

@ -9,8 +9,7 @@ import 'package:recon/models/message.dart';
import 'package:recon/widgets/messages/message_state_indicator.dart'; import 'package:recon/widgets/messages/message_state_indicator.dart';
class MessageAudioPlayer extends StatefulWidget { class MessageAudioPlayer extends StatefulWidget {
const MessageAudioPlayer( const MessageAudioPlayer({required this.message, this.foregroundColor, super.key});
{required this.message, this.foregroundColor, super.key});
final Message message; final Message message;
final Color? foregroundColor; final Color? foregroundColor;
@ -19,8 +18,7 @@ class MessageAudioPlayer extends StatefulWidget {
State<MessageAudioPlayer> createState() => _MessageAudioPlayerState(); State<MessageAudioPlayer> createState() => _MessageAudioPlayerState();
} }
class _MessageAudioPlayerState extends State<MessageAudioPlayer> class _MessageAudioPlayerState extends State<MessageAudioPlayer> with WidgetsBindingObserver {
with WidgetsBindingObserver {
final AudioPlayer _audioPlayer = AudioPlayer(); final AudioPlayer _audioPlayer = AudioPlayer();
Future? _audioFileFuture; Future? _audioFileFuture;
double _sliderValue = 0; double _sliderValue = 0;
@ -43,8 +41,7 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer>
super.didChangeDependencies(); super.didChangeDependencies();
final audioCache = Provider.of<AudioCacheClient>(context); final audioCache = Provider.of<AudioCacheClient>(context);
_audioFileFuture = audioCache _audioFileFuture = audioCache
.cachedNetworkAudioFile( .cachedNetworkAudioFile(AudioClipContent.fromMap(jsonDecode(widget.message.content)))
AudioClipContent.fromMap(jsonDecode(widget.message.content)))
.then((value) => _audioPlayer.setFilePath(value.path)) .then((value) => _audioPlayer.setFilePath(value.path))
.whenComplete(() => _audioPlayer.setLoopMode(LoopMode.off)); .whenComplete(() => _audioPlayer.setLoopMode(LoopMode.off));
} }
@ -55,8 +52,7 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer>
if (oldWidget.message.id == widget.message.id) return; if (oldWidget.message.id == widget.message.id) return;
final audioCache = Provider.of<AudioCacheClient>(context); final audioCache = Provider.of<AudioCacheClient>(context);
_audioFileFuture = audioCache _audioFileFuture = audioCache
.cachedNetworkAudioFile( .cachedNetworkAudioFile(AudioClipContent.fromMap(jsonDecode(widget.message.content)))
AudioClipContent.fromMap(jsonDecode(widget.message.content)))
.then((value) async { .then((value) async {
final path = _audioPlayer.setFilePath(value.path); final path = _audioPlayer.setFilePath(value.path);
await _audioPlayer.setLoopMode(LoopMode.off); await _audioPlayer.setLoopMode(LoopMode.off);
@ -91,10 +87,7 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer>
textAlign: TextAlign.center, textAlign: TextAlign.center,
softWrap: true, softWrap: true,
maxLines: 3, maxLines: 3,
style: Theme.of(context) style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Theme.of(context).colorScheme.error),
.textTheme
.bodySmall
?.copyWith(color: Theme.of(context).colorScheme.error),
), ),
], ],
), ),
@ -108,8 +101,7 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer>
stream: _audioPlayer.playerStateStream, stream: _audioPlayer.playerStateStream,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasError) { if (snapshot.hasError) {
FlutterError.reportError(FlutterErrorDetails( FlutterError.reportError(FlutterErrorDetails(exception: snapshot.error!, stack: snapshot.stackTrace));
exception: snapshot.error!, stack: snapshot.stackTrace));
return _createErrorWidget("Failed to load audio-message."); return _createErrorWidget("Failed to load audio-message.");
} }
final playerState = snapshot.data; final playerState = snapshot.data;
@ -125,9 +117,8 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer>
future: _audioFileFuture, future: _audioFileFuture,
builder: (context, fileSnapshot) { builder: (context, fileSnapshot) {
if (fileSnapshot.hasError) { if (fileSnapshot.hasError) {
FlutterError.reportError(FlutterErrorDetails( FlutterError.reportError(
exception: fileSnapshot.error!, FlutterErrorDetails(exception: fileSnapshot.error!, stack: fileSnapshot.stackTrace));
stack: fileSnapshot.stackTrace));
return const IconButton( return const IconButton(
icon: Icon(Icons.warning), icon: Icon(Icons.warning),
tooltip: "Failed to load audio-message.", tooltip: "Failed to load audio-message.",
@ -138,8 +129,7 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer>
onPressed: fileSnapshot.hasData && onPressed: fileSnapshot.hasData &&
snapshot.hasData && snapshot.hasData &&
playerState != null && playerState != null &&
playerState.processingState != playerState.processingState != ProcessingState.loading
ProcessingState.loading
? () { ? () {
switch (playerState.processingState) { switch (playerState.processingState) {
case ProcessingState.idle: case ProcessingState.idle:
@ -162,15 +152,11 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer>
: null, : null,
color: widget.foregroundColor, color: widget.foregroundColor,
icon: Icon( icon: Icon(
((_audioPlayer.duration ?? ((_audioPlayer.duration ?? const Duration(days: 9999)) - _audioPlayer.position)
const Duration(days: 9999)) -
_audioPlayer.position)
.inMilliseconds < .inMilliseconds <
10 10
? Icons.replay ? Icons.replay
: ((playerState?.playing ?? false) : ((playerState?.playing ?? false) ? Icons.pause : Icons.play_arrow),
? Icons.pause
: Icons.play_arrow),
), ),
); );
}, },
@ -180,16 +166,14 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer>
builder: (context, snapshot) { builder: (context, snapshot) {
_sliderValue = _audioPlayer.duration == null _sliderValue = _audioPlayer.duration == null
? 0 ? 0
: (_audioPlayer.position.inMilliseconds / : (_audioPlayer.position.inMilliseconds / (_audioPlayer.duration!.inMilliseconds))
(_audioPlayer.duration!.inMilliseconds))
.clamp(0, 1); .clamp(0, 1);
return StatefulBuilder( return StatefulBuilder(
// Not sure if this makes sense here... // Not sure if this makes sense here...
builder: (context, setState) { builder: (context, setState) {
return SliderTheme( return SliderTheme(
data: SliderThemeData( data: SliderThemeData(
inactiveTrackColor: inactiveTrackColor: widget.foregroundColor?.withAlpha(100),
widget.foregroundColor?.withAlpha(100),
), ),
child: Slider( child: Slider(
thumbColor: widget.foregroundColor, thumbColor: widget.foregroundColor,
@ -203,11 +187,7 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer>
}); });
_audioPlayer.seek( _audioPlayer.seek(
Duration( Duration(
milliseconds: (value * milliseconds: (value * (_audioPlayer.duration?.inMilliseconds ?? 0)).round(),
(_audioPlayer
.duration?.inMilliseconds ??
0))
.round(),
), ),
); );
}, },
@ -231,8 +211,10 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer>
builder: (context, snapshot) { builder: (context, snapshot) {
return Text( return Text(
"${snapshot.data?.format() ?? "??"}/${_audioPlayer.duration?.format() ?? "??"}", "${snapshot.data?.format() ?? "??"}/${_audioPlayer.duration?.format() ?? "??"}",
style: Theme.of(context).textTheme.bodySmall?.copyWith( style: Theme.of(context)
color: widget.foregroundColor?.withAlpha(150)), .textTheme
.bodySmall
?.copyWith(color: widget.foregroundColor?.withAlpha(150)),
); );
}, },
), ),

View file

@ -18,11 +18,7 @@ import 'package:recon/widgets/messages/message_attachment_list.dart';
import 'package:record/record.dart'; import 'package:record/record.dart';
class MessageInputBar extends StatefulWidget { class MessageInputBar extends StatefulWidget {
const MessageInputBar( const MessageInputBar({this.disabled = false, required this.recipient, this.onMessageSent, super.key});
{this.disabled = false,
required this.recipient,
this.onMessageSent,
super.key});
final bool disabled; final bool disabled;
final Friend recipient; final Friend recipient;
@ -45,8 +41,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
String _currentText = ""; String _currentText = "";
double? _sendProgress; double? _sendProgress;
bool get _isRecording => _recordingStartTime != null; bool get _isRecording => _recordingStartTime != null;
set _isRecording(value) => set _isRecording(value) => _recordingStartTime = value ? DateTime.now() : null;
_recordingStartTime = value ? DateTime.now() : null;
bool _recordingCancelled = false; bool _recordingCancelled = false;
@override @override
@ -56,8 +51,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
super.dispose(); super.dispose();
} }
Future<void> sendTextMessage( Future<void> sendTextMessage(ApiClient client, MessagingClient mClient, String content) async {
ApiClient client, MessagingClient mClient, String content) async {
if (content.isEmpty) return; if (content.isEmpty) return;
final message = Message( final message = Message(
id: Message.generateId(), id: Message.generateId(),
@ -71,11 +65,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
mClient.sendMessage(message); mClient.sendMessage(message);
} }
Future<void> sendImageMessage( Future<void> sendImageMessage(ApiClient client, MessagingClient mClient, File file, String machineId,
ApiClient client,
MessagingClient mClient,
File file,
String machineId,
void Function(double progress) progressCallback) async { void Function(double progress) progressCallback) async {
final record = await RecordApi.uploadImage( final record = await RecordApi.uploadImage(
client, client,
@ -94,11 +84,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
mClient.sendMessage(message); mClient.sendMessage(message);
} }
Future<void> sendVoiceMessage( Future<void> sendVoiceMessage(ApiClient client, MessagingClient mClient, File file, String machineId,
ApiClient client,
MessagingClient mClient,
File file,
String machineId,
void Function(double progress) progressCallback) async { void Function(double progress) progressCallback) async {
final record = await RecordApi.uploadVoiceClip( final record = await RecordApi.uploadVoiceClip(
client, client,
@ -118,11 +104,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
mClient.sendMessage(message); mClient.sendMessage(message);
} }
Future<void> sendRawFileMessage( Future<void> sendRawFileMessage(ApiClient client, MessagingClient mClient, File file, String machineId,
ApiClient client,
MessagingClient mClient,
File file,
String machineId,
void Function(double progress) progressCallback) async { void Function(double progress) progressCallback) async {
final record = await RecordApi.uploadRawFile( final record = await RecordApi.uploadRawFile(
client, client,
@ -205,9 +187,8 @@ class _MessageInputBarState extends State<MessageInputBar> {
_sendProgress = 0; _sendProgress = 0;
}); });
final apiClient = cHolder.apiClient; final apiClient = cHolder.apiClient;
await sendVoiceMessage(apiClient, mClient, file, await sendVoiceMessage(
cHolder.settingsClient.currentSettings.machineId.valueOrDefault, apiClient, mClient, file, cHolder.settingsClient.currentSettings.machineId.valueOrDefault, (progress) {
(progress) {
setState(() { setState(() {
_sendProgress = progress; _sendProgress = progress;
}); });
@ -229,8 +210,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
top: false, top: false,
child: Column( child: Column(
children: [ children: [
if (_isSending && _sendProgress != null) if (_isSending && _sendProgress != null) LinearProgressIndicator(value: _sendProgress),
LinearProgressIndicator(value: _sendProgress),
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceVariant, color: Theme.of(context).colorScheme.surfaceVariant,
@ -239,8 +219,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
switchInCurve: Curves.easeOut, switchInCurve: Curves.easeOut,
switchOutCurve: Curves.easeOut, switchOutCurve: Curves.easeOut,
transitionBuilder: (Widget child, animation) => transitionBuilder: (Widget child, animation) => SizeTransition(
SizeTransition(
sizeFactor: animation, sizeFactor: animation,
child: child, child: child,
), ),
@ -252,19 +231,12 @@ class _MessageInputBarState extends State<MessageInputBar> {
onPressed: _isSending onPressed: _isSending
? null ? null
: () async { : () async {
final result = await FilePicker.platform final result =
.pickFiles( await FilePicker.platform.pickFiles(type: FileType.image, allowMultiple: true);
type: FileType.image,
allowMultiple: true);
if (result != null) { if (result != null) {
setState(() { setState(() {
_loadedFiles.addAll(result.files _loadedFiles.addAll(result.files
.map((e) => e.path != null .map((e) => e.path != null ? (FileType.image, File(e.path!)) : null)
? (
FileType.image,
File(e.path!)
)
: null)
.whereNotNull()); .whereNotNull());
}); });
} }
@ -276,29 +248,23 @@ class _MessageInputBarState extends State<MessageInputBar> {
onPressed: _isSending onPressed: _isSending
? null ? null
: () async { : () async {
final picture = await _imagePicker final picture = await _imagePicker.pickImage(source: ImageSource.camera);
.pickImage(source: ImageSource.camera);
if (picture == null) { if (picture == null) {
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context) ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar( .showSnackBar(const SnackBar(content: Text("Failed to get image path")));
content: Text(
"Failed to get image path")));
} }
return; return;
} }
final file = File(picture.path); final file = File(picture.path);
if (await file.exists()) { if (await file.exists()) {
setState(() { setState(() {
_loadedFiles _loadedFiles.add((FileType.image, file));
.add((FileType.image, file));
}); });
} else { } else {
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context) ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar( .showSnackBar(const SnackBar(content: Text("Failed to load image file")));
content: Text(
"Failed to load image file")));
} }
} }
}, },
@ -309,16 +275,12 @@ class _MessageInputBarState extends State<MessageInputBar> {
onPressed: _isSending onPressed: _isSending
? null ? null
: () async { : () async {
final result = await FilePicker.platform final result =
.pickFiles( await FilePicker.platform.pickFiles(type: FileType.any, allowMultiple: true);
type: FileType.any,
allowMultiple: true);
if (result != null) { if (result != null) {
setState(() { setState(() {
_loadedFiles.addAll(result.files _loadedFiles.addAll(result.files
.map((e) => e.path != null .map((e) => e.path != null ? (FileType.any, File(e.path!)) : null)
? (FileType.any, File(e.path!))
: null)
.whereNotNull()); .whereNotNull());
}); });
} }
@ -332,8 +294,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
(_, _) => MessageAttachmentList( (_, _) => MessageAttachmentList(
disabled: _isSending, disabled: _isSending,
initialFiles: _loadedFiles, initialFiles: _loadedFiles,
onChange: (List<(FileType, File)> loadedFiles) => onChange: (List<(FileType, File)> loadedFiles) => setState(() {
setState(() {
_loadedFiles.clear(); _loadedFiles.clear();
_loadedFiles.addAll(loadedFiles); _loadedFiles.addAll(loadedFiles);
}), }),
@ -345,13 +306,10 @@ class _MessageInputBarState extends State<MessageInputBar> {
children: [ children: [
AnimatedSwitcher( AnimatedSwitcher(
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
transitionBuilder: transitionBuilder: (Widget child, Animation<double> animation) => FadeTransition(
(Widget child, Animation<double> animation) =>
FadeTransition(
opacity: animation, opacity: animation,
child: RotationTransition( child: RotationTransition(
turns: Tween<double>(begin: 0.6, end: 1) turns: Tween<double>(begin: 0.6, end: 1).animate(animation),
.animate(animation),
child: child, child: child,
), ),
), ),
@ -360,9 +318,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
onPressed: () {}, onPressed: () {},
icon: Icon( icon: Icon(
Icons.delete, Icons.delete,
color: _recordingCancelled color: _recordingCancelled ? Theme.of(context).colorScheme.error : null,
? Theme.of(context).colorScheme.error
: null,
), ),
), ),
(false, _) => IconButton( (false, _) => IconButton(
@ -371,9 +327,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
? null ? null
: () { : () {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(content: Text("Sorry, this feature is not yet available")));
content: Text(
"Sorry, this feature is not yet available")));
return; return;
// setState(() { // setState(() {
// _attachmentPickerOpen = true; // _attachmentPickerOpen = true;
@ -392,10 +346,8 @@ class _MessageInputBarState extends State<MessageInputBar> {
await showDialog( await showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: const Text( title: const Text("Remove all attachments"),
"Remove all attachments"), content: const Text("This will remove all attachments, are you sure?"),
content: const Text(
"This will remove all attachments, are you sure?"),
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () {
@ -407,8 +359,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
onPressed: () { onPressed: () {
setState(() { setState(() {
_loadedFiles.clear(); _loadedFiles.clear();
_attachmentPickerOpen = _attachmentPickerOpen = false;
false;
}); });
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
@ -430,8 +381,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
), ),
Expanded( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4),
vertical: 8, horizontal: 4),
child: Stack( child: Stack(
children: [ children: [
TextField( TextField(
@ -453,12 +403,9 @@ class _MessageInputBarState extends State<MessageInputBar> {
style: Theme.of(context).textTheme.bodyLarge, style: Theme.of(context).textTheme.bodyLarge,
decoration: InputDecoration( decoration: InputDecoration(
isDense: true, isDense: true,
hintText: _isRecording hintText: _isRecording ? "" : "Message ${widget.recipient.username}...",
? ""
: "Message ${widget.recipient.username}...",
hintMaxLines: 1, hintMaxLines: 1,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
horizontal: 16, vertical: 12),
fillColor: Colors.black26, fillColor: Colors.black26,
filled: true, filled: true,
border: OutlineInputBorder( border: OutlineInputBorder(
@ -468,9 +415,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
), ),
AnimatedSwitcher( AnimatedSwitcher(
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
transitionBuilder: transitionBuilder: (Widget child, Animation<double> animation) => FadeTransition(
(Widget child, Animation<double> animation) =>
FadeTransition(
opacity: animation, opacity: animation,
child: SlideTransition( child: SlideTransition(
position: Tween<Offset>( position: Tween<Offset>(
@ -482,41 +427,33 @@ class _MessageInputBarState extends State<MessageInputBar> {
), ),
child: _isRecording child: _isRecording
? Padding( ? Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(vertical: 12.0),
vertical: 12.0),
child: _recordingCancelled child: _recordingCancelled
? Row( ? Row(
mainAxisAlignment: mainAxisAlignment: MainAxisAlignment.start,
MainAxisAlignment.start,
children: [ children: [
const SizedBox( const SizedBox(
width: 8, width: 8,
), ),
const Padding( const Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(horizontal: 8.0),
horizontal: 8.0),
child: Icon( child: Icon(
Icons.cancel, Icons.cancel,
color: Colors.red, color: Colors.red,
size: 16, size: 16,
), ),
), ),
Text("Cancel Recording", Text("Cancel Recording", style: Theme.of(context).textTheme.titleMedium),
style: Theme.of(context)
.textTheme
.titleMedium),
], ],
) )
: Row( : Row(
mainAxisAlignment: mainAxisAlignment: MainAxisAlignment.start,
MainAxisAlignment.start,
children: [ children: [
const SizedBox( const SizedBox(
width: 8, width: 8,
), ),
const Padding( const Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(horizontal: 8.0),
horizontal: 8.0),
child: Icon( child: Icon(
Icons.circle, Icons.circle,
color: Colors.red, color: Colors.red,
@ -524,14 +461,10 @@ class _MessageInputBarState extends State<MessageInputBar> {
), ),
), ),
StreamBuilder<Duration>( StreamBuilder<Duration>(
stream: stream: _recordingDurationStream(),
_recordingDurationStream(),
builder: (context, snapshot) { builder: (context, snapshot) {
return Text( return Text("Recording: ${snapshot.data?.format()}",
"Recording: ${snapshot.data?.format()}", style: Theme.of(context).textTheme.titleMedium);
style: Theme.of(context)
.textTheme
.titleMedium);
}), }),
], ],
), ),
@ -544,13 +477,10 @@ class _MessageInputBarState extends State<MessageInputBar> {
), ),
AnimatedSwitcher( AnimatedSwitcher(
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
transitionBuilder: transitionBuilder: (Widget child, Animation<double> animation) => FadeTransition(
(Widget child, Animation<double> animation) =>
FadeTransition(
opacity: animation, opacity: animation,
child: RotationTransition( child: RotationTransition(
turns: Tween<double>(begin: 0.5, end: 1) turns: Tween<double>(begin: 0.5, end: 1).animate(animation),
.animate(animation),
child: child, child: child,
), ),
), ),
@ -563,12 +493,9 @@ class _MessageInputBarState extends State<MessageInputBar> {
? null ? null
: () async { : () async {
final cHolder = ClientHolder.of(context); final cHolder = ClientHolder.of(context);
final sMsgnr = final sMsgnr = ScaffoldMessenger.of(context);
ScaffoldMessenger.of(context); final settings = cHolder.settingsClient.currentSettings;
final settings = final toSend = List<(FileType, File)>.from(_loadedFiles);
cHolder.settingsClient.currentSettings;
final toSend = List<(FileType, File)>.from(
_loadedFiles);
setState(() { setState(() {
_isSending = true; _isSending = true;
_sendProgress = 0; _sendProgress = 0;
@ -586,8 +513,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
file.$2, file.$2,
settings.machineId.valueOrDefault, settings.machineId.valueOrDefault,
(progress) => setState(() { (progress) => setState(() {
_sendProgress = totalProgress + _sendProgress = totalProgress + progress * 1 / toSend.length;
progress * 1 / toSend.length;
}), }),
); );
} else { } else {
@ -596,12 +522,8 @@ class _MessageInputBarState extends State<MessageInputBar> {
mClient, mClient,
file.$2, file.$2,
settings.machineId.valueOrDefault, settings.machineId.valueOrDefault,
(progress) => setState(() => (progress) => setState(
_sendProgress = () => _sendProgress = totalProgress + progress * 1 / toSend.length));
totalProgress +
progress *
1 /
toSend.length));
} }
} }
setState(() { setState(() {
@ -609,22 +531,15 @@ class _MessageInputBarState extends State<MessageInputBar> {
}); });
if (_currentText.isNotEmpty) { if (_currentText.isNotEmpty) {
await sendTextMessage( await sendTextMessage(cHolder.apiClient, mClient, _messageTextController.text);
cHolder.apiClient,
mClient,
_messageTextController.text);
} }
_messageTextController.clear(); _messageTextController.clear();
_currentText = ""; _currentText = "";
_loadedFiles.clear(); _loadedFiles.clear();
_attachmentPickerOpen = false; _attachmentPickerOpen = false;
} catch (e, s) { } catch (e, s) {
FlutterError.reportError( FlutterError.reportError(FlutterErrorDetails(exception: e, stack: s));
FlutterErrorDetails( sMsgnr.showSnackBar(SnackBar(content: Text("Failed to send a message: $e")));
exception: e, stack: s));
sMsgnr.showSnackBar(SnackBar(
content: Text(
"Failed to send a message: $e")));
} }
setState(() { setState(() {
_isSending = false; _isSending = false;
@ -642,9 +557,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
? null ? null
: (_) async { : (_) async {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(content: Text("Sorry, this feature is not yet available")));
content: Text(
"Sorry, this feature is not yet available")));
return; return;
// HapticFeedback.vibrate(); // HapticFeedback.vibrate();
// final hadToAsk = // final hadToAsk =

View file

@ -17,8 +17,7 @@ class MessagesList extends StatefulWidget {
State<StatefulWidget> createState() => _MessagesListState(); State<StatefulWidget> createState() => _MessagesListState();
} }
class _MessagesListState extends State<MessagesList> class _MessagesListState extends State<MessagesList> with SingleTickerProviderStateMixin {
with SingleTickerProviderStateMixin {
final ScrollController _sessionListScrollController = ScrollController(); final ScrollController _sessionListScrollController = ScrollController();
bool _showSessionListScrollChevron = false; bool _showSessionListScrollChevron = false;
@ -36,8 +35,7 @@ class _MessagesListState extends State<MessagesList>
void initState() { void initState() {
super.initState(); super.initState();
_sessionListScrollController.addListener(() { _sessionListScrollController.addListener(() {
if (_sessionListScrollController.position.maxScrollExtent > 0 && if (_sessionListScrollController.position.maxScrollExtent > 0 && !_showSessionListScrollChevron) {
!_showSessionListScrollChevron) {
setState(() { setState(() {
_showSessionListScrollChevron = true; _showSessionListScrollChevron = true;
}); });
@ -58,9 +56,7 @@ class _MessagesListState extends State<MessagesList>
return Consumer<MessagingClient>(builder: (context, mClient, _) { return Consumer<MessagingClient>(builder: (context, mClient, _) {
final friend = mClient.selectedFriend ?? Friend.empty(); final friend = mClient.selectedFriend ?? Friend.empty();
final cache = mClient.getUserMessageCache(friend.id); final cache = mClient.getUserMessageCache(friend.id);
final sessions = friend.userStatus.decodedSessions final sessions = friend.userStatus.decodedSessions.where((element) => element.isVisible).toList();
.where((element) => element.isVisible)
.toList();
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Row( title: Row(
@ -77,10 +73,7 @@ class _MessagesListState extends State<MessagesList>
child: Icon( child: Icon(
Icons.dns, Icons.dns,
size: 18, size: 18,
color: Theme.of(context) color: Theme.of(context).colorScheme.onSecondaryContainer.withAlpha(150),
.colorScheme
.onSecondaryContainer
.withAlpha(150),
), ),
), ),
], ],
@ -110,8 +103,8 @@ class _MessagesListState extends State<MessagesList>
if (sessions.isNotEmpty) if (sessions.isNotEmpty)
AnimatedSwitcher( AnimatedSwitcher(
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
transitionBuilder: (child, animation) => SizeTransition( transitionBuilder: (child, animation) =>
sizeFactor: animation, axis: Axis.vertical, child: child), SizeTransition(sizeFactor: animation, axis: Axis.vertical, child: child),
child: sessions.isEmpty || !_sessionListOpen child: sessions.isEmpty || !_sessionListOpen
? null ? null
: Container( : Container(
@ -139,8 +132,7 @@ class _MessagesListState extends State<MessagesList>
child: Align( child: Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: Container( child: Container(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(left: 16, right: 4, top: 1, bottom: 1),
left: 16, right: 4, top: 1, bottom: 1),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.centerLeft, begin: Alignment.centerLeft,
@ -190,13 +182,11 @@ class _MessagesListState extends State<MessagesList>
children: [ children: [
const Icon(Icons.message_outlined), const Icon(Icons.message_outlined),
Padding( Padding(
padding: padding: const EdgeInsets.symmetric(vertical: 24),
const EdgeInsets.symmetric(vertical: 24),
child: Text( child: Text(
"There are no messages here\nWhy not say hello?", "There are no messages here\nWhy not say hello?",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: style: Theme.of(context).textTheme.titleMedium,
Theme.of(context).textTheme.titleMedium,
), ),
) )
], ],
@ -207,8 +197,7 @@ class _MessagesListState extends State<MessagesList>
create: (BuildContext context) => AudioCacheClient(), create: (BuildContext context) => AudioCacheClient(),
child: ListView.builder( child: ListView.builder(
reverse: true, reverse: true,
physics: const BouncingScrollPhysics( physics: const BouncingScrollPhysics(decelerationRate: ScrollDecelerationRate.fast),
decelerationRate: ScrollDecelerationRate.fast),
itemCount: cache.messages.length, itemCount: cache.messages.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final entry = cache.messages[index]; final entry = cache.messages[index];

View file

@ -17,15 +17,12 @@ class SessionTile extends StatelessWidget {
foregroundColor: Theme.of(context).colorScheme.onSurface, foregroundColor: Theme.of(context).colorScheme.onSurface,
), ),
onPressed: () { onPressed: () {
Navigator.of(context).push(MaterialPageRoute( Navigator.of(context).push(MaterialPageRoute(builder: (context) => SessionView(session: session)));
builder: (context) => SessionView(session: session)));
}, },
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
GenericAvatar( GenericAvatar(imageUri: Aux.resdbToHttp(session.thumbnailUrl), placeholderIcon: Icons.no_photography),
imageUri: Aux.resdbToHttp(session.thumbnailUrl),
placeholderIcon: Icons.no_photography),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0), padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: Column( child: Column(
@ -35,11 +32,10 @@ class SessionTile extends StatelessWidget {
FormattedText(session.formattedName), FormattedText(session.formattedName),
Text( Text(
"${session.sessionUsers.length.toString().padLeft(2, "0")}/${session.maxUsers.toString().padLeft(2, "0")} active users", "${session.sessionUsers.length.toString().padLeft(2, "0")}/${session.maxUsers.toString().padLeft(2, "0")} active users",
style: Theme.of(context).textTheme.labelMedium?.copyWith( style: Theme.of(context)
color: Theme.of(context) .textTheme
.colorScheme .labelMedium
.onSurface ?.copyWith(color: Theme.of(context).colorScheme.onSurface.withOpacity(.6)),
.withOpacity(.6)),
) )
], ],
), ),

View file

@ -1,8 +1,7 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:recon/clients/session_client.dart'; import 'package:recon/clients/session_client.dart';
import 'package:recon/widgets/sessions/session_filter_dialog.dart'; import 'package:recon/widgets/sessions/session_filter_dialog.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
class SessionListAppBar extends StatefulWidget { class SessionListAppBar extends StatefulWidget {
const SessionListAppBar({super.key}); const SessionListAppBar({super.key});

View file

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class SettingsAppBar extends StatelessWidget { class SettingsAppBar extends StatelessWidget {
const SettingsAppBar({super.key}); const SettingsAppBar({super.key});

View file

@ -7,7 +7,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:recon/main.dart'; import 'package:recon/main.dart';
void main() { void main() {