// pages/login_page.dart import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; //import 'package:audioplayers/audioplayers.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; import 'package:just_audio/just_audio.dart'; import 'package:path_provider/path_provider.dart'; import 'package:MakerBook/pages/epub_page.dart'; import 'package:MakerBook/pages/qr_code_scanner.dart'; import 'package:MakerBook/utils/encrypt.dart'; import 'package:MakerBook/widgets/web_viewer.dart'; import 'package:MakerBook/utils/global.dart'; import '../widgets/animated_button.dart'; import 'pdf_page.dart'; import 'home_page.dart'; import 'package:crypto/crypto.dart'; class LoginPage extends StatefulWidget { const LoginPage({Key? key}) : super(key: key); @override _LoginPageState createState() => _LoginPageState(); } class _LoginPageState extends State { final TextEditingController _codeController = TextEditingController(); bool loading = false; Future _login() async { if (loading) return; if (_codeController.text.isNotEmpty) { setState(() => loading = true); final url = Uri.parse('$apiEndpoint/auth/loginBook'); final response = await http.post( url, headers: {'Content-Type': 'application/json'}, body: jsonEncode({'accesskey': _codeController.text}), ); if (response.statusCode == 200) { final data = jsonDecode(response.body)['results'][1][0]; if (data['success'] == 1) { final ext = data['ext']; final book_id = data['courseId']; final courseName = data['courseName']; final modifiedAt = data['modifiedAt']; final directory = await getApplicationDocumentsDirectory(); final file = File('${directory.path}/$book_id$ext'); await baixarOuAtualizarArquivo(book_id, bookPath, ext, modifiedAt); //final player = AudioPlayer(); //await player.play(AssetSource('sounds/open_book.mp3')); final player = AudioPlayer(); await player.setAsset('assets/sounds/open_book.mp3'); await player.play(); Navigator.push( context, MaterialPageRoute( builder: (context) => ext == ".pdf" ? PdfPage(pdfBytes: file.readAsBytesSync(), courseName: courseName, book_id: book_id,) : ext == ".epub" ? EpubPage(epubBytes: [], book_id: book_id, courseName: courseName) : ext == ".web" ? WebViewer(book_id: book_id.toString()) : const LoginPage(), ), ).then((_) async { final directory = await getTemporaryDirectory(); final tempFile = File('${directory.path}/${book_id}_temp$ext'); if (await tempFile.exists()) { await tempFile.delete(); } }); } else { _showSnackBar('Login falhou'); } } else { _showSnackBar('Erro na requisição'); } } else { _showSnackBar('Por favor, insira o código de acesso'); } setState(() => loading = false); } Future baixarOuAtualizarArquivo( int bookId, String bookPath, String ext, String? serverModifiedAtStr, ) async { final directory = await getApplicationDocumentsDirectory(); final file = File('${directory.path}/$bookId$ext'); final jsonFile = File('${directory.path}/$bookId.json'); print("[LOG] Diretorio Json: ${'${directory.path}/$bookId.json'}"); DateTime? serverModifiedAt = DateTime.tryParse(serverModifiedAtStr ?? ""); // Verifica se já existe o JSON com a modifiedAt local if (await jsonFile.exists()) { final jsonData = jsonDecode(await jsonFile.readAsString()); final localModifiedAt = DateTime.tryParse(jsonData['modifiedAt'] ?? ''); if (serverModifiedAt != null && localModifiedAt != null) { if (!serverModifiedAt.isAfter(localModifiedAt)) { print("[LOG] Arquivo já está atualizado (por modifiedAt)."); return; } else { print("[LOG] Arquivo modificado no servidor. Baixando..."); } } } // Baixa o arquivo final bookUrl = Uri.parse("$bookPath/course_$bookId$ext"); final resBook = await http.get(bookUrl); if (resBook.statusCode != 200) { _showSnackBar('[LOG] Falha ao carregar o livro'); return; } Uint8List conteudoProtegidoBytes; if (ext == ".epub") { String protegido = await protectFile(resBook.bodyBytes, null); conteudoProtegidoBytes = Uint8List.fromList(utf8.encode(protegido)); } else { conteudoProtegidoBytes = resBook.bodyBytes; } bool arquivoMudou = true; // Compara com arquivo local, se existir if (await file.exists()) { final localBytes = await file.readAsBytes(); final hashNovo = md5.convert(conteudoProtegidoBytes); final hashLocal = md5.convert(localBytes); if (hashNovo == hashLocal) { arquivoMudou = false; print("[LOG] Hashs iguais. Nada a fazer."); } else { print("[LOG] Arquivo realmente mudou. Substituindo."); } } else { print("[LOG] Arquivo ainda não existe. Salvando."); } if (arquivoMudou) { // Salva o arquivo EPUB await file.writeAsBytes(conteudoProtegidoBytes); } // Atualiza o JSON com nova modifiedAt, mantendo o lastLocation se já existir Map newJson = { "modifiedAt": serverModifiedAtStr, "lastLocation": null, }; if (await jsonFile.exists()) { final conteudo = await jsonFile.readAsString(); print("[LOG] Conteudo Json Original: ${conteudo}"); final oldJson = jsonDecode(conteudo); if (oldJson['lastLocation'] != null) { newJson['lastLocation'] = oldJson['lastLocation']; } } await jsonFile.writeAsString(jsonEncode(newJson)); print("[LOG] Arquivo e JSON atualizados."); } void _showSnackBar(String msg) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg))); } Future _scanQRCode() async { if (!loading) { final result = await Navigator.push( context, MaterialPageRoute( builder: (context) => const QRCodeScannerPage(), ), ); if (result != null) { setState(() { _codeController.text = result; }); } } } @override Widget build(BuildContext context) { final alturaTela = MediaQuery.of(context).size.height; return Scaffold( body: Stack( children: [ // Fundo Container( decoration: const BoxDecoration( image: DecorationImage( image: AssetImage('assets/images/background.png'), fit: BoxFit.cover, ), ), ), SafeArea( child: Stack( children: [ // Conteúdo com rolagem e centralização SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 16), child: ConstrainedBox( constraints: BoxConstraints(minHeight: alturaTela * 0.9), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const SizedBox(height: 40), Image.asset('assets/images/icon.png', width: 160), const SizedBox(height: 40), // Campo de código Container( width: 300, padding: const EdgeInsets.symmetric(horizontal: 20), decoration: BoxDecoration( border: Border.all(color: Colors.lightBlueAccent, width: 2), borderRadius: BorderRadius.circular(25), color: Colors.white, ), child: TextField( enabled: !loading, controller: _codeController, textAlign: TextAlign.center, style: GoogleFonts.fredoka( fontSize: 20, fontWeight: FontWeight.bold, ), decoration: const InputDecoration( hintText: 'DIGITE O CÓDIGO DO LIVRO', border: InputBorder.none, ), ), ), const SizedBox(height: 20), // Botão abrir livro com mãozinha Stack( clipBehavior: Clip.none, alignment: Alignment.center, children: [ AnimatedButton( onTap: loading ? () {} : _login, child: ElevatedButton( onPressed: _login, style: ElevatedButton.styleFrom( backgroundColor: Colors.orange, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(25), ), padding: const EdgeInsets.symmetric(horizontal: 60, vertical: 16), ), child: Text( loading ? 'ENTRANDO...' : 'ABRIR LIVRO', style: GoogleFonts.fredoka( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), Positioned( right: -30, bottom: -25, child: const AnimatedHand(size: 50), ) ], ), const SizedBox(height: 40), Text( 'PREFERE USAR A CÂMERA?', style: GoogleFonts.fredoka( fontSize: 19, fontWeight: FontWeight.bold, color: Colors.white, ), ), const SizedBox(height: 20), // Botão QR AnimatedButton( onTap: _scanQRCode, child: ElevatedButton.icon( onPressed: _scanQRCode, icon: const Icon(Icons.qr_code, color: Colors.black), label: Text( 'ESCANEAR QR CODE', style: GoogleFonts.fredoka( fontSize: 15, fontWeight: FontWeight.bold, color: Colors.black, ), ), style: ElevatedButton.styleFrom( backgroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(25), side: const BorderSide(color: Colors.lightBlueAccent, width: 2), ), padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 14), ), ), ), const SizedBox(height: 30), if (loading) const CircularProgressIndicator(), const SizedBox(height: 40), ], ), ), ), ), // 👉 Botão de voltar por cima Positioned( top: 10, left: 10, child: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () { Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => const HomePage()), ); }, ), ), ], ), ), ], ), ); } }