feat: better ui

This commit is contained in:
Gringo
2026-03-01 17:54:50 +01:00
parent 54ce0b03fc
commit 9069e3fe36
6 changed files with 328 additions and 48 deletions
+69 -48
View File
@@ -1,6 +1,7 @@
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:file_transfer/models/file_info.dart';
import 'package:file_transfer/routes.dart';
import 'package:file_transfer/utils/platform_helper.dart';
import 'package:file_transfer_sdk/file_transfer_sdk.dart';
@@ -14,12 +15,14 @@ import 'package:toastification/toastification.dart';
class HomePageController extends GetxController {
final _sharedFile = Rxn<SharedFile>();
final _fileInfo = Rxn<FileInfo>();
final _isUploading = false.obs;
final _error = RxnString();
final _isDragging = false.obs;
final dropzoneController = Rxn<DropzoneViewController>();
SharedFile? get sharedFile => _sharedFile.value;
FileInfo? get fileInfo => _fileInfo.value;
bool get isUploading => _isUploading.value;
String? get error => _error.value;
bool get isDragging => _isDragging.value;
@@ -38,11 +41,44 @@ class HomePageController extends GetxController {
);
}
Future<void> handleDroppedFile(dynamic file) async {
_isDragging.value = false;
Future<void> _uploadFile(Uint8List bytes, String filename) async {
_isUploading.value = true;
_error.value = null;
try {
final mimeType = lookupMimeType(
filename,
headerBytes: bytes.take(8).toList(),
);
final sharedFile = await shareFile(
ndk: Get.find(),
bytes: bytes,
contentType: mimeType,
filename: filename,
);
_sharedFile.value = sharedFile;
_fileInfo.value = null;
} catch (e) {
_error.value = e.toString();
} finally {
_isUploading.value = false;
}
}
void _setFileInfo(Uint8List bytes, String filename, String? mimeType) {
_fileInfo.value = FileInfo(
name: filename,
size: bytes.length,
mimeType: mimeType,
bytes: bytes,
);
}
Future<void> handleDroppedFile(dynamic file) async {
_isDragging.value = false;
try {
String filename;
Uint8List bytes;
@@ -66,15 +102,7 @@ class HomePageController extends GetxController {
headerBytes: bytes.take(8).toList(),
);
final sharedFile = await shareFile(
ndk: Get.find(),
bytes: bytes,
contentType: mimeType,
filename: filename,
);
_sharedFile.value = sharedFile;
_isUploading.value = false;
_setFileInfo(bytes, filename, mimeType);
} catch (e) {
_error.value = e.toString();
_isUploading.value = false;
@@ -90,45 +118,37 @@ class HomePageController extends GetxController {
}
Future<void> pickAndShareFile() async {
_isUploading.value = true;
_error.value = null;
final pickResult = await FilePicker.platform.pickFiles(
type: FileType.any,
withData: true,
);
try {
final pickResult = await FilePicker.platform.pickFiles(
type: FileType.any,
withData: true,
);
if (pickResult == null || pickResult.files.isEmpty) {
_isUploading.value = false;
return;
}
final file = pickResult.files.first;
if (file.bytes == null) {
_error.value = 'Unable to read file data';
_isUploading.value = false;
return;
}
final mimeType = lookupMimeType(
file.name,
headerBytes: file.bytes?.take(8).toList(),
);
final sharedFile = await shareFile(
ndk: Get.find(),
bytes: file.bytes!,
contentType: mimeType,
filename: file.name,
);
_sharedFile.value = sharedFile;
_isUploading.value = false;
} catch (e) {
_error.value = e.toString();
_isUploading.value = false;
if (pickResult == null || pickResult.files.isEmpty) {
return;
}
final file = pickResult.files.first;
if (file.bytes == null) {
_error.value = 'Unable to read file data';
return;
}
final mimeType = lookupMimeType(
file.name,
headerBytes: file.bytes?.take(8).toList(),
);
_setFileInfo(file.bytes!, file.name, mimeType);
}
Future<void> startUpload() async {
final fileInfo = _fileInfo.value;
if (fileInfo == null) {
_error.value = 'No file selected';
return;
}
await _uploadFile(fileInfo.bytes, fileInfo.name);
}
void copyToClipboard(String text, String label) {
@@ -147,6 +167,7 @@ class HomePageController extends GetxController {
void reset() {
_sharedFile.value = null;
_fileInfo.value = null;
_error.value = null;
}
+17
View File
@@ -0,0 +1,17 @@
import 'package:flutter/foundation.dart';
class FileInfo {
final String name;
final int size;
final String? mimeType;
final DateTime? lastModified;
final Uint8List bytes;
FileInfo({
required this.name,
required this.size,
this.mimeType,
this.lastModified,
required this.bytes,
});
}
@@ -83,7 +83,35 @@ class ReadyView extends GetView<FileShareController> {
child: Padding(
padding: EdgeInsets.all(isSmallScreen ? 12 : 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (metadata.filename != null) ...[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Name:',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: isSmallScreen ? 13 : 14,
),
),
Flexible(
child: Text(
metadata.filename!,
textAlign: TextAlign.right,
style: TextStyle(
fontSize: isSmallScreen ? 13 : 14,
color: colorScheme.onSurfaceVariant,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
],
),
const Divider(),
],
if (metadata.fileType != null) ...[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
+210
View File
@@ -0,0 +1,210 @@
import 'package:file_transfer/controllers/home_controller.dart';
import 'package:file_transfer/models/file_info.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class FileInfoView extends GetView<HomePageController> {
const FileInfoView({super.key});
@override
Widget build(BuildContext context) {
final fileInfo = controller.fileInfo;
final isSmallScreen = MediaQuery.of(context).size.width < 600;
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
appBar: AppBar(
title: const Text('File Ready to Upload'),
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: controller.reset,
),
),
body: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: isSmallScreen ? 16 : 24),
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 500),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(height: MediaQuery.of(context).padding.top),
Icon(
Icons.insert_drive_file_rounded,
size: isSmallScreen ? 64 : 80,
color: colorScheme.primary,
),
SizedBox(height: isSmallScreen ? 20 : 24),
Text(
'File selected',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: isSmallScreen ? 16 : 18,
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
),
),
SizedBox(height: isSmallScreen ? 16 : 24),
_buildFileInfoCard(
context,
isSmallScreen,
fileInfo!,
colorScheme,
),
SizedBox(height: isSmallScreen ? 24 : 32),
Obx(
() => FilledButton.icon(
onPressed: controller.isUploading
? null
: () => controller.startUpload(),
icon: controller.isUploading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: const Icon(Icons.cloud_upload),
label: Text(
controller.isUploading ? 'Uploading...' : 'Upload File',
),
),
),
SizedBox(height: isSmallScreen ? 6 : 8),
OutlinedButton.icon(
onPressed: controller.reset,
icon: const Icon(Icons.refresh),
label: const Text('Select Different File'),
),
Obx(() {
if (controller.error != null) {
return _buildErrorBox(context, isSmallScreen, colorScheme);
}
return const SizedBox.shrink();
}),
],
),
),
),
),
);
}
Widget _buildErrorBox(
BuildContext context,
bool isSmallScreen,
ColorScheme colorScheme,
) {
return Container(
padding: EdgeInsets.all(isSmallScreen ? 10 : 12),
decoration: BoxDecoration(
color: colorScheme.errorContainer,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(Icons.error_outline, color: colorScheme.onErrorContainer),
SizedBox(width: isSmallScreen ? 8 : 12),
Expanded(
child: Text(
controller.error!,
style: TextStyle(
color: colorScheme.onErrorContainer,
fontSize: isSmallScreen ? 12 : 14,
),
),
),
],
),
);
}
Widget _buildFileInfoCard(
BuildContext context,
bool isSmallScreen,
FileInfo fileInfo,
ColorScheme colorScheme,
) {
return Card(
child: Padding(
padding: EdgeInsets.all(isSmallScreen ? 12 : 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoRow('Name:', fileInfo.name, isSmallScreen, colorScheme),
SizedBox(height: isSmallScreen ? 12 : 16),
_buildInfoRow(
'Size:',
_formatFileSize(fileInfo.size),
isSmallScreen,
colorScheme,
),
SizedBox(height: isSmallScreen ? 12 : 16),
_buildInfoRow(
'Type:',
fileInfo.mimeType ?? 'Unknown',
isSmallScreen,
colorScheme,
),
if (fileInfo.lastModified != null) ...[
SizedBox(height: isSmallScreen ? 12 : 16),
_buildInfoRow(
'Modified:',
_formatDate(fileInfo.lastModified!),
isSmallScreen,
colorScheme,
),
],
],
),
),
);
}
Widget _buildInfoRow(
String label,
String value,
bool isSmallScreen,
ColorScheme colorScheme,
) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: isSmallScreen ? 13 : 14,
),
),
SizedBox(height: isSmallScreen ? 6 : 8),
Text(
value,
style: TextStyle(
fontSize: isSmallScreen ? 11 : 12,
fontFamily: 'monospace',
color: colorScheme.onSurfaceVariant,
),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
],
);
}
String _formatFileSize(int bytes) {
if (bytes < 1024) return '$bytes B';
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
if (bytes < 1024 * 1024 * 1024) {
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
}
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB';
}
String _formatDate(DateTime date) {
return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')} '
'${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}:${date.second.toString().padLeft(2, '0')}';
}
}
+1
View File
@@ -1,2 +1,3 @@
export 'upload_view.dart';
export 'share_link_view.dart';
export 'file_info_view.dart';
+3
View File
@@ -12,6 +12,9 @@ class HomePage extends GetView<HomePageController> {
if (controller.sharedFile != null) {
return const ShareLinkView();
}
if (controller.fileInfo != null) {
return const FileInfoView();
}
return const UploadView();
});
}