feat: do not show hashtag

This commit is contained in:
Tien Do Nam
2026-08-05 18:10:03 +02:00
parent a075ce9f8c
commit babe67a12e
3 changed files with 29 additions and 103 deletions
+1 -2
View File
@@ -9,7 +9,6 @@ import 'package:localsend_app/provider/animation_provider.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/ip_helper.dart';
import 'package:localsend_app/widget/animations/initial_fade_transition.dart';
import 'package:localsend_app/widget/column_list_view.dart';
import 'package:localsend_app/widget/custom_icon_button.dart';
@@ -94,7 +93,7 @@ class _ReceiveTabState extends State<ReceiveTab> {
duration: const Duration(milliseconds: 300),
delay: const Duration(milliseconds: 500),
child: Text(
serverState == null ? t.general.offline : localIps.map((ip) => '#${ip.visualId}').toSet().join(' '),
serverState == null ? t.general.offline : t.general.online,
style: const TextStyle(fontSize: 24),
textAlign: TextAlign.center,
),
-4
View File
@@ -1,9 +1,5 @@
import 'package:collection/collection.dart';
extension StringIpExt on String {
String get visualId => split('.').last;
}
class IpHelper {
/// Sorts Ip addresses with first being the most likely primary local address
/// Currently,
@@ -17,20 +17,7 @@ import 'package:localsend_isolates/util/rust.dart';
import 'package:refena_flutter/refena_flutter.dart';
import 'package:routerino/routerino.dart';
enum _InputMode {
hashtag,
ip
;
String get label {
return switch (this) {
_InputMode.hashtag => t.dialogs.addressInput.hashtag,
_InputMode.ip => t.dialogs.addressInput.ip,
};
}
}
/// A dialog to input an hash or address.
/// A dialog to input an address.
/// Pops the dialog with the device if found.
class AddressInputDialog extends StatefulWidget {
const AddressInputDialog();
@@ -40,22 +27,12 @@ class AddressInputDialog extends StatefulWidget {
}
class _AddressInputDialogState extends State<AddressInputDialog> with Refena {
final _selected = List.generate(_InputMode.values.length, (index) => index == 0);
_InputMode _mode = _InputMode.hashtag;
String _input = '';
bool _fetching = false;
String? _error;
Future<void> _submit(List<String> localIps, int port, [String? candidate]) async {
final List<String> candidates;
final String input = _input.trim();
if (candidate != null) {
candidates = [candidate];
} else if (_mode == _InputMode.ip) {
candidates = [input];
} else {
candidates = localIps.map((ip) => '${ip.ipPrefix}.$input').toList();
}
Future<void> _submit(int port, [String? candidate]) async {
final candidates = [candidate ?? _input.trim()];
setState(() {
_fetching = true;
@@ -129,89 +106,43 @@ class _AddressInputDialogState extends State<AddressInputDialog> with Refena {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ToggleButtons(
isSelected: _selected,
onPressed: (int index) {
setState(() {
for (int i = 0; i < _selected.length; i++) {
_selected[i] = i == index;
}
_mode = _InputMode.values[index];
});
},
borderRadius: BorderRadius.circular(10),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
constraints: const BoxConstraints(minWidth: 0, minHeight: 0),
children: _InputMode.values.map((mode) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
child: Text(mode.label),
);
}).toList(),
),
const SizedBox(height: 15),
TextFormField(
key: ValueKey('input-$_mode'),
autofocus: true,
enabled: !_fetching,
keyboardType: _mode == _InputMode.hashtag ? TextInputType.number : TextInputType.text,
decoration: InputDecoration(
prefixText: _mode == _InputMode.hashtag ? '# ' : 'IP: ',
prefixText: 'IP: ',
),
onChanged: (s) {
setState(() => _input = s);
},
onFieldSubmitted: (s) async => _submit(localIps, settings.port),
onFieldSubmitted: (s) async => _submit(settings.port),
),
const SizedBox(height: 10),
if (_mode == _InputMode.hashtag) ...[
if (lastDevices.isEmpty)
Text(
'${t.general.example}: 123',
'${t.general.example}: ${localIps.firstOrNull?.ipPrefix ?? '192.168.2'}.123',
style: const TextStyle(color: Colors.grey),
)
else
Text.rich(
TextSpan(
children: [
TextSpan(text: t.dialogs.addressInput.recentlyUsed),
...lastDevices
.mapIndexed((index, device) {
return [
if (index != 0) const TextSpan(text: ', '),
TextSpan(
text: device.ip,
style: TextStyle(color: Theme.of(context).colorScheme.primary),
recognizer: TapGestureRecognizer()..onTap = () async => _submit(settings.port, device.ip),
),
];
})
.expand((e) => e),
],
),
),
if (localIps.length <= 1)
Text(
'${t.dialogs.addressInput.ip}: ${localIps.firstOrNull?.ipPrefix ?? '192.168.2'}.$_input',
style: const TextStyle(color: Colors.grey),
)
else ...[
Text(
'${t.dialogs.addressInput.ip}:',
style: const TextStyle(color: Colors.grey),
),
for (final ip in localIps)
Text(
'- ${ip.ipPrefix}.$_input',
style: const TextStyle(color: Colors.grey),
),
],
] else ...[
if (lastDevices.isEmpty)
Text(
'${t.general.example}: ${localIps.firstOrNull?.ipPrefix ?? '192.168.2'}.123',
style: const TextStyle(color: Colors.grey),
)
else
Text.rich(
TextSpan(
children: [
TextSpan(text: t.dialogs.addressInput.recentlyUsed),
...lastDevices
.mapIndexed((index, device) {
return [
if (index != 0) const TextSpan(text: ', '),
TextSpan(
text: device.ip,
style: TextStyle(color: Theme.of(context).colorScheme.primary),
recognizer: TapGestureRecognizer()..onTap = () async => _submit(localIps, settings.port, device.ip),
),
];
})
.expand((e) => e),
],
),
),
],
if (_error != null)
Padding(
padding: const EdgeInsets.only(top: 10),
@@ -244,7 +175,7 @@ class _AddressInputDialogState extends State<AddressInputDialog> with Refena {
child: Text(t.general.cancel),
),
FilledButton(
onPressed: _fetching ? null : () async => _submit(localIps, settings.port),
onPressed: _fetching ? null : () async => _submit(settings.port),
child: Text(t.general.confirm),
),
],