mirror of
https://github.com/localsend/localsend.git
synced 2026-08-07 07:14:52 +00:00
feat: handle rejected requests
This commit is contained in:
@@ -3,7 +3,9 @@
|
||||
"general": {
|
||||
"accept": "Accept",
|
||||
"advanced": "Advanced",
|
||||
"cancel": "Cancel",
|
||||
"decline": "Decline",
|
||||
"finish": "Finish",
|
||||
"hide": "Hide",
|
||||
"offline": "Offline",
|
||||
"online": "Online",
|
||||
@@ -27,7 +29,8 @@
|
||||
"files": "Files",
|
||||
"filesWithCount": "Files ({count})",
|
||||
"selectFiles": "Select files",
|
||||
"nearbyDevices": "Nearby devices"
|
||||
"nearbyDevices": "Nearby devices",
|
||||
"thisDevice": "This Device"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
@@ -57,5 +60,9 @@
|
||||
"one": "wants to send you a file.",
|
||||
"other": "wants to send you {n} files."
|
||||
}
|
||||
},
|
||||
"sendPage": {
|
||||
"waiting": "Waiting for response...",
|
||||
"rejected": "The recipient has rejected the request."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
"general": {
|
||||
"accept": "Akzeptieren",
|
||||
"advanced": "Erweitert",
|
||||
"cancel": "Abbrechen",
|
||||
"decline": "Ablehnen",
|
||||
"finish": "Abschließen",
|
||||
"hide": "Verstecken",
|
||||
"offline": "Offline",
|
||||
"online": "Online",
|
||||
@@ -27,7 +29,8 @@
|
||||
"files": "Dateien",
|
||||
"filesWithCount": "Dateien ({count})",
|
||||
"selectFiles": "Dateien auswählen",
|
||||
"nearbyDevices": "Geräte in der Nähe"
|
||||
"nearbyDevices": "Geräte in der Nähe",
|
||||
"thisDevice": "Dieses Gerät"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Einstellungen",
|
||||
@@ -57,5 +60,9 @@
|
||||
"one": "möchte dir eine Datei senden.",
|
||||
"other": "möchte dir {n} Dateien senden."
|
||||
}
|
||||
},
|
||||
"sendPage": {
|
||||
"waiting": "Warte auf Antwort...",
|
||||
"rejected": "Der Empfänger hat die Anfrage abgelehnt."
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ Future<void> main() async {
|
||||
runApp(TranslationProvider(
|
||||
child: ProviderScope(
|
||||
overrides: [
|
||||
deviceInfoProvider.overrideWithValue(deviceInfo),
|
||||
deviceRawInfoProvider.overrideWithValue(deviceInfo),
|
||||
settingsProvider.overrideWith((ref) => SettingsNotifier(persistenceService)),
|
||||
],
|
||||
child: const LocalSendApp(),
|
||||
|
||||
@@ -1,26 +1,16 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:localsend_app/model/device.dart';
|
||||
import 'package:localsend_app/model/file_type.dart';
|
||||
|
||||
part 'file_dto.freezed.dart';
|
||||
part 'file_dto.g.dart';
|
||||
|
||||
/// Categorization of one file.
|
||||
/// We use this information for a better UX.
|
||||
enum FileType {
|
||||
image,
|
||||
video,
|
||||
pdf,
|
||||
text,
|
||||
other,
|
||||
}
|
||||
|
||||
@freezed
|
||||
class FileDto with _$FileDto {
|
||||
const factory FileDto({
|
||||
required int id, // unique inside from send batch
|
||||
required String id, // unique inside send session
|
||||
required String fileName,
|
||||
required int size,
|
||||
required DeviceType deviceType,
|
||||
required FileType fileType,
|
||||
}) = _FileDto;
|
||||
|
||||
factory FileDto.fromJson(Map<String, Object?> json) => _$FileDtoFromJson(json);
|
||||
|
||||
@@ -9,7 +9,7 @@ part 'send_request_dto.g.dart';
|
||||
class SendRequestDto with _$SendRequestDto {
|
||||
const factory SendRequestDto({
|
||||
required InfoDto info,
|
||||
required List<FileDto> files,
|
||||
required Map<String, FileDto> files,
|
||||
}) = _SendRequestDto;
|
||||
|
||||
factory SendRequestDto.fromJson(Map<String, Object?> json) => _$SendRequestDtoFromJson(json);
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/// Categorization of one file.
|
||||
/// We use this information for a better UX.
|
||||
enum FileType {
|
||||
image,
|
||||
video,
|
||||
pdf,
|
||||
text,
|
||||
other,
|
||||
}
|
||||
|
||||
extension FileTypeGuessExt on String {
|
||||
FileType guessFileType() {
|
||||
final extension = substring(lastIndexOf('.') + 1).toLowerCase();
|
||||
switch (extension) {
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
case 'png':
|
||||
case 'gif':
|
||||
case 'svg':
|
||||
return FileType.image;
|
||||
case 'mp4':
|
||||
case 'mov':
|
||||
return FileType.video;
|
||||
case 'pdf':
|
||||
return FileType.pdf;
|
||||
case 'txt':
|
||||
return FileType.text;
|
||||
default:
|
||||
return FileType.other;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:localsend_app/model/device.dart';
|
||||
import 'package:localsend_app/model/send_files/sending_file.dart';
|
||||
|
||||
part 'send_state.freezed.dart';
|
||||
|
||||
enum SendStatus {
|
||||
waiting,
|
||||
declined,
|
||||
sending,
|
||||
finished,
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SendState with _$SendState {
|
||||
const factory SendState({
|
||||
required SendStatus status,
|
||||
required Device target,
|
||||
required Map<String, SendingFile> files, // file id as key
|
||||
required CancelToken? cancelToken,
|
||||
}) = _SendState;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:localsend_app/model/dto/file_dto.dart';
|
||||
|
||||
part 'sending_file.freezed.dart';
|
||||
|
||||
@freezed
|
||||
class SendingFile with _$SendingFile {
|
||||
const factory SendingFile({
|
||||
required FileDto file,
|
||||
required Future<Uint8List> Function() read,
|
||||
required String? token,
|
||||
}) = _SendingFile;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:localsend_app/model/device.dart';
|
||||
import 'package:localsend_app/model/server/expected_file.dart';
|
||||
import 'package:localsend_app/model/server/receiving_file.dart';
|
||||
|
||||
part 'receive_state.freezed.dart';
|
||||
|
||||
@@ -8,6 +8,6 @@ part 'receive_state.freezed.dart';
|
||||
class ReceiveState with _$ReceiveState {
|
||||
const factory ReceiveState({
|
||||
required Device sender,
|
||||
required Map<int, ExpectedFile> files,
|
||||
required Map<String, ReceivingFile> files,
|
||||
}) = _ReceiveState;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:localsend_app/model/dto/file_dto.dart';
|
||||
|
||||
part 'expected_file.freezed.dart';
|
||||
part 'receiving_file.freezed.dart';
|
||||
|
||||
@freezed
|
||||
class ExpectedFile with _$ExpectedFile {
|
||||
const factory ExpectedFile({
|
||||
required String token,
|
||||
class ReceivingFile with _$ReceivingFile {
|
||||
const factory ReceivingFile({
|
||||
required FileDto file,
|
||||
required String token,
|
||||
required String? tempPath, // file is saved to a temporary path first
|
||||
}) = _ExpectedFile;
|
||||
}) = _ReceivingFile;
|
||||
}
|
||||
@@ -10,7 +10,7 @@ part 'temp_request.freezed.dart';
|
||||
class TempRequest with _$TempRequest {
|
||||
const factory TempRequest({
|
||||
required Device sender,
|
||||
required List<FileDto> files,
|
||||
required Map<String, FileDto> files,
|
||||
required StreamController<bool> responseHandler, // use this to accept / decline the request
|
||||
}) = _TempRequest;
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ class ReceivePage extends ConsumerWidget {
|
||||
foregroundColor: Theme.of(context).buttonTheme.colorScheme!.onPrimary,
|
||||
),
|
||||
onPressed: () {
|
||||
ref.read(serverProvider.notifier).acceptFileRequest();
|
||||
ref.read(serverProvider.notifier).acceptFileRequest(tempRequest.files.values.map((f) => f.id).toSet());
|
||||
context.pop();
|
||||
},
|
||||
icon: const Icon(Icons.check_circle),
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:localsend_app/gen/strings.g.dart';
|
||||
import 'package:localsend_app/model/device.dart';
|
||||
import 'package:localsend_app/model/send_files/send_state.dart';
|
||||
import 'package:localsend_app/provider/device_info_provider.dart';
|
||||
import 'package:localsend_app/provider/network/send_provider.dart';
|
||||
import 'package:localsend_app/widget/device_list_tile.dart';
|
||||
|
||||
class SendPage extends ConsumerStatefulWidget {
|
||||
const SendPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
ConsumerState<SendPage> createState() => _SendPageState();
|
||||
}
|
||||
|
||||
class _SendPageState extends ConsumerState<SendPage> {
|
||||
Device? _myDevice;
|
||||
Device? _targetDevice;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sendState = ref.watch(sendProvider);
|
||||
if (sendState == null && _myDevice == null && _targetDevice == null) {
|
||||
return Scaffold(
|
||||
body: Container(),
|
||||
);
|
||||
}
|
||||
final myDevice = ref.watch(deviceInfoProvider);
|
||||
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 30),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Hero(
|
||||
tag: 'this-device',
|
||||
child: DeviceListTile(
|
||||
device: myDevice,
|
||||
thisDevice: true,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const Icon(Icons.arrow_downward),
|
||||
const SizedBox(height: 20),
|
||||
Hero(
|
||||
tag: 'device-${(sendState?.target ?? _targetDevice)?.ip}',
|
||||
child: DeviceListTile(
|
||||
device: sendState?.target ?? _targetDevice!,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (sendState != null)
|
||||
...[
|
||||
if (sendState.status == SendStatus.waiting)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 20),
|
||||
child: Text(t.sendPage.waiting, textAlign: TextAlign.center),
|
||||
)
|
||||
else if (sendState.status == SendStatus.declined)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 20),
|
||||
child: Text(t.sendPage.rejected, style: const TextStyle(color: Colors.orange), textAlign: TextAlign.center),
|
||||
),
|
||||
Center(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_myDevice = myDevice;
|
||||
_targetDevice = sendState.target;
|
||||
});
|
||||
ref.read(sendProvider.notifier).cancel();
|
||||
context.pop();
|
||||
},
|
||||
icon: Icon(sendState.status == SendStatus.declined ? Icons.check_circle : Icons.close),
|
||||
label: Text(sendState.status == SendStatus.declined ? t.general.finish : t.general.cancel),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,12 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:localsend_app/gen/strings.g.dart';
|
||||
import 'package:localsend_app/model/dto/info_dto.dart';
|
||||
import 'package:localsend_app/model/dto/send_request_dto.dart';
|
||||
import 'package:localsend_app/provider/device_info_provider.dart';
|
||||
import 'package:localsend_app/provider/nearby_devices_provider.dart';
|
||||
import 'package:localsend_app/provider/network/send_provider.dart';
|
||||
import 'package:localsend_app/provider/selected_files_provider.dart';
|
||||
import 'package:localsend_app/provider/settings_provider.dart';
|
||||
import 'package:localsend_app/util/file_size_helper.dart';
|
||||
import 'package:localsend_app/widget/device_list_tile.dart';
|
||||
|
||||
@@ -28,9 +25,8 @@ class _SendTabState extends ConsumerState<SendTab> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final deviceInfo = ref.watch(deviceInfoProvider);
|
||||
final settings = ref.watch(settingsProvider);
|
||||
final selectedFiles = ref.watch(selectedFilesProvider);
|
||||
final myDevice = ref.watch(deviceInfoProvider);
|
||||
final devices = ref.watch(nearbyDevicesProvider);
|
||||
return ListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 20),
|
||||
@@ -90,36 +86,30 @@ class _SendTabState extends ConsumerState<SendTab> {
|
||||
const SizedBox(height: 20),
|
||||
Text(t.send.nearbyDevices, style: Theme.of(context).textTheme.subtitle1),
|
||||
const SizedBox(height: 10),
|
||||
Hero(
|
||||
tag: 'this-device',
|
||||
child: DeviceListTile(
|
||||
device: myDevice,
|
||||
thisDevice: true,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
...devices.when(
|
||||
data: (data) {
|
||||
return data.map((device) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: DeviceListTile(
|
||||
device: device,
|
||||
onTap: () async {
|
||||
final url = 'http://${device.ip}:${device.port}/localsend/v1/send-request';
|
||||
final dio = Dio(BaseOptions(
|
||||
connectTimeout: 30 * 1000,
|
||||
sendTimeout: 30 * 1000,
|
||||
));
|
||||
try {
|
||||
final response = await dio.post(url,
|
||||
data: SendRequestDto(
|
||||
info: InfoDto(
|
||||
alias: settings.alias,
|
||||
deviceModel: deviceInfo.deviceModel,
|
||||
deviceType: deviceInfo.deviceType,
|
||||
),
|
||||
files: [],
|
||||
).toJson());
|
||||
print('Response: ${response.statusCode}, ${response.data.runtimeType}');
|
||||
} on DioError catch (e) {
|
||||
if (e.type != DioErrorType.response) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Hero(
|
||||
tag: 'device-${device.ip}',
|
||||
child: DeviceListTile(
|
||||
device: device,
|
||||
onTap: () {
|
||||
ref.read(sendProvider.notifier).sendRequest(
|
||||
target: device,
|
||||
files: ref.read(selectedFilesProvider),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:localsend_app/model/device.dart';
|
||||
import 'package:localsend_app/provider/network/server_provider.dart';
|
||||
import 'package:localsend_app/provider/network_info_provider.dart';
|
||||
import 'package:localsend_app/util/device_info_helper.dart';
|
||||
|
||||
final deviceInfoProvider = Provider<DeviceInfoResult>((ref) {
|
||||
final deviceRawInfoProvider = Provider<DeviceInfoResult>((ref) {
|
||||
throw Exception('settingsProvider not initialized');
|
||||
});
|
||||
|
||||
final deviceInfoProvider = Provider((ref) {
|
||||
final networkInfo = ref.watch(networkInfoProvider);
|
||||
final serverState = ref.watch(serverProvider);
|
||||
final rawInfo = ref.watch(deviceRawInfoProvider);
|
||||
return Device(
|
||||
ip: networkInfo?.localIp ?? '-',
|
||||
port: serverState?.port ?? -1,
|
||||
alias: serverState?.alias ?? '-',
|
||||
deviceModel: rawInfo.deviceModel,
|
||||
deviceType: rawInfo.deviceType,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:localsend_app/main.dart';
|
||||
import 'package:localsend_app/model/device.dart';
|
||||
import 'package:localsend_app/model/dto/file_dto.dart';
|
||||
import 'package:localsend_app/model/dto/info_dto.dart';
|
||||
import 'package:localsend_app/model/dto/send_request_dto.dart';
|
||||
import 'package:localsend_app/model/file_type.dart';
|
||||
import 'package:localsend_app/model/send_files/send_state.dart';
|
||||
import 'package:localsend_app/model/send_files/sending_file.dart';
|
||||
import 'package:localsend_app/provider/device_info_provider.dart';
|
||||
import 'package:localsend_app/routes.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
const _uuid = Uuid();
|
||||
|
||||
final sendProvider = StateNotifierProvider<SendNotifier, SendState?>((ref) {
|
||||
return SendNotifier(ref);
|
||||
});
|
||||
|
||||
class SendNotifier extends StateNotifier<SendState?> {
|
||||
final Ref _ref;
|
||||
SendNotifier(this._ref) : super(null);
|
||||
|
||||
Future<void> sendRequest({
|
||||
required Device target,
|
||||
required List<PlatformFile> files,
|
||||
}) async {
|
||||
final url = 'http://${target.ip}:${target.port}/localsend/v1/send-request';
|
||||
final dio = Dio(BaseOptions(
|
||||
connectTimeout: 30 * 1000,
|
||||
sendTimeout: 30 * 1000,
|
||||
));
|
||||
final cancelToken = CancelToken();
|
||||
|
||||
final requestState = SendState(
|
||||
status: SendStatus.waiting,
|
||||
target: target,
|
||||
files: Map.fromEntries(files.map((file) {
|
||||
final id = _uuid.v4();
|
||||
return MapEntry(
|
||||
id,
|
||||
SendingFile(
|
||||
file: FileDto(
|
||||
id: id,
|
||||
fileName: file.name,
|
||||
size: file.size,
|
||||
fileType: file.name.guessFileType(),
|
||||
),
|
||||
read: () async => file.bytes!,
|
||||
token: null,
|
||||
),
|
||||
);
|
||||
})),
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
|
||||
final originDevice = _ref.read(deviceInfoProvider);
|
||||
|
||||
final requestDto = SendRequestDto(
|
||||
info: InfoDto(
|
||||
alias: originDevice.alias,
|
||||
deviceModel: originDevice.deviceModel,
|
||||
deviceType: originDevice.deviceType,
|
||||
),
|
||||
files: {
|
||||
for (final file in requestState.files.values)
|
||||
file.file.id: file.file,
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
state = requestState;
|
||||
|
||||
// ignore: use_build_context_synchronously
|
||||
const SendRoute().push(LocalSendApp.routerContext);
|
||||
|
||||
final response = await dio.post(
|
||||
url,
|
||||
data: requestDto.toJson(),
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
|
||||
final responseMap = response.data as Map;
|
||||
state = requestState.copyWith(
|
||||
status: SendStatus.sending,
|
||||
files: {
|
||||
for (final file in requestState.files.values)
|
||||
file.file.id: responseMap.containsKey(file.file.id) ? file.copyWith(token: responseMap[file.file.id]) : file,
|
||||
}
|
||||
);
|
||||
|
||||
print('Response: ${response.statusCode}, ${response.data}, ${response.data.runtimeType}');
|
||||
} on DioError catch (e) {
|
||||
if (e.type != DioErrorType.response && e.type != DioErrorType.cancel) {
|
||||
print(e);
|
||||
}
|
||||
state = state?.copyWith(
|
||||
status: SendStatus.declined,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
state?.cancelToken?.cancel();
|
||||
state = null;
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:localsend_app/main.dart';
|
||||
import 'package:localsend_app/model/dto/info_dto.dart';
|
||||
import 'package:localsend_app/model/dto/send_request_dto.dart';
|
||||
import 'package:localsend_app/model/server/expected_file.dart';
|
||||
import 'package:localsend_app/model/server/receive_state.dart';
|
||||
import 'package:localsend_app/model/server/receiving_file.dart';
|
||||
import 'package:localsend_app/model/server/server_state.dart';
|
||||
import 'package:localsend_app/model/server/temp_request.dart';
|
||||
import 'package:localsend_app/provider/device_info_provider.dart';
|
||||
@@ -22,7 +22,7 @@ import 'package:uuid/uuid.dart';
|
||||
|
||||
/// This provider manages receiving file requests.
|
||||
final serverProvider = StateNotifierProvider<ServerNotifier, ServerState?>((ref) {
|
||||
final deviceInfo = ref.watch(deviceInfoProvider);
|
||||
final deviceInfo = ref.watch(deviceRawInfoProvider);
|
||||
return ServerNotifier(deviceInfo);
|
||||
});
|
||||
|
||||
@@ -84,7 +84,10 @@ class ServerNotifier extends StateNotifier<ServerState?> {
|
||||
// Delayed response (waiting for user's decision)
|
||||
final result = await streamController.stream.first;
|
||||
if (result) {
|
||||
return Response.ok('');
|
||||
return Response.ok(jsonEncode({
|
||||
for (final file in state!.receiveState!.files.values)
|
||||
file.file.id: file.token,
|
||||
}), headers: {'Content-Type': 'application/json'});
|
||||
} else {
|
||||
return Response.badRequest();
|
||||
}
|
||||
@@ -120,28 +123,29 @@ class ServerNotifier extends StateNotifier<ServerState?> {
|
||||
return await startServer(alias: alias, port: port);
|
||||
}
|
||||
|
||||
void acceptFileRequest() {
|
||||
void acceptFileRequest(Set<String> fileIds) {
|
||||
final tempRequest = state?.tempRequest;
|
||||
if (tempRequest == null) {
|
||||
return;
|
||||
}
|
||||
tempRequest.responseHandler.add(true);
|
||||
tempRequest.responseHandler.close();
|
||||
|
||||
state = state?.copyWith(
|
||||
tempRequest: null,
|
||||
receiveState: ReceiveState(
|
||||
sender: tempRequest.sender,
|
||||
files: {
|
||||
for (final file in tempRequest.files)
|
||||
file.id: ExpectedFile(
|
||||
for (final file in tempRequest.files.values)
|
||||
file.id: ReceivingFile(
|
||||
token: _uuid.v4(),
|
||||
file: file,
|
||||
tempPath: null,
|
||||
tempPath: fileIds.contains(file.id) ? _uuid.v4() : null,
|
||||
),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
tempRequest.responseHandler.add(true);
|
||||
tempRequest.responseHandler.close();
|
||||
}
|
||||
|
||||
void declineFileRequest() {
|
||||
@@ -149,11 +153,13 @@ class ServerNotifier extends StateNotifier<ServerState?> {
|
||||
if (controller == null) {
|
||||
return;
|
||||
}
|
||||
controller.add(false);
|
||||
controller.close();
|
||||
|
||||
state = state?.copyWith(
|
||||
tempRequest: null,
|
||||
receiveState: null,
|
||||
);
|
||||
|
||||
controller.add(false);
|
||||
controller.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:localsend_app/pages/home_page.dart';
|
||||
import 'package:localsend_app/pages/receive_page.dart';
|
||||
import 'package:localsend_app/pages/send_page.dart';
|
||||
|
||||
part 'routes.g.dart';
|
||||
|
||||
@@ -20,3 +21,11 @@ class ReceiveRoute extends GoRouteData {
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) => const ReceivePage();
|
||||
}
|
||||
|
||||
@TypedGoRoute<SendRoute>(path: '/send')
|
||||
class SendRoute extends GoRouteData {
|
||||
const SendRoute();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) => const SendPage();
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:localsend_app/gen/strings.g.dart';
|
||||
import 'package:localsend_app/model/device.dart';
|
||||
import 'package:localsend_app/util/ip_helper.dart';
|
||||
import 'package:localsend_app/widget/device_bage.dart';
|
||||
|
||||
class DeviceListTile extends StatelessWidget {
|
||||
final Device device;
|
||||
final bool thisDevice;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const DeviceListTile({required this.device, this.onTap});
|
||||
const DeviceListTile({required this.device, this.thisDevice = false, this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -24,27 +26,31 @@ class DeviceListTile extends StatelessWidget {
|
||||
children: [
|
||||
Icon(device.deviceType.icon, size: 46),
|
||||
const SizedBox(width: 15),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(device.alias, style: const TextStyle(fontSize: 20)),
|
||||
const SizedBox(height: 5),
|
||||
Wrap(
|
||||
runSpacing: 10,
|
||||
spacing: 10,
|
||||
children: [
|
||||
DeviceBadge(
|
||||
color: Theme.of(context).colorScheme.tertiaryContainer,
|
||||
label: '#${device.ip.visualId}',
|
||||
),
|
||||
if (device.deviceModel != null)
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FittedBox(
|
||||
child: Text('${device.alias}${thisDevice ? ' (${t.send.thisDevice})' : ''}', style: const TextStyle(fontSize: 20)),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Wrap(
|
||||
runSpacing: 10,
|
||||
spacing: 10,
|
||||
children: [
|
||||
DeviceBadge(
|
||||
color: Theme.of(context).colorScheme.tertiaryContainer,
|
||||
label: device.deviceModel!,
|
||||
label: '#${device.ip.visualId}',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
if (device.deviceModel != null)
|
||||
DeviceBadge(
|
||||
color: Theme.of(context).colorScheme.tertiaryContainer,
|
||||
label: device.deviceModel!,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user