363 lines
13 KiB
Dart
363 lines
13 KiB
Dart
// 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<LoginPage> {
|
|
final TextEditingController _codeController = TextEditingController();
|
|
bool loading = false;
|
|
|
|
Future<void> _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<void> 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<String, dynamic> 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<void> _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()),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
} |