second success :D commit

This commit is contained in:
2025-10-10 23:32:24 -04:00
parent 06b59a3a99
commit 4ae53e264b
192 changed files with 2178 additions and 6815 deletions
+154 -16
View File
@@ -11,7 +11,7 @@ import 'package:http_parser/http_parser.dart';
// IMPORTANT: Change this to your computer's IP address for testing on real device
// For emulator, you can use 'http://10.0.2.2:3030' (Android) or 'http://localhost:3030' (iOS)
const String SERVER_URL = 'http://localhost:3030';
const String SERVER_URL = 'http://localhost:3000';
void main() {
runApp(MangaShelfApp());
@@ -260,6 +260,7 @@ class HomeScreen extends StatefulWidget {
}
class _HomeScreenState extends State<HomeScreen> {
String status = 'No file selected';
List<dynamic> _mangaList = [];
bool _isLoading = true;
@@ -307,27 +308,38 @@ class _HomeScreenState extends State<HomeScreen> {
);
}
Future<void> _uploadManga() async {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['cbz', 'cbr', 'zip', 'rar'],
allowMultiple: false,
);
Future<void> _uploadManga() async {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['cbz', 'cbr', 'zip', 'rar'],
);
if (result != null) {
String filePath = result.files.single.path!;
PlatformFile file = result.files.single;
// Show upload dialog
if (result != null) {
// For web, use bytes instead of path
final bytes = result.files.single.bytes;
final fileName = result.files.single.name;
if (bytes != null) {
showDialog(
context: context,
builder: (BuildContext context) {
return UploadDialog(filePath: filePath, fileName: file.name);
},
builder: (context) => UploadDialogWeb(
fileBytes: bytes,
fileName: fileName,
),
).then((_) {
loadLibrary();
});
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error reading file')),
);
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('No file selected')),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -577,6 +589,132 @@ class _UploadDialogState extends State<UploadDialog> {
}
}
// ==================== UPLOAD DIALOG FOR WEB ====================
class UploadDialogWeb extends StatefulWidget {
final Uint8List fileBytes;
final String fileName;
UploadDialogWeb({required this.fileBytes, required this.fileName});
@override
_UploadDialogWebState createState() => _UploadDialogWebState();
}
class _UploadDialogWebState extends State<UploadDialogWeb> {
final _formKey = GlobalKey<FormState>();
final _titleController = TextEditingController();
final _authorController = TextEditingController();
final _descriptionController = TextEditingController();
bool _isUploading = false;
@override
void initState() {
super.initState();
String fileNameWithoutExt = widget.fileName.split('.').first;
_titleController.text = fileNameWithoutExt;
}
Future<void> _uploadManga() async {
if (_formKey.currentState!.validate()) {
setState(() => _isUploading = true);
try {
final prefs = await SharedPreferences.getInstance();
final token = prefs.getString('auth_token');
var request = http.MultipartRequest('POST', Uri.parse('$SERVER_URL/api/upload'));
request.headers['Authorization'] = 'Bearer $token';
var multipartFile = http.MultipartFile.fromBytes(
'comic',
widget.fileBytes,
filename: widget.fileName,
contentType: MediaType('application', 'octet-stream'),
);
request.files.add(multipartFile);
request.fields['title'] = _titleController.text;
request.fields['author'] = _authorController.text;
request.fields['description'] = _descriptionController.text;
var response = await request.send();
var responseBody = await response.stream.bytesToString();
var result = json.decode(responseBody);
if (response.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Upload successful!')),
);
Navigator.of(context).pop();
} else {
throw Exception(result['error'] ?? 'Upload failed');
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Upload failed: $e')),
);
} finally {
setState(() => _isUploading = false);
}
}
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Upload Comic'),
content: Container(
width: double.maxFinite,
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
controller: _titleController,
decoration: InputDecoration(
labelText: 'Title',
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
),
validator: (value) => value?.isEmpty ?? true ? 'Please enter a title' : null,
),
SizedBox(height: 12),
TextFormField(
controller: _authorController,
decoration: InputDecoration(
labelText: 'Author',
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
),
),
SizedBox(height: 12),
TextFormField(
controller: _descriptionController,
maxLines: 3,
decoration: InputDecoration(
labelText: 'Description',
alignLabelWithHint: true,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
),
),
SizedBox(height: 16),
Text('File: ${widget.fileName}', style: TextStyle(fontSize: 12, color: Colors.grey)),
],
),
),
),
actions: [
TextButton(onPressed: () => Navigator.of(context).pop(), child: Text('Cancel')),
ElevatedButton(
onPressed: _isUploading ? null : _uploadManga,
style: ElevatedButton.styleFrom(backgroundColor: Colors.purple),
child: _isUploading
? SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, valueColor: AlwaysStoppedAnimation<Color>(Colors.white)))
: Text('Upload'),
),
],
);
}
}
// ==================== MANGA CARD WIDGET ====================
class MangaCard extends StatelessWidget {
final Map<String, dynamic> manga;