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");
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:
|
linux: fln.LinuxInitializationSettings(defaultActionName: "Open ReCon"),
|
||||||
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";
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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"),
|
||||||
),
|
),
|
||||||
|
|
|
@ -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)),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -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)),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -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 =
|
||||||
|
|
|
@ -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];
|
||||||
|
|
|
@ -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)),
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -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});
|
||||||
|
|
|
@ -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});
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Loading…
Reference in a new issue