second success :D commit
This commit is contained in:
+154
-16
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user