mirror of
https://github.com/localsend/localsend.git
synced 2026-08-07 07:14:52 +00:00
refactor: merge web receive and web send
This commit is contained in:
@@ -6,7 +6,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:localsend_app/config/theme.dart';
|
||||
import 'package:localsend_app/gen/strings.g.dart';
|
||||
import 'package:localsend_app/model/state/server/receive_session_state.dart';
|
||||
import 'package:localsend_app/pages/web_receive_page.dart';
|
||||
import 'package:localsend_app/pages/web_share_page.dart';
|
||||
import 'package:localsend_app/provider/network/send_provider.dart';
|
||||
import 'package:localsend_app/provider/network/server/server_provider.dart';
|
||||
import 'package:localsend_app/provider/progress_provider.dart';
|
||||
@@ -143,7 +143,7 @@ class _ProgressPageState extends State<ProgressPage> with Refena {
|
||||
|
||||
if (result && mounted) {
|
||||
if (ref.read(serverProvider)?.webUpload == true) {
|
||||
context.global.dispatch(NavigateAction.popUntil<WebReceivePage>());
|
||||
context.global.dispatch(NavigateAction.popUntil<WebSharePage>());
|
||||
} else {
|
||||
context.global.dispatch(NavigateAction.popUntilRoot());
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:localsend_app/pages/home_page.dart';
|
||||
import 'package:localsend_app/pages/home_page_controller.dart';
|
||||
import 'package:localsend_app/pages/receive_history_page.dart';
|
||||
import 'package:localsend_app/pages/tabs/receive_tab_vm.dart';
|
||||
import 'package:localsend_app/pages/web_receive_page.dart';
|
||||
import 'package:localsend_app/pages/web_share_page.dart';
|
||||
import 'package:localsend_app/provider/animation_provider.dart';
|
||||
import 'package:localsend_app/util/ip_helper.dart';
|
||||
import 'package:localsend_app/widget/animations/initial_fade_transition.dart';
|
||||
@@ -74,7 +74,7 @@ class ReceiveTab extends StatelessWidget {
|
||||
child: Center(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () async {
|
||||
await context.global.dispatchAsync(NavigateAction.push(WebReceivePage()));
|
||||
await context.global.dispatchAsync(NavigateAction.push(const WebSharePage()));
|
||||
},
|
||||
icon: Icon(Icons.language),
|
||||
label: Text(t.$wip.receiveTab.link('Receive via link')),
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:localsend_app/model/persistence/favorite_device.dart';
|
||||
import 'package:localsend_app/model/send_mode.dart';
|
||||
import 'package:localsend_app/pages/progress_page.dart';
|
||||
import 'package:localsend_app/pages/send_page.dart';
|
||||
import 'package:localsend_app/pages/web_send_page.dart';
|
||||
import 'package:localsend_app/pages/web_share_page.dart';
|
||||
import 'package:localsend_app/provider/favorites_provider.dart';
|
||||
import 'package:localsend_app/provider/local_ip_provider.dart';
|
||||
import 'package:localsend_app/provider/network/nearby_devices_provider.dart';
|
||||
@@ -113,7 +113,7 @@ final sendTabVmProvider = ViewProvider((ref) {
|
||||
await context.pushBottomSheet(() => const NoFilesDialog());
|
||||
return;
|
||||
}
|
||||
await context.push(() => WebSendPage(files));
|
||||
await context.push(() => WebSharePage(files: files));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,252 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:localsend_app/config/theme.dart';
|
||||
import 'package:localsend_app/gen/strings.g.dart';
|
||||
import 'package:localsend_app/provider/local_ip_provider.dart';
|
||||
import 'package:localsend_app/provider/network/server/server_provider.dart';
|
||||
import 'package:localsend_app/provider/settings_provider.dart';
|
||||
import 'package:localsend_app/util/native/platform_check.dart';
|
||||
import 'package:localsend_app/util/ui/snackbar.dart';
|
||||
import 'package:localsend_app/widget/dialogs/qr_dialog.dart';
|
||||
import 'package:localsend_app/widget/dialogs/zoom_dialog.dart';
|
||||
import 'package:localsend_app/widget/responsive_list_view.dart';
|
||||
import 'package:localsend_isolates/util/sleep.dart';
|
||||
import 'package:refena_flutter/refena_flutter.dart';
|
||||
import 'package:routerino/routerino.dart';
|
||||
|
||||
enum _ServerState { initializing, running, error, stopping }
|
||||
|
||||
/// Lets web browsers upload files to this device.
|
||||
/// Incoming requests are not listed here because they open the receive page
|
||||
/// like any other incoming request.
|
||||
class WebReceivePage extends StatefulWidget {
|
||||
const WebReceivePage();
|
||||
|
||||
@override
|
||||
State<WebReceivePage> createState() => _WebReceivePageState();
|
||||
}
|
||||
|
||||
class _WebReceivePageState extends State<WebReceivePage> with Refena {
|
||||
_ServerState _stateEnum = _ServerState.initializing;
|
||||
bool _encrypted = false;
|
||||
String? _initializedError;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_init(encrypted: false);
|
||||
});
|
||||
}
|
||||
|
||||
void _init({required bool encrypted}) async {
|
||||
final settings = ref.read(settingsProvider);
|
||||
setState(() {
|
||||
_stateEnum = _ServerState.initializing;
|
||||
_encrypted = encrypted;
|
||||
_initializedError = null;
|
||||
});
|
||||
await sleepAsync(500);
|
||||
try {
|
||||
await ref
|
||||
.notifier(serverProvider)
|
||||
.restartServer(
|
||||
alias: settings.alias,
|
||||
port: settings.port,
|
||||
https: _encrypted,
|
||||
webUpload: true,
|
||||
);
|
||||
setState(() {
|
||||
_stateEnum = _ServerState.running;
|
||||
});
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
setState(() {
|
||||
_stateEnum = _ServerState.error;
|
||||
_initializedError = e.toString();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Web receive uses unencrypted http by default, so we need to revert to the previous state.
|
||||
Future<void> _revertServerState() async {
|
||||
await ref.notifier(serverProvider).restartServerFromSettings();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopScope(
|
||||
onPopInvokedWithResult: (_, _) async {
|
||||
if (_stateEnum != _ServerState.running) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_stateEnum = _ServerState.stopping;
|
||||
});
|
||||
await sleepAsync(250);
|
||||
await _revertServerState();
|
||||
await sleepAsync(250);
|
||||
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
}
|
||||
},
|
||||
canPop: false,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(t.webReceivePage.title),
|
||||
),
|
||||
body: Builder(
|
||||
builder: (context) {
|
||||
if (_stateEnum != _ServerState.running) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (_stateEnum == _ServerState.initializing || _stateEnum == _ServerState.stopping) ...[
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(height: 20),
|
||||
Center(
|
||||
child: Text(
|
||||
_stateEnum == _ServerState.initializing ? t.webSharePage.loading : t.webSharePage.stopping,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
] else if (_initializedError != null) ...[
|
||||
const Icon(Icons.error_outline, size: 48, color: Colors.red),
|
||||
const SizedBox(height: 10),
|
||||
Center(
|
||||
child: Text(t.webSharePage.error, style: Theme.of(context).textTheme.titleLarge),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Center(
|
||||
child: SelectableText(_initializedError!, style: Theme.of(context).textTheme.bodyMedium),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
final serverState = context.watch(serverProvider);
|
||||
if (serverState == null) {
|
||||
// the server is restarting
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
final networkState = context.watch(localIpProvider);
|
||||
final settings = context.watch(settingsProvider);
|
||||
|
||||
return ResponsiveListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 20),
|
||||
children: [
|
||||
Text(t.webSharePage.openLink(n: networkState.localIps.length), style: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(height: 10),
|
||||
Card(
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
...networkState.localIps.map((ip) {
|
||||
final url = '${_encrypted ? 'https' : 'http'}://$ip:${serverState.port}';
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: Row(
|
||||
children: [
|
||||
SelectableText(
|
||||
url,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
await Clipboard.setData(ClipboardData(text: url));
|
||||
if (context.mounted && checkPlatformIsDesktop()) {
|
||||
context.showSnackBar(t.general.copiedToClipboard);
|
||||
}
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
child: Icon(Icons.content_copy, size: 16),
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (_) => QrDialog(
|
||||
data: url,
|
||||
label: url,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
child: Icon(Icons.qr_code, size: 16),
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (_) => ZoomDialog(
|
||||
label: url,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
child: Icon(Icons.tv, size: 16),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(t.webSharePage.encryption, style: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(width: 10),
|
||||
Checkbox(
|
||||
value: _encrypted,
|
||||
onChanged: (value) {
|
||||
_init(encrypted: value == true);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_encrypted)
|
||||
Text(
|
||||
t.webSharePage.encryptionHint,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: Theme.of(context).colorScheme.warning),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(t.webSharePage.autoAccept, style: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(width: 10),
|
||||
Checkbox(
|
||||
value: settings.receiveViaLinkAutoAccept,
|
||||
onChanged: (value) async {
|
||||
await ref.notifier(settingsProvider).setReceiveViaLinkAutoAccept(value == true);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -13,25 +13,37 @@ import 'package:localsend_app/widget/dialogs/qr_dialog.dart';
|
||||
import 'package:localsend_app/widget/dialogs/zoom_dialog.dart';
|
||||
import 'package:localsend_app/widget/responsive_list_view.dart';
|
||||
import 'package:localsend_isolates/util/sleep.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:refena_flutter/refena_flutter.dart';
|
||||
import 'package:routerino/routerino.dart';
|
||||
|
||||
final _logger = Logger('WebSharePage');
|
||||
|
||||
enum _ServerState { initializing, running, error, stopping }
|
||||
|
||||
class WebSendPage extends StatefulWidget {
|
||||
final List<CrossFile> files;
|
||||
/// Shares a link with web browsers, in one of two modes:
|
||||
/// - send: offers [WebSharePage.files] for download.
|
||||
/// - receive: serves the upload page so browsers can upload files to this device.
|
||||
/// Incoming requests are not listed here because they open the receive page
|
||||
/// like any other incoming request.
|
||||
class WebSharePage extends StatefulWidget {
|
||||
/// The files offered for download (share via link).
|
||||
/// `null` serves the upload page instead (receive via link).
|
||||
final List<CrossFile>? files;
|
||||
|
||||
const WebSendPage(this.files);
|
||||
const WebSharePage({this.files});
|
||||
|
||||
@override
|
||||
State<WebSendPage> createState() => _WebSendPageState();
|
||||
State<WebSharePage> createState() => _WebSharePageState();
|
||||
}
|
||||
|
||||
class _WebSendPageState extends State<WebSendPage> with Refena {
|
||||
class _WebSharePageState extends State<WebSharePage> with Refena {
|
||||
_ServerState _stateEnum = _ServerState.initializing;
|
||||
bool _encrypted = false;
|
||||
String? _initializedError;
|
||||
|
||||
bool get _sendMode => widget.files != null;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -49,15 +61,27 @@ class _WebSendPageState extends State<WebSendPage> with Refena {
|
||||
});
|
||||
await sleepAsync(500);
|
||||
try {
|
||||
// The auto accept setting and the pin of a previous web send state are kept.
|
||||
await ref
|
||||
.notifier(serverProvider)
|
||||
.restartServerWithWebSend(
|
||||
alias: settings.alias,
|
||||
port: settings.port,
|
||||
https: _encrypted,
|
||||
files: widget.files,
|
||||
);
|
||||
final files = widget.files;
|
||||
if (files != null) {
|
||||
// The auto accept setting and the pin of a previous web send state are kept.
|
||||
await ref
|
||||
.notifier(serverProvider)
|
||||
.restartServerWithWebSend(
|
||||
alias: settings.alias,
|
||||
port: settings.port,
|
||||
https: _encrypted,
|
||||
files: files,
|
||||
);
|
||||
} else {
|
||||
await ref
|
||||
.notifier(serverProvider)
|
||||
.restartServer(
|
||||
alias: settings.alias,
|
||||
port: settings.port,
|
||||
https: _encrypted,
|
||||
webUpload: true,
|
||||
);
|
||||
}
|
||||
setState(() {
|
||||
_stateEnum = _ServerState.running;
|
||||
});
|
||||
@@ -71,7 +95,7 @@ class _WebSendPageState extends State<WebSendPage> with Refena {
|
||||
}
|
||||
}
|
||||
|
||||
/// Web share uses unencrypted http, so we need to revert to the previous state.
|
||||
/// Web share uses unencrypted http by default, so we need to revert to the previous state.
|
||||
Future<void> _revertServerState() async {
|
||||
await ref.notifier(serverProvider).restartServerFromSettings();
|
||||
}
|
||||
@@ -80,7 +104,7 @@ class _WebSendPageState extends State<WebSendPage> with Refena {
|
||||
Widget build(BuildContext context) {
|
||||
return PopScope(
|
||||
onPopInvokedWithResult: (_, _) async {
|
||||
if (_stateEnum != _ServerState.running) {
|
||||
if (_stateEnum == _ServerState.initializing || _stateEnum == _ServerState.stopping) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -88,7 +112,12 @@ class _WebSendPageState extends State<WebSendPage> with Refena {
|
||||
_stateEnum = _ServerState.stopping;
|
||||
});
|
||||
await sleepAsync(250);
|
||||
await _revertServerState();
|
||||
try {
|
||||
// Also needed in the error state: the failed restart already stopped the old server.
|
||||
await _revertServerState();
|
||||
} catch (e) {
|
||||
_logger.warning('Failed to restore the server', e);
|
||||
}
|
||||
await sleepAsync(250);
|
||||
|
||||
if (context.mounted) {
|
||||
@@ -98,7 +127,7 @@ class _WebSendPageState extends State<WebSendPage> with Refena {
|
||||
canPop: false,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(t.webSharePage.title),
|
||||
title: Text(_sendMode ? t.webSharePage.title : t.webReceivePage.title),
|
||||
),
|
||||
body: Builder(
|
||||
builder: (context) {
|
||||
@@ -134,11 +163,12 @@ class _WebSendPageState extends State<WebSendPage> with Refena {
|
||||
|
||||
final serverState = context.watch(serverProvider);
|
||||
final webSendState = serverState?.webSendState;
|
||||
if (serverState == null || webSendState == null) {
|
||||
if (serverState == null || (_sendMode && webSendState == null)) {
|
||||
// the server is restarting (e.g. because the pin changed)
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
final networkState = context.watch(localIpProvider);
|
||||
final settings = context.watch(settingsProvider);
|
||||
|
||||
return ResponsiveListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 20),
|
||||
@@ -154,8 +184,8 @@ class _WebSendPageState extends State<WebSendPage> with Refena {
|
||||
children: [
|
||||
...networkState.localIps.map((ip) {
|
||||
final url = '${_encrypted ? 'https' : 'http'}://$ip:${serverState.port}';
|
||||
final urlWithPin = switch (webSendState.pin) {
|
||||
String() => '$url/?pin=${Uri.encodeQueryComponent(webSendState.pin!)}',
|
||||
final urlWithPin = switch (webSendState?.pin) {
|
||||
String() => '$url/?pin=${Uri.encodeQueryComponent(webSendState!.pin!)}',
|
||||
null => url,
|
||||
};
|
||||
return Padding(
|
||||
@@ -186,8 +216,8 @@ class _WebSendPageState extends State<WebSendPage> with Refena {
|
||||
builder: (_) => QrDialog(
|
||||
data: urlWithPin,
|
||||
label: url,
|
||||
listenIncomingWebSendRequests: true,
|
||||
pin: webSendState.pin,
|
||||
listenIncomingWebSendRequests: _sendMode,
|
||||
pin: webSendState?.pin,
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -202,8 +232,8 @@ class _WebSendPageState extends State<WebSendPage> with Refena {
|
||||
context: context,
|
||||
builder: (_) => ZoomDialog(
|
||||
label: url,
|
||||
pin: webSendState.pin,
|
||||
listenIncomingWebSendRequests: true,
|
||||
listenIncomingWebSendRequests: _sendMode,
|
||||
pin: webSendState?.pin,
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -221,72 +251,74 @@ class _WebSendPageState extends State<WebSendPage> with Refena {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(t.webSharePage.requests, style: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(height: 10),
|
||||
if (webSendState.sessions.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 30),
|
||||
child: Text(t.webSharePage.noRequests),
|
||||
),
|
||||
...webSendState.sessions.entries.map((entry) {
|
||||
final session = entry.value;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
session.deviceInfo,
|
||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||
color: session.pending ? Theme.of(context).colorScheme.warning : null,
|
||||
if (webSendState != null) ...[
|
||||
Text(t.webSharePage.requests, style: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(height: 10),
|
||||
if (webSendState.sessions.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 30),
|
||||
child: Text(t.webSharePage.noRequests),
|
||||
),
|
||||
...webSendState.sessions.entries.map((entry) {
|
||||
final session = entry.value;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
session.deviceInfo,
|
||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||
color: session.pending ? Theme.of(context).colorScheme.warning : null,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(session.ip, style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: Colors.grey)),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (session.pending) ...[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
ref.notifier(serverProvider).declineWebSendRequest(session.sessionId);
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
child: const Icon(Icons.close),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
ref.notifier(serverProvider).acceptWebSendRequest(session.sessionId);
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
child: const Icon(Icons.check_circle),
|
||||
),
|
||||
] else
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(
|
||||
t.general.accepted,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(session.ip, style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: Colors.grey)),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (session.pending) ...[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
ref.notifier(serverProvider).declineWebSendRequest(session.sessionId);
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
child: const Icon(Icons.close),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
ref.notifier(serverProvider).acceptWebSendRequest(session.sessionId);
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
child: const Icon(Icons.check_circle),
|
||||
),
|
||||
] else
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(
|
||||
t.general.accepted,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}),
|
||||
],
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
@@ -311,46 +343,51 @@ class _WebSendPageState extends State<WebSendPage> with Refena {
|
||||
Text(t.webSharePage.autoAccept, style: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(width: 10),
|
||||
Checkbox(
|
||||
value: webSendState.autoAccept,
|
||||
onChanged: (value) {
|
||||
ref.notifier(serverProvider).setWebSendAutoAccept(value == true);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(t.webSharePage.requirePin, style: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(width: 10),
|
||||
Checkbox(
|
||||
value: webSendState.pin != null,
|
||||
value: webSendState != null ? webSendState.autoAccept : settings.receiveViaLinkAutoAccept,
|
||||
onChanged: (value) async {
|
||||
final currentPIN = webSendState.pin;
|
||||
if (currentPIN != null) {
|
||||
await ref.notifier(serverProvider).setWebSendPin(null);
|
||||
if (webSendState != null) {
|
||||
ref.notifier(serverProvider).setWebSendAutoAccept(value == true);
|
||||
} else {
|
||||
final String? newPin = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (_) => const PinDialog(
|
||||
obscureText: false,
|
||||
generateRandom: true,
|
||||
),
|
||||
);
|
||||
|
||||
if (newPin != null && newPin.isNotEmpty) {
|
||||
await ref.notifier(serverProvider).setWebSendPin(newPin);
|
||||
}
|
||||
await ref.notifier(settingsProvider).setReceiveViaLinkAutoAccept(value == true);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
if (webSendState.pin != null) ...[
|
||||
Text(
|
||||
t.webSharePage.pinHint(pin: webSendState.pin!),
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: Theme.of(context).colorScheme.warning),
|
||||
if (webSendState != null) ...[
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(t.webSharePage.requirePin, style: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(width: 10),
|
||||
Checkbox(
|
||||
value: webSendState.pin != null,
|
||||
onChanged: (value) async {
|
||||
final currentPIN = webSendState.pin;
|
||||
if (currentPIN != null) {
|
||||
await ref.notifier(serverProvider).setWebSendPin(null);
|
||||
} else {
|
||||
final String? newPin = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (_) => const PinDialog(
|
||||
obscureText: false,
|
||||
generateRandom: true,
|
||||
),
|
||||
);
|
||||
|
||||
if (newPin != null && newPin.isNotEmpty) {
|
||||
await ref.notifier(serverProvider).setWebSendPin(newPin);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
if (webSendState.pin != null)
|
||||
Text(
|
||||
t.webSharePage.pinHint(pin: webSendState.pin!),
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: Theme.of(context).colorScheme.warning),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
Reference in New Issue
Block a user