chore: update formatting to use 120 char line length
This commit is contained in:
parent
0cf8bde724
commit
96d95f528d
12 changed files with 171 additions and 388 deletions
|
@ -18,8 +18,7 @@ class AudioCacheClient {
|
|||
final file = File("${directory.path}/$fileName.ogg");
|
||||
if (!await file.exists()) {
|
||||
await file.create(recursive: true);
|
||||
final response =
|
||||
await http.get(Uri.parse(Aux.resdbToHttp(clip.assetUri)));
|
||||
final response = await http.get(Uri.parse(Aux.resdbToHttp(clip.assetUri)));
|
||||
ApiClient.checkResponseCode(response);
|
||||
await file.writeAsBytes(response.bodyBytes);
|
||||
}
|
||||
|
@ -31,8 +30,7 @@ class AudioCacheClient {
|
|||
}
|
||||
if (!wavFileExists) {
|
||||
await wavFile.create(recursive: true);
|
||||
await FFmpegKit.executeAsync(
|
||||
"-y -acodec libvorbis -i ${file.path} -acodec pcm_s16le ${wavFile.path}");
|
||||
await FFmpegKit.executeAsync("-y -acodec libvorbis -i ${file.path} -acodec pcm_s16le ${wavFile.path}");
|
||||
}
|
||||
return wavFile;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart'
|
||||
as fln;
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart' as fln;
|
||||
import 'package:recon/auxiliary.dart';
|
||||
import 'package:recon/models/message.dart';
|
||||
import 'package:recon/models/session.dart';
|
||||
|
@ -12,8 +11,7 @@ class NotificationChannel {
|
|||
final String name;
|
||||
final String description;
|
||||
|
||||
const NotificationChannel(
|
||||
{required this.name, required this.id, required this.description});
|
||||
const NotificationChannel({required this.name, required this.id, required this.description});
|
||||
}
|
||||
|
||||
class NotificationClient {
|
||||
|
@ -23,18 +21,15 @@ class NotificationClient {
|
|||
description: "Messages received from your friends",
|
||||
);
|
||||
|
||||
final fln.FlutterLocalNotificationsPlugin _notifier =
|
||||
fln.FlutterLocalNotificationsPlugin()
|
||||
..initialize(const fln.InitializationSettings(
|
||||
android: fln.AndroidInitializationSettings("ic_notification"),
|
||||
iOS: fln.DarwinInitializationSettings(),
|
||||
macOS: fln.DarwinInitializationSettings(),
|
||||
linux:
|
||||
fln.LinuxInitializationSettings(defaultActionName: "Open ReCon"),
|
||||
));
|
||||
final fln.FlutterLocalNotificationsPlugin _notifier = fln.FlutterLocalNotificationsPlugin()
|
||||
..initialize(const fln.InitializationSettings(
|
||||
android: fln.AndroidInitializationSettings("ic_notification"),
|
||||
iOS: fln.DarwinInitializationSettings(),
|
||||
macOS: fln.DarwinInitializationSettings(),
|
||||
linux: fln.LinuxInitializationSettings(defaultActionName: "Open ReCon"),
|
||||
));
|
||||
|
||||
Future<void> showUnreadMessagesNotification(
|
||||
Iterable<Message> messages) async {
|
||||
Future<void> showUnreadMessagesNotification(Iterable<Message> messages) async {
|
||||
if (messages.isEmpty) return;
|
||||
|
||||
final bySender = groupBy(messages, (p0) => p0.senderId);
|
||||
|
@ -73,8 +68,7 @@ class NotificationClient {
|
|||
break;
|
||||
case MessageType.sessionInvite:
|
||||
try {
|
||||
final session =
|
||||
Session.fromMap(jsonDecode(message.content));
|
||||
final session = Session.fromMap(jsonDecode(message.content));
|
||||
content = "Session Invite to ${session.name}";
|
||||
} catch (e) {
|
||||
content = "Session Invite";
|
||||
|
|
|
@ -38,23 +38,19 @@ void main() async {
|
|||
),
|
||||
);
|
||||
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge,
|
||||
overlays: [SystemUiOverlay.top]);
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge, overlays: [SystemUiOverlay.top]);
|
||||
|
||||
await Hive.initFlutter();
|
||||
|
||||
final dateFormat = DateFormat.Hms();
|
||||
Logger.root.onRecord.listen((event) => log(
|
||||
"${dateFormat.format(event.time)}: ${event.message}",
|
||||
name: event.loggerName,
|
||||
time: event.time));
|
||||
Logger.root.onRecord.listen(
|
||||
(event) => log("${dateFormat.format(event.time)}: ${event.message}", name: event.loggerName, time: event.time));
|
||||
|
||||
final settingsClient = SettingsClient();
|
||||
await settingsClient.loadSettings();
|
||||
final newSettings = settingsClient.currentSettings.copyWith(
|
||||
machineId: settingsClient.currentSettings.machineId.valueOrDefault);
|
||||
await settingsClient
|
||||
.changeSettings(newSettings); // Save generated machineId to disk
|
||||
final newSettings =
|
||||
settingsClient.currentSettings.copyWith(machineId: settingsClient.currentSettings.machineId.valueOrDefault);
|
||||
await settingsClient.changeSettings(newSettings); // Save generated machineId to disk
|
||||
|
||||
AuthenticationData cachedAuth = AuthenticationData.unauthenticated();
|
||||
try {
|
||||
|
@ -63,15 +59,11 @@ void main() async {
|
|||
// Ignore
|
||||
}
|
||||
|
||||
runApp(
|
||||
ReCon(settingsClient: settingsClient, cachedAuthentication: cachedAuth));
|
||||
runApp(ReCon(settingsClient: settingsClient, cachedAuthentication: cachedAuth));
|
||||
}
|
||||
|
||||
class ReCon extends StatefulWidget {
|
||||
const ReCon(
|
||||
{required this.settingsClient,
|
||||
required this.cachedAuthentication,
|
||||
super.key});
|
||||
const ReCon({required this.settingsClient, required this.cachedAuthentication, super.key});
|
||||
|
||||
final SettingsClient settingsClient;
|
||||
final AuthenticationData cachedAuthentication;
|
||||
|
@ -81,8 +73,7 @@ class ReCon extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _ReConState extends State<ReCon> {
|
||||
final Typography _typography =
|
||||
Typography.material2021(platform: defaultTargetPlatform);
|
||||
final Typography _typography = Typography.material2021(platform: defaultTargetPlatform);
|
||||
final ReceivePort _port = ReceivePort();
|
||||
late AuthenticationData _authData = widget.cachedAuthentication;
|
||||
bool _checkedForUpdate = false;
|
||||
|
@ -105,8 +96,7 @@ class _ReConState extends State<ReCon> {
|
|||
}
|
||||
|
||||
try {
|
||||
lastDismissedSem = SemVer.fromString(
|
||||
settings.currentSettings.lastDismissedVersion.valueOrDefault);
|
||||
lastDismissedSem = SemVer.fromString(settings.currentSettings.lastDismissedVersion.valueOrDefault);
|
||||
} catch (_) {
|
||||
lastDismissedSem = SemVer.zero();
|
||||
}
|
||||
|
@ -121,9 +111,7 @@ class _ReConState extends State<ReCon> {
|
|||
return;
|
||||
}
|
||||
|
||||
if (remoteSem > currentSem &&
|
||||
navigator.overlay?.context != null &&
|
||||
context.mounted) {
|
||||
if (remoteSem > currentSem && navigator.overlay?.context != null && context.mounted) {
|
||||
showDialog(
|
||||
context: navigator.overlay!.context,
|
||||
builder: (context) {
|
||||
|
@ -141,8 +129,7 @@ class _ReConState extends State<ReCon> {
|
|||
void initState() {
|
||||
super.initState();
|
||||
|
||||
IsolateNameServer.registerPortWithName(
|
||||
_port.sendPort, 'downloader_send_port');
|
||||
IsolateNameServer.registerPortWithName(_port.sendPort, 'downloader_send_port');
|
||||
_port.listen((dynamic data) {
|
||||
// Not useful yet? idk...
|
||||
// String id = data[0];
|
||||
|
@ -176,26 +163,21 @@ class _ReConState extends State<ReCon> {
|
|||
Phoenix.rebirth(context);
|
||||
},
|
||||
child: DynamicColorBuilder(
|
||||
builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) =>
|
||||
MaterialApp(
|
||||
builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) => MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'ReCon',
|
||||
theme: ThemeData(
|
||||
useMaterial3: true,
|
||||
textTheme: _typography.black,
|
||||
colorScheme: lightDynamic ??
|
||||
ColorScheme.fromSeed(
|
||||
seedColor: Colors.purple, brightness: Brightness.light),
|
||||
colorScheme:
|
||||
lightDynamic ?? ColorScheme.fromSeed(seedColor: Colors.purple, brightness: Brightness.light),
|
||||
),
|
||||
darkTheme: ThemeData(
|
||||
useMaterial3: true,
|
||||
textTheme: _typography.white,
|
||||
colorScheme: darkDynamic ??
|
||||
ColorScheme.fromSeed(
|
||||
seedColor: Colors.purple, brightness: Brightness.dark),
|
||||
colorScheme: darkDynamic ?? ColorScheme.fromSeed(seedColor: Colors.purple, brightness: Brightness.dark),
|
||||
),
|
||||
themeMode: ThemeMode.values[widget
|
||||
.settingsClient.currentSettings.themeMode.valueOrDefault],
|
||||
themeMode: ThemeMode.values[widget.settingsClient.currentSettings.themeMode.valueOrDefault],
|
||||
home: Builder(
|
||||
// Builder is necessary here since we need a context which has access to the ClientHolder
|
||||
builder: (context) {
|
||||
|
@ -208,8 +190,7 @@ class _ReConState extends State<ReCon> {
|
|||
create: (context) => MessagingClient(
|
||||
apiClient: clientHolder.apiClient,
|
||||
settingsClient: clientHolder.settingsClient,
|
||||
notificationClient:
|
||||
clientHolder.notificationClient,
|
||||
notificationClient: clientHolder.notificationClient,
|
||||
),
|
||||
),
|
||||
ChangeNotifierProvider(
|
||||
|
@ -226,15 +207,13 @@ class _ReConState extends State<ReCon> {
|
|||
],
|
||||
child: AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarColor:
|
||||
Theme.of(context).colorScheme.surfaceVariant,
|
||||
statusBarColor: Theme.of(context).colorScheme.surfaceVariant,
|
||||
),
|
||||
child: const Home(),
|
||||
),
|
||||
)
|
||||
: LoginScreen(
|
||||
onLoginSuccessful:
|
||||
(AuthenticationData authData) async {
|
||||
onLoginSuccessful: (AuthenticationData authData) async {
|
||||
if (authData.isAuthenticated) {
|
||||
setState(() {
|
||||
_authData = authData;
|
||||
|
|
|
@ -28,8 +28,7 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
|
|||
final id = event.task.taskId;
|
||||
final status = event is TaskStatusUpdate ? event.status : null;
|
||||
final progress = event is TaskProgressUpdate ? event.progress : null;
|
||||
final SendPort? send =
|
||||
IsolateNameServer.lookupPortByName('downloader_send_port');
|
||||
final SendPort? send = IsolateNameServer.lookupPortByName('downloader_send_port');
|
||||
|
||||
send?.send([id, status, progress]);
|
||||
}
|
||||
|
@ -74,9 +73,7 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
|
|||
style: TextStyle(
|
||||
color: iClient.sortReverse == false
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface,
|
||||
: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -89,9 +86,7 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
|
|||
Icon(Icons.arrow_downward,
|
||||
color: iClient.sortReverse == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface),
|
||||
: Theme.of(context).colorScheme.onSurface),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
|
@ -100,9 +95,7 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
|
|||
style: TextStyle(
|
||||
color: iClient.sortReverse == true
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface,
|
||||
: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -128,27 +121,18 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
|
|||
Icon(
|
||||
e.icon,
|
||||
color: iClient.sortMode == e
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface,
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Text(
|
||||
toBeginningOfSentenceCase(e.name) ??
|
||||
e.name,
|
||||
toBeginningOfSentenceCase(e.name) ?? e.name,
|
||||
style: TextStyle(
|
||||
color: iClient.sortMode == e
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface,
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
)
|
||||
],
|
||||
|
@ -173,8 +157,7 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
|
|||
actions: [
|
||||
if (iClient.selectedRecordCount == 1 &&
|
||||
(iClient.selectedRecords.firstOrNull?.isLink == true ||
|
||||
iClient.selectedRecords.firstOrNull?.isItem ==
|
||||
true))
|
||||
iClient.selectedRecords.firstOrNull?.isItem == true))
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Share.share(iClient.selectedRecords.first.assetUri);
|
||||
|
@ -186,12 +169,8 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
|
|||
onPressed: () async {
|
||||
final selectedRecords = iClient.selectedRecords;
|
||||
|
||||
final assetUris = selectedRecords
|
||||
.map((record) => record.assetUri)
|
||||
.toList();
|
||||
final thumbUris = selectedRecords
|
||||
.map((record) => record.thumbnailUri)
|
||||
.toList();
|
||||
final assetUris = selectedRecords.map((record) => record.assetUri).toList();
|
||||
final thumbUris = selectedRecords.map((record) => record.thumbnailUri).toList();
|
||||
|
||||
final selectedUris = await showDialog<List<String>>(
|
||||
context: context,
|
||||
|
@ -232,8 +211,7 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
|
|||
);
|
||||
if (selectedUris == null) return;
|
||||
|
||||
final directory = await FilePicker.platform
|
||||
.getDirectoryPath(dialogTitle: "Download to...");
|
||||
final directory = await FilePicker.platform.getDirectoryPath(dialogTitle: "Download to...");
|
||||
if (directory == null) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
|
@ -248,17 +226,14 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
|
|||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content:
|
||||
Text("Selected directory is invalid"),
|
||||
content: Text("Selected directory is invalid"),
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (var record in selectedRecords) {
|
||||
final uri = selectedUris == thumbUris
|
||||
? record.thumbnailUri
|
||||
: record.assetUri;
|
||||
final uri = selectedUris == thumbUris ? record.thumbnailUri : record.assetUri;
|
||||
final filename =
|
||||
"${record.id.split("-")[1]}-${record.formattedName.toString()}${extension(uri)}";
|
||||
final downloadTask = DownloadTask(
|
||||
|
@ -268,22 +243,18 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
|
|||
filename: filename,
|
||||
updates: Updates.statusAndProgress,
|
||||
);
|
||||
await FileDownloader()
|
||||
.enqueue(downloadTask)
|
||||
.then((b) {
|
||||
await FileDownloader().enqueue(downloadTask).then((b) {
|
||||
if (context.mounted) {
|
||||
if (b) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
"Downloaded ${record.formattedName.toString()}"),
|
||||
content: Text("Downloaded ${record.formattedName.toString()}"),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
"Failed to download ${record.formattedName.toString()}"),
|
||||
content: Text("Failed to download ${record.formattedName.toString()}"),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -317,10 +288,8 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
|
|||
title: Text(iClient.selectedRecordCount == 1
|
||||
? "Really delete this Record?"
|
||||
: "Really delete ${iClient.selectedRecordCount} Records?"),
|
||||
content: const Text(
|
||||
"This action cannot be undone!"),
|
||||
actionsAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
content: const Text("This action cannot be undone!"),
|
||||
actionsAlignment: MainAxisAlignment.spaceBetween,
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: loading
|
||||
|
@ -336,8 +305,7 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
|
|||
if (loading)
|
||||
const SizedBox.square(
|
||||
dimension: 16,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2),
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
|
@ -350,16 +318,12 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
|
|||
loading = true;
|
||||
});
|
||||
try {
|
||||
await iClient
|
||||
.deleteSelectedRecords();
|
||||
await iClient.deleteSelectedRecords();
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context)
|
||||
.showSnackBar(
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
"Failed to delete one or more records: $e"),
|
||||
content: Text("Failed to delete one or more records: $e"),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -368,16 +332,12 @@ class _InventoryBrowserAppBarState extends State<InventoryBrowserAppBar> {
|
|||
});
|
||||
}
|
||||
if (context.mounted) {
|
||||
Navigator.of(context)
|
||||
.pop(true);
|
||||
Navigator.of(context).pop(true);
|
||||
}
|
||||
iClient
|
||||
.reloadCurrentDirectory();
|
||||
iClient.reloadCurrentDirectory();
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.error,
|
||||
foregroundColor: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
child: const Text("Delete"),
|
||||
),
|
||||
|
|
|
@ -83,10 +83,8 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||
_error = "Please enter your 2FA-Code";
|
||||
_totpFocusNode.requestFocus();
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
_scrollController.animateTo(
|
||||
_scrollController.position.maxScrollExtent,
|
||||
duration: const Duration(milliseconds: 400),
|
||||
curve: Curves.easeOutCirc);
|
||||
_scrollController.animateTo(_scrollController.position.maxScrollExtent,
|
||||
duration: const Duration(milliseconds: 400), curve: Curves.easeOutCirc);
|
||||
});
|
||||
} else {
|
||||
_error = "The given 2FA code is not valid.";
|
||||
|
@ -115,16 +113,14 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text(
|
||||
"This app needs to ask your permission to send background notifications."),
|
||||
title: const Text("This app needs to ask your permission to send background notifications."),
|
||||
content: const Text("Are you okay with that?"),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
await settingsClient.changeSettings(settingsClient
|
||||
.currentSettings
|
||||
.copyWith(notificationsDenied: true));
|
||||
await settingsClient
|
||||
.changeSettings(settingsClient.currentSettings.copyWith(notificationsDenied: true));
|
||||
},
|
||||
child: const Text("No"),
|
||||
),
|
||||
|
@ -133,31 +129,21 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||
Navigator.of(context).pop();
|
||||
final requestResult = switch (Platform.operatingSystem) {
|
||||
"android" => await notificationManager
|
||||
.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>()
|
||||
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.requestNotificationsPermission(),
|
||||
"fuschia" =>
|
||||
null, // "fuschia" is not supported by flutter_local_notifications
|
||||
"fuschia" => null, // "fuschia" is not supported by flutter_local_notifications
|
||||
"ios" => await notificationManager
|
||||
.resolvePlatformSpecificImplementation<
|
||||
IOSFlutterLocalNotificationsPlugin>()
|
||||
?.requestPermissions(
|
||||
alert: true, badge: true, sound: true),
|
||||
.resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()
|
||||
?.requestPermissions(alert: true, badge: true, sound: true),
|
||||
"linux" => null, // don't want to deal with this right now
|
||||
"macos" => await notificationManager
|
||||
.resolvePlatformSpecificImplementation<
|
||||
MacOSFlutterLocalNotificationsPlugin>()
|
||||
?.requestPermissions(
|
||||
alert: true, badge: true, sound: true),
|
||||
"windows" =>
|
||||
null, // also don't want to deal with this right now
|
||||
.resolvePlatformSpecificImplementation<MacOSFlutterLocalNotificationsPlugin>()
|
||||
?.requestPermissions(alert: true, badge: true, sound: true),
|
||||
"windows" => null, // also don't want to deal with this right now
|
||||
_ => null,
|
||||
};
|
||||
await settingsClient.changeSettings(
|
||||
settingsClient.currentSettings.copyWith(
|
||||
notificationsDenied: requestResult == null
|
||||
? false
|
||||
: !requestResult));
|
||||
await settingsClient.changeSettings(settingsClient.currentSettings
|
||||
.copyWith(notificationsDenied: requestResult == null ? false : !requestResult));
|
||||
},
|
||||
child: const Text("Yes"),
|
||||
)
|
||||
|
@ -183,8 +169,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 64),
|
||||
child: Center(
|
||||
child: Text("Sign In",
|
||||
style: Theme.of(context).textTheme.headlineMedium),
|
||||
child: Text("Sign In", style: Theme.of(context).textTheme.headlineMedium),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
|
@ -193,8 +178,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||
controller: _usernameController,
|
||||
onEditingComplete: () => _passwordFocusNode.requestFocus(),
|
||||
decoration: InputDecoration(
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(vertical: 20, horizontal: 24),
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 20, horizontal: 24),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
),
|
||||
|
@ -210,26 +194,22 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||
onEditingComplete: submit,
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(vertical: 20, horizontal: 24),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(32)),
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 20, horizontal: 24),
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(32)),
|
||||
labelText: 'Password',
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_needsTotp)
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 16, horizontal: 64),
|
||||
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 64),
|
||||
child: TextField(
|
||||
controller: _totpController,
|
||||
focusNode: _totpFocusNode,
|
||||
onEditingComplete: submit,
|
||||
obscureText: false,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 20, horizontal: 24),
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 20, horizontal: 24),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
),
|
||||
|
@ -252,13 +232,8 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||
opacity: _errorOpacity,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 16, horizontal: 64),
|
||||
child: Text(_error,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium
|
||||
?.copyWith(color: Colors.red)),
|
||||
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 64),
|
||||
child: Text(_error, style: Theme.of(context).textTheme.labelMedium?.copyWith(color: Colors.red)),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -9,8 +9,7 @@ import 'package:recon/models/message.dart';
|
|||
import 'package:recon/widgets/messages/message_state_indicator.dart';
|
||||
|
||||
class MessageAudioPlayer extends StatefulWidget {
|
||||
const MessageAudioPlayer(
|
||||
{required this.message, this.foregroundColor, super.key});
|
||||
const MessageAudioPlayer({required this.message, this.foregroundColor, super.key});
|
||||
|
||||
final Message message;
|
||||
final Color? foregroundColor;
|
||||
|
@ -19,8 +18,7 @@ class MessageAudioPlayer extends StatefulWidget {
|
|||
State<MessageAudioPlayer> createState() => _MessageAudioPlayerState();
|
||||
}
|
||||
|
||||
class _MessageAudioPlayerState extends State<MessageAudioPlayer>
|
||||
with WidgetsBindingObserver {
|
||||
class _MessageAudioPlayerState extends State<MessageAudioPlayer> with WidgetsBindingObserver {
|
||||
final AudioPlayer _audioPlayer = AudioPlayer();
|
||||
Future? _audioFileFuture;
|
||||
double _sliderValue = 0;
|
||||
|
@ -43,8 +41,7 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer>
|
|||
super.didChangeDependencies();
|
||||
final audioCache = Provider.of<AudioCacheClient>(context);
|
||||
_audioFileFuture = audioCache
|
||||
.cachedNetworkAudioFile(
|
||||
AudioClipContent.fromMap(jsonDecode(widget.message.content)))
|
||||
.cachedNetworkAudioFile(AudioClipContent.fromMap(jsonDecode(widget.message.content)))
|
||||
.then((value) => _audioPlayer.setFilePath(value.path))
|
||||
.whenComplete(() => _audioPlayer.setLoopMode(LoopMode.off));
|
||||
}
|
||||
|
@ -55,8 +52,7 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer>
|
|||
if (oldWidget.message.id == widget.message.id) return;
|
||||
final audioCache = Provider.of<AudioCacheClient>(context);
|
||||
_audioFileFuture = audioCache
|
||||
.cachedNetworkAudioFile(
|
||||
AudioClipContent.fromMap(jsonDecode(widget.message.content)))
|
||||
.cachedNetworkAudioFile(AudioClipContent.fromMap(jsonDecode(widget.message.content)))
|
||||
.then((value) async {
|
||||
final path = _audioPlayer.setFilePath(value.path);
|
||||
await _audioPlayer.setLoopMode(LoopMode.off);
|
||||
|
@ -91,10 +87,7 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer>
|
|||
textAlign: TextAlign.center,
|
||||
softWrap: true,
|
||||
maxLines: 3,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(color: Theme.of(context).colorScheme.error),
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Theme.of(context).colorScheme.error),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -108,8 +101,7 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer>
|
|||
stream: _audioPlayer.playerStateStream,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) {
|
||||
FlutterError.reportError(FlutterErrorDetails(
|
||||
exception: snapshot.error!, stack: snapshot.stackTrace));
|
||||
FlutterError.reportError(FlutterErrorDetails(exception: snapshot.error!, stack: snapshot.stackTrace));
|
||||
return _createErrorWidget("Failed to load audio-message.");
|
||||
}
|
||||
final playerState = snapshot.data;
|
||||
|
@ -125,9 +117,8 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer>
|
|||
future: _audioFileFuture,
|
||||
builder: (context, fileSnapshot) {
|
||||
if (fileSnapshot.hasError) {
|
||||
FlutterError.reportError(FlutterErrorDetails(
|
||||
exception: fileSnapshot.error!,
|
||||
stack: fileSnapshot.stackTrace));
|
||||
FlutterError.reportError(
|
||||
FlutterErrorDetails(exception: fileSnapshot.error!, stack: fileSnapshot.stackTrace));
|
||||
return const IconButton(
|
||||
icon: Icon(Icons.warning),
|
||||
tooltip: "Failed to load audio-message.",
|
||||
|
@ -138,8 +129,7 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer>
|
|||
onPressed: fileSnapshot.hasData &&
|
||||
snapshot.hasData &&
|
||||
playerState != null &&
|
||||
playerState.processingState !=
|
||||
ProcessingState.loading
|
||||
playerState.processingState != ProcessingState.loading
|
||||
? () {
|
||||
switch (playerState.processingState) {
|
||||
case ProcessingState.idle:
|
||||
|
@ -162,15 +152,11 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer>
|
|||
: null,
|
||||
color: widget.foregroundColor,
|
||||
icon: Icon(
|
||||
((_audioPlayer.duration ??
|
||||
const Duration(days: 9999)) -
|
||||
_audioPlayer.position)
|
||||
((_audioPlayer.duration ?? const Duration(days: 9999)) - _audioPlayer.position)
|
||||
.inMilliseconds <
|
||||
10
|
||||
? Icons.replay
|
||||
: ((playerState?.playing ?? false)
|
||||
? Icons.pause
|
||||
: Icons.play_arrow),
|
||||
: ((playerState?.playing ?? false) ? Icons.pause : Icons.play_arrow),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -180,16 +166,14 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer>
|
|||
builder: (context, snapshot) {
|
||||
_sliderValue = _audioPlayer.duration == null
|
||||
? 0
|
||||
: (_audioPlayer.position.inMilliseconds /
|
||||
(_audioPlayer.duration!.inMilliseconds))
|
||||
: (_audioPlayer.position.inMilliseconds / (_audioPlayer.duration!.inMilliseconds))
|
||||
.clamp(0, 1);
|
||||
return StatefulBuilder(
|
||||
// Not sure if this makes sense here...
|
||||
builder: (context, setState) {
|
||||
return SliderTheme(
|
||||
data: SliderThemeData(
|
||||
inactiveTrackColor:
|
||||
widget.foregroundColor?.withAlpha(100),
|
||||
inactiveTrackColor: widget.foregroundColor?.withAlpha(100),
|
||||
),
|
||||
child: Slider(
|
||||
thumbColor: widget.foregroundColor,
|
||||
|
@ -203,11 +187,7 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer>
|
|||
});
|
||||
_audioPlayer.seek(
|
||||
Duration(
|
||||
milliseconds: (value *
|
||||
(_audioPlayer
|
||||
.duration?.inMilliseconds ??
|
||||
0))
|
||||
.round(),
|
||||
milliseconds: (value * (_audioPlayer.duration?.inMilliseconds ?? 0)).round(),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -231,8 +211,10 @@ class _MessageAudioPlayerState extends State<MessageAudioPlayer>
|
|||
builder: (context, snapshot) {
|
||||
return Text(
|
||||
"${snapshot.data?.format() ?? "??"}/${_audioPlayer.duration?.format() ?? "??"}",
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: widget.foregroundColor?.withAlpha(150)),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(color: widget.foregroundColor?.withAlpha(150)),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
|
@ -18,11 +18,7 @@ import 'package:recon/widgets/messages/message_attachment_list.dart';
|
|||
import 'package:record/record.dart';
|
||||
|
||||
class MessageInputBar extends StatefulWidget {
|
||||
const MessageInputBar(
|
||||
{this.disabled = false,
|
||||
required this.recipient,
|
||||
this.onMessageSent,
|
||||
super.key});
|
||||
const MessageInputBar({this.disabled = false, required this.recipient, this.onMessageSent, super.key});
|
||||
|
||||
final bool disabled;
|
||||
final Friend recipient;
|
||||
|
@ -45,8 +41,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
String _currentText = "";
|
||||
double? _sendProgress;
|
||||
bool get _isRecording => _recordingStartTime != null;
|
||||
set _isRecording(value) =>
|
||||
_recordingStartTime = value ? DateTime.now() : null;
|
||||
set _isRecording(value) => _recordingStartTime = value ? DateTime.now() : null;
|
||||
bool _recordingCancelled = false;
|
||||
|
||||
@override
|
||||
|
@ -56,8 +51,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> sendTextMessage(
|
||||
ApiClient client, MessagingClient mClient, String content) async {
|
||||
Future<void> sendTextMessage(ApiClient client, MessagingClient mClient, String content) async {
|
||||
if (content.isEmpty) return;
|
||||
final message = Message(
|
||||
id: Message.generateId(),
|
||||
|
@ -71,11 +65,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
mClient.sendMessage(message);
|
||||
}
|
||||
|
||||
Future<void> sendImageMessage(
|
||||
ApiClient client,
|
||||
MessagingClient mClient,
|
||||
File file,
|
||||
String machineId,
|
||||
Future<void> sendImageMessage(ApiClient client, MessagingClient mClient, File file, String machineId,
|
||||
void Function(double progress) progressCallback) async {
|
||||
final record = await RecordApi.uploadImage(
|
||||
client,
|
||||
|
@ -94,11 +84,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
mClient.sendMessage(message);
|
||||
}
|
||||
|
||||
Future<void> sendVoiceMessage(
|
||||
ApiClient client,
|
||||
MessagingClient mClient,
|
||||
File file,
|
||||
String machineId,
|
||||
Future<void> sendVoiceMessage(ApiClient client, MessagingClient mClient, File file, String machineId,
|
||||
void Function(double progress) progressCallback) async {
|
||||
final record = await RecordApi.uploadVoiceClip(
|
||||
client,
|
||||
|
@ -118,11 +104,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
mClient.sendMessage(message);
|
||||
}
|
||||
|
||||
Future<void> sendRawFileMessage(
|
||||
ApiClient client,
|
||||
MessagingClient mClient,
|
||||
File file,
|
||||
String machineId,
|
||||
Future<void> sendRawFileMessage(ApiClient client, MessagingClient mClient, File file, String machineId,
|
||||
void Function(double progress) progressCallback) async {
|
||||
final record = await RecordApi.uploadRawFile(
|
||||
client,
|
||||
|
@ -205,9 +187,8 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
_sendProgress = 0;
|
||||
});
|
||||
final apiClient = cHolder.apiClient;
|
||||
await sendVoiceMessage(apiClient, mClient, file,
|
||||
cHolder.settingsClient.currentSettings.machineId.valueOrDefault,
|
||||
(progress) {
|
||||
await sendVoiceMessage(
|
||||
apiClient, mClient, file, cHolder.settingsClient.currentSettings.machineId.valueOrDefault, (progress) {
|
||||
setState(() {
|
||||
_sendProgress = progress;
|
||||
});
|
||||
|
@ -229,8 +210,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
top: false,
|
||||
child: Column(
|
||||
children: [
|
||||
if (_isSending && _sendProgress != null)
|
||||
LinearProgressIndicator(value: _sendProgress),
|
||||
if (_isSending && _sendProgress != null) LinearProgressIndicator(value: _sendProgress),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
|
@ -239,8 +219,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
duration: const Duration(milliseconds: 200),
|
||||
switchInCurve: Curves.easeOut,
|
||||
switchOutCurve: Curves.easeOut,
|
||||
transitionBuilder: (Widget child, animation) =>
|
||||
SizeTransition(
|
||||
transitionBuilder: (Widget child, animation) => SizeTransition(
|
||||
sizeFactor: animation,
|
||||
child: child,
|
||||
),
|
||||
|
@ -252,19 +231,12 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
onPressed: _isSending
|
||||
? null
|
||||
: () async {
|
||||
final result = await FilePicker.platform
|
||||
.pickFiles(
|
||||
type: FileType.image,
|
||||
allowMultiple: true);
|
||||
final result =
|
||||
await FilePicker.platform.pickFiles(type: FileType.image, allowMultiple: true);
|
||||
if (result != null) {
|
||||
setState(() {
|
||||
_loadedFiles.addAll(result.files
|
||||
.map((e) => e.path != null
|
||||
? (
|
||||
FileType.image,
|
||||
File(e.path!)
|
||||
)
|
||||
: null)
|
||||
.map((e) => e.path != null ? (FileType.image, File(e.path!)) : null)
|
||||
.whereNotNull());
|
||||
});
|
||||
}
|
||||
|
@ -276,29 +248,23 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
onPressed: _isSending
|
||||
? null
|
||||
: () async {
|
||||
final picture = await _imagePicker
|
||||
.pickImage(source: ImageSource.camera);
|
||||
final picture = await _imagePicker.pickImage(source: ImageSource.camera);
|
||||
if (picture == null) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(const SnackBar(
|
||||
content: Text(
|
||||
"Failed to get image path")));
|
||||
.showSnackBar(const SnackBar(content: Text("Failed to get image path")));
|
||||
}
|
||||
return;
|
||||
}
|
||||
final file = File(picture.path);
|
||||
if (await file.exists()) {
|
||||
setState(() {
|
||||
_loadedFiles
|
||||
.add((FileType.image, file));
|
||||
_loadedFiles.add((FileType.image, file));
|
||||
});
|
||||
} else {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(const SnackBar(
|
||||
content: Text(
|
||||
"Failed to load image file")));
|
||||
.showSnackBar(const SnackBar(content: Text("Failed to load image file")));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -309,16 +275,12 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
onPressed: _isSending
|
||||
? null
|
||||
: () async {
|
||||
final result = await FilePicker.platform
|
||||
.pickFiles(
|
||||
type: FileType.any,
|
||||
allowMultiple: true);
|
||||
final result =
|
||||
await FilePicker.platform.pickFiles(type: FileType.any, allowMultiple: true);
|
||||
if (result != null) {
|
||||
setState(() {
|
||||
_loadedFiles.addAll(result.files
|
||||
.map((e) => e.path != null
|
||||
? (FileType.any, File(e.path!))
|
||||
: null)
|
||||
.map((e) => e.path != null ? (FileType.any, File(e.path!)) : null)
|
||||
.whereNotNull());
|
||||
});
|
||||
}
|
||||
|
@ -332,8 +294,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
(_, _) => MessageAttachmentList(
|
||||
disabled: _isSending,
|
||||
initialFiles: _loadedFiles,
|
||||
onChange: (List<(FileType, File)> loadedFiles) =>
|
||||
setState(() {
|
||||
onChange: (List<(FileType, File)> loadedFiles) => setState(() {
|
||||
_loadedFiles.clear();
|
||||
_loadedFiles.addAll(loadedFiles);
|
||||
}),
|
||||
|
@ -345,13 +306,10 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
children: [
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
transitionBuilder:
|
||||
(Widget child, Animation<double> animation) =>
|
||||
FadeTransition(
|
||||
transitionBuilder: (Widget child, Animation<double> animation) => FadeTransition(
|
||||
opacity: animation,
|
||||
child: RotationTransition(
|
||||
turns: Tween<double>(begin: 0.6, end: 1)
|
||||
.animate(animation),
|
||||
turns: Tween<double>(begin: 0.6, end: 1).animate(animation),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
|
@ -360,9 +318,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
onPressed: () {},
|
||||
icon: Icon(
|
||||
Icons.delete,
|
||||
color: _recordingCancelled
|
||||
? Theme.of(context).colorScheme.error
|
||||
: null,
|
||||
color: _recordingCancelled ? Theme.of(context).colorScheme.error : null,
|
||||
),
|
||||
),
|
||||
(false, _) => IconButton(
|
||||
|
@ -371,9 +327,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
? null
|
||||
: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text(
|
||||
"Sorry, this feature is not yet available")));
|
||||
const SnackBar(content: Text("Sorry, this feature is not yet available")));
|
||||
return;
|
||||
// setState(() {
|
||||
// _attachmentPickerOpen = true;
|
||||
|
@ -392,10 +346,8 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text(
|
||||
"Remove all attachments"),
|
||||
content: const Text(
|
||||
"This will remove all attachments, are you sure?"),
|
||||
title: const Text("Remove all attachments"),
|
||||
content: const Text("This will remove all attachments, are you sure?"),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
|
@ -407,8 +359,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
onPressed: () {
|
||||
setState(() {
|
||||
_loadedFiles.clear();
|
||||
_attachmentPickerOpen =
|
||||
false;
|
||||
_attachmentPickerOpen = false;
|
||||
});
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
|
@ -430,8 +381,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8, horizontal: 4),
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4),
|
||||
child: Stack(
|
||||
children: [
|
||||
TextField(
|
||||
|
@ -453,12 +403,9 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
hintText: _isRecording
|
||||
? ""
|
||||
: "Message ${widget.recipient.username}...",
|
||||
hintText: _isRecording ? "" : "Message ${widget.recipient.username}...",
|
||||
hintMaxLines: 1,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16, vertical: 12),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
fillColor: Colors.black26,
|
||||
filled: true,
|
||||
border: OutlineInputBorder(
|
||||
|
@ -468,9 +415,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
),
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
transitionBuilder:
|
||||
(Widget child, Animation<double> animation) =>
|
||||
FadeTransition(
|
||||
transitionBuilder: (Widget child, Animation<double> animation) => FadeTransition(
|
||||
opacity: animation,
|
||||
child: SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
|
@ -482,41 +427,33 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
),
|
||||
child: _isRecording
|
||||
? Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12.0),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0),
|
||||
child: _recordingCancelled
|
||||
? Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 8.0),
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Icon(
|
||||
Icons.cancel,
|
||||
color: Colors.red,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
Text("Cancel Recording",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium),
|
||||
Text("Cancel Recording", style: Theme.of(context).textTheme.titleMedium),
|
||||
],
|
||||
)
|
||||
: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 8.0),
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Icon(
|
||||
Icons.circle,
|
||||
color: Colors.red,
|
||||
|
@ -524,14 +461,10 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
),
|
||||
),
|
||||
StreamBuilder<Duration>(
|
||||
stream:
|
||||
_recordingDurationStream(),
|
||||
stream: _recordingDurationStream(),
|
||||
builder: (context, snapshot) {
|
||||
return Text(
|
||||
"Recording: ${snapshot.data?.format()}",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium);
|
||||
return Text("Recording: ${snapshot.data?.format()}",
|
||||
style: Theme.of(context).textTheme.titleMedium);
|
||||
}),
|
||||
],
|
||||
),
|
||||
|
@ -544,13 +477,10 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
),
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
transitionBuilder:
|
||||
(Widget child, Animation<double> animation) =>
|
||||
FadeTransition(
|
||||
transitionBuilder: (Widget child, Animation<double> animation) => FadeTransition(
|
||||
opacity: animation,
|
||||
child: RotationTransition(
|
||||
turns: Tween<double>(begin: 0.5, end: 1)
|
||||
.animate(animation),
|
||||
turns: Tween<double>(begin: 0.5, end: 1).animate(animation),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
|
@ -563,12 +493,9 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
? null
|
||||
: () async {
|
||||
final cHolder = ClientHolder.of(context);
|
||||
final sMsgnr =
|
||||
ScaffoldMessenger.of(context);
|
||||
final settings =
|
||||
cHolder.settingsClient.currentSettings;
|
||||
final toSend = List<(FileType, File)>.from(
|
||||
_loadedFiles);
|
||||
final sMsgnr = ScaffoldMessenger.of(context);
|
||||
final settings = cHolder.settingsClient.currentSettings;
|
||||
final toSend = List<(FileType, File)>.from(_loadedFiles);
|
||||
setState(() {
|
||||
_isSending = true;
|
||||
_sendProgress = 0;
|
||||
|
@ -586,8 +513,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
file.$2,
|
||||
settings.machineId.valueOrDefault,
|
||||
(progress) => setState(() {
|
||||
_sendProgress = totalProgress +
|
||||
progress * 1 / toSend.length;
|
||||
_sendProgress = totalProgress + progress * 1 / toSend.length;
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
|
@ -596,12 +522,8 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
mClient,
|
||||
file.$2,
|
||||
settings.machineId.valueOrDefault,
|
||||
(progress) => setState(() =>
|
||||
_sendProgress =
|
||||
totalProgress +
|
||||
progress *
|
||||
1 /
|
||||
toSend.length));
|
||||
(progress) => setState(
|
||||
() => _sendProgress = totalProgress + progress * 1 / toSend.length));
|
||||
}
|
||||
}
|
||||
setState(() {
|
||||
|
@ -609,22 +531,15 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
});
|
||||
|
||||
if (_currentText.isNotEmpty) {
|
||||
await sendTextMessage(
|
||||
cHolder.apiClient,
|
||||
mClient,
|
||||
_messageTextController.text);
|
||||
await sendTextMessage(cHolder.apiClient, mClient, _messageTextController.text);
|
||||
}
|
||||
_messageTextController.clear();
|
||||
_currentText = "";
|
||||
_loadedFiles.clear();
|
||||
_attachmentPickerOpen = false;
|
||||
} catch (e, s) {
|
||||
FlutterError.reportError(
|
||||
FlutterErrorDetails(
|
||||
exception: e, stack: s));
|
||||
sMsgnr.showSnackBar(SnackBar(
|
||||
content: Text(
|
||||
"Failed to send a message: $e")));
|
||||
FlutterError.reportError(FlutterErrorDetails(exception: e, stack: s));
|
||||
sMsgnr.showSnackBar(SnackBar(content: Text("Failed to send a message: $e")));
|
||||
}
|
||||
setState(() {
|
||||
_isSending = false;
|
||||
|
@ -642,9 +557,7 @@ class _MessageInputBarState extends State<MessageInputBar> {
|
|||
? null
|
||||
: (_) async {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text(
|
||||
"Sorry, this feature is not yet available")));
|
||||
const SnackBar(content: Text("Sorry, this feature is not yet available")));
|
||||
return;
|
||||
// HapticFeedback.vibrate();
|
||||
// final hadToAsk =
|
||||
|
|
|
@ -17,8 +17,7 @@ class MessagesList extends StatefulWidget {
|
|||
State<StatefulWidget> createState() => _MessagesListState();
|
||||
}
|
||||
|
||||
class _MessagesListState extends State<MessagesList>
|
||||
with SingleTickerProviderStateMixin {
|
||||
class _MessagesListState extends State<MessagesList> with SingleTickerProviderStateMixin {
|
||||
final ScrollController _sessionListScrollController = ScrollController();
|
||||
|
||||
bool _showSessionListScrollChevron = false;
|
||||
|
@ -36,8 +35,7 @@ class _MessagesListState extends State<MessagesList>
|
|||
void initState() {
|
||||
super.initState();
|
||||
_sessionListScrollController.addListener(() {
|
||||
if (_sessionListScrollController.position.maxScrollExtent > 0 &&
|
||||
!_showSessionListScrollChevron) {
|
||||
if (_sessionListScrollController.position.maxScrollExtent > 0 && !_showSessionListScrollChevron) {
|
||||
setState(() {
|
||||
_showSessionListScrollChevron = true;
|
||||
});
|
||||
|
@ -58,9 +56,7 @@ class _MessagesListState extends State<MessagesList>
|
|||
return Consumer<MessagingClient>(builder: (context, mClient, _) {
|
||||
final friend = mClient.selectedFriend ?? Friend.empty();
|
||||
final cache = mClient.getUserMessageCache(friend.id);
|
||||
final sessions = friend.userStatus.decodedSessions
|
||||
.where((element) => element.isVisible)
|
||||
.toList();
|
||||
final sessions = friend.userStatus.decodedSessions.where((element) => element.isVisible).toList();
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Row(
|
||||
|
@ -77,10 +73,7 @@ class _MessagesListState extends State<MessagesList>
|
|||
child: Icon(
|
||||
Icons.dns,
|
||||
size: 18,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondaryContainer
|
||||
.withAlpha(150),
|
||||
color: Theme.of(context).colorScheme.onSecondaryContainer.withAlpha(150),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -110,8 +103,8 @@ class _MessagesListState extends State<MessagesList>
|
|||
if (sessions.isNotEmpty)
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
transitionBuilder: (child, animation) => SizeTransition(
|
||||
sizeFactor: animation, axis: Axis.vertical, child: child),
|
||||
transitionBuilder: (child, animation) =>
|
||||
SizeTransition(sizeFactor: animation, axis: Axis.vertical, child: child),
|
||||
child: sessions.isEmpty || !_sessionListOpen
|
||||
? null
|
||||
: Container(
|
||||
|
@ -139,8 +132,7 @@ class _MessagesListState extends State<MessagesList>
|
|||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16, right: 4, top: 1, bottom: 1),
|
||||
padding: const EdgeInsets.only(left: 16, right: 4, top: 1, bottom: 1),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.centerLeft,
|
||||
|
@ -190,13 +182,11 @@ class _MessagesListState extends State<MessagesList>
|
|||
children: [
|
||||
const Icon(Icons.message_outlined),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 24),
|
||||
padding: const EdgeInsets.symmetric(vertical: 24),
|
||||
child: Text(
|
||||
"There are no messages here\nWhy not say hello?",
|
||||
textAlign: TextAlign.center,
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
)
|
||||
],
|
||||
|
@ -207,8 +197,7 @@ class _MessagesListState extends State<MessagesList>
|
|||
create: (BuildContext context) => AudioCacheClient(),
|
||||
child: ListView.builder(
|
||||
reverse: true,
|
||||
physics: const BouncingScrollPhysics(
|
||||
decelerationRate: ScrollDecelerationRate.fast),
|
||||
physics: const BouncingScrollPhysics(decelerationRate: ScrollDecelerationRate.fast),
|
||||
itemCount: cache.messages.length,
|
||||
itemBuilder: (context, index) {
|
||||
final entry = cache.messages[index];
|
||||
|
|
|
@ -17,15 +17,12 @@ class SessionTile extends StatelessWidget {
|
|||
foregroundColor: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) => SessionView(session: session)));
|
||||
Navigator.of(context).push(MaterialPageRoute(builder: (context) => SessionView(session: session)));
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
GenericAvatar(
|
||||
imageUri: Aux.resdbToHttp(session.thumbnailUrl),
|
||||
placeholderIcon: Icons.no_photography),
|
||||
GenericAvatar(imageUri: Aux.resdbToHttp(session.thumbnailUrl), placeholderIcon: Icons.no_photography),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Column(
|
||||
|
@ -35,11 +32,10 @@ class SessionTile extends StatelessWidget {
|
|||
FormattedText(session.formattedName),
|
||||
Text(
|
||||
"${session.sessionUsers.length.toString().padLeft(2, "0")}/${session.maxUsers.toString().padLeft(2, "0")} active users",
|
||||
style: Theme.of(context).textTheme.labelMedium?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withOpacity(.6)),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium
|
||||
?.copyWith(color: Theme.of(context).colorScheme.onSurface.withOpacity(.6)),
|
||||
)
|
||||
],
|
||||
),
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:recon/clients/session_client.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 {
|
||||
const SessionListAppBar({super.key});
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class SettingsAppBar extends StatelessWidget {
|
||||
const SettingsAppBar({super.key});
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:recon/main.dart';
|
||||
|
||||
void main() {
|
||||
|
|
Loading…
Reference in a new issue