606 lines
21 KiB
Dart
606 lines
21 KiB
Dart
// widgets/pdf_viewer.dart
|
|
import 'dart:convert';
|
|
|
|
import 'package:MakerBook/secure_screen/ios_secure_screen.dart';
|
|
import 'package:MakerBook/utils/global.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_pdfview/flutter_pdfview.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import 'package:path_provider/path_provider.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'dart:io';
|
|
|
|
import 'package:tutorial_coach_mark/tutorial_coach_mark.dart';
|
|
|
|
class PdfViewer extends StatefulWidget {
|
|
final List<int> pdfBytes;
|
|
final String courseName;
|
|
final int book_id;
|
|
|
|
const PdfViewer({super.key, required this.pdfBytes, required this.courseName, required this.book_id});
|
|
|
|
@override
|
|
_PdfViewerState createState() => _PdfViewerState();
|
|
}
|
|
|
|
class _PdfViewerState extends State<PdfViewer> {
|
|
String? localPath;
|
|
PDFViewController? _controller;
|
|
int _totalPages = 0;
|
|
int _currentPage = 0;
|
|
String _courseName = "";
|
|
bool setaTutorial = false;
|
|
bool _mostrarControles = true;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
IosSecureScreen.enable(); // <- liga a “tela segura” no iOS
|
|
_loadPdf();
|
|
_verificaSeMostraTutorial();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_saveLastPage();
|
|
IosSecureScreen.enable(); // <- liga a “tela segura” no iOS
|
|
super.dispose();
|
|
}
|
|
|
|
|
|
Future<void> _loadPdf() async {
|
|
final tempDir = await getTemporaryDirectory();
|
|
final tempFile = File('${tempDir.path}/temp.pdf');
|
|
await tempFile.writeAsBytes(widget.pdfBytes, flush: true);
|
|
|
|
setState(() {
|
|
localPath = tempFile.path;
|
|
_courseName = widget.courseName;
|
|
});
|
|
}
|
|
|
|
Future<void> _saveLastPage() async {
|
|
try {
|
|
int page = await _controller!.getCurrentPage() ?? 1;
|
|
await saveLastLocation(widget.book_id, page.toString());
|
|
}
|
|
catch (ex) {
|
|
print("Erro ao salvar lastLocation no PDF");
|
|
}
|
|
}
|
|
|
|
void _goToPage(int page) {
|
|
if (_controller != null && page >= 0 && page < _totalPages) {
|
|
_controller!.setPage(page);
|
|
print("[LOG] Página do PDF definida para ${page}");
|
|
}
|
|
}
|
|
|
|
void _showPageInputDialog() {
|
|
final TextEditingController _pageController = TextEditingController();
|
|
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false, // Para manter o foco
|
|
builder: (context) {
|
|
return Dialog(
|
|
backgroundColor: const Color(0xFFFDF6E4),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: SizedBox(
|
|
width: 300,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
// Título com lupa
|
|
Stack(
|
|
children: [
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
decoration: const BoxDecoration(
|
|
color: Color(0xFF84D1F0),
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
'PESQUISA DETALHADA!',
|
|
style: GoogleFonts.fredoka(
|
|
fontSize: 17,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.black
|
|
),
|
|
),
|
|
),
|
|
),
|
|
Positioned(
|
|
top: 0,
|
|
right: 0,
|
|
child: Image.asset(
|
|
'assets/images/zoom.png',
|
|
width: 50,
|
|
height: 50,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
// Campo de texto
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
|
child: TextField(
|
|
controller: _pageController,
|
|
keyboardType: TextInputType.number,
|
|
textAlign: TextAlign.center,
|
|
style: GoogleFonts.fredoka(
|
|
fontSize: 17,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
decoration: InputDecoration(
|
|
hintText: 'NÚMERO DA PÁGINA',
|
|
hintStyle: GoogleFonts.fredoka(
|
|
fontSize: 17,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
contentPadding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(25),
|
|
borderSide: const BorderSide(color: Colors.lightBlueAccent, width: 2),
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(25),
|
|
borderSide: const BorderSide(color: Colors.blue, width: 2),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
// Botões
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
// Botão cancelar
|
|
ElevatedButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFFFFB1B1),
|
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
),
|
|
child: Text(
|
|
'CANCELAR',
|
|
style: GoogleFonts.fredoka(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.black
|
|
),
|
|
),
|
|
),
|
|
// Botão pesquisar
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
final int? page = int.tryParse(_pageController.text);
|
|
if (page != null && page > 0 && page <= _totalPages) {
|
|
_goToPage(page - 1);
|
|
}
|
|
Navigator.pop(context);
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFF94A38F),
|
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
),
|
|
child: Text(
|
|
'PESQUISAR',
|
|
style: GoogleFonts.fredoka(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.black
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
void _showAnnotationDialog() async {
|
|
final TextEditingController _noteController = TextEditingController();
|
|
final directory = await getApplicationDocumentsDirectory();
|
|
final file = File('${directory.path}/${widget.book_id}.json');
|
|
Map<String, dynamic> data = {};
|
|
int currentPage = _currentPage + 1;
|
|
|
|
// Carregar anotação atual
|
|
if (await file.exists()) {
|
|
data = jsonDecode(await file.readAsString());
|
|
final notes = data['notes'] ?? {};
|
|
if (notes.containsKey('$currentPage')) {
|
|
_noteController.text = notes['$currentPage'];
|
|
}
|
|
}
|
|
|
|
int numLinhas = 5;
|
|
double alturaLinha = 24;
|
|
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) {
|
|
return Dialog(
|
|
backgroundColor: const Color(0xFFFDF6E4),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
|
child: SizedBox(
|
|
width: 300,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
// Header
|
|
Stack(
|
|
children: [
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
decoration: const BoxDecoration(
|
|
color: Color(0xFFF79C5E),
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
'ÁREA DE ANOTAÇÕES',
|
|
style: GoogleFonts.fredoka(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
Positioned(
|
|
top: 2,
|
|
right: -0,
|
|
child: Image.asset(
|
|
'assets/images/lamp.png',
|
|
width: 50,
|
|
height: 50,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
child: Stack(
|
|
children: [
|
|
// Fundo com linhas estilo caderno
|
|
CustomPaint(
|
|
size: Size(double.infinity, numLinhas * alturaLinha), // 5 * 35 linhas
|
|
painter: CadernoPainter(lineCount: numLinhas, lineHeight: alturaLinha),
|
|
),
|
|
// Campo de texto por cima
|
|
TextField(
|
|
controller: _noteController,
|
|
keyboardType: TextInputType.multiline,
|
|
maxLines: numLinhas,
|
|
textAlign: TextAlign.center,
|
|
style: GoogleFonts.fredoka(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
decoration: const InputDecoration(
|
|
hintText: 'REGISTRE AQUI SUAS IDEIAS!',
|
|
hintStyle: TextStyle(fontWeight: FontWeight.bold),
|
|
border: InputBorder.none,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
|
|
const SizedBox(height: 10),
|
|
|
|
// Botões
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
ElevatedButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFFFFB1B1),
|
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
),
|
|
child: Text(
|
|
'FECHAR',
|
|
style: GoogleFonts.fredoka(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () async {
|
|
data['notes'] ??= {};
|
|
data['notes']['$currentPage'] = _noteController.text;
|
|
await file.writeAsString(jsonEncode(data));
|
|
Navigator.pop(context);
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFF94A38F),
|
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
),
|
|
child: Text(
|
|
'SALVAR',
|
|
style: GoogleFonts.fredoka(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
|
|
|
|
|
|
List<TargetFocus> targets = [];
|
|
GlobalKey lupaKey = GlobalKey();
|
|
GlobalKey anotacaoKey = GlobalKey();
|
|
GlobalKey rolagemKey = GlobalKey();
|
|
|
|
void _verificaSeMostraTutorial() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
bool jaViu = prefs.getBool('tutorialPdfVisto') ?? false;
|
|
//bool jaViu = false;
|
|
|
|
setState(() {
|
|
setaTutorial = !jaViu;
|
|
});
|
|
|
|
if (!jaViu) {
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
_mostrarTutorial();
|
|
});
|
|
prefs.setBool('tutorialPdfVisto', true);
|
|
}
|
|
}
|
|
|
|
void _mostrarTutorial() {
|
|
final targets = [
|
|
// Passo anotacao (sem alterações)
|
|
TargetFocus(
|
|
identify: "lupa",
|
|
keyTarget: lupaKey,
|
|
contents: [
|
|
TargetContent(
|
|
align: ContentAlign.bottom,
|
|
child: const Text(
|
|
"Use a lupa para realizar uma pesquisa detalhada!",
|
|
style: TextStyle(color: Colors.white, fontSize: 18),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
// Passo anotacao (sem alterações)
|
|
TargetFocus(
|
|
identify: "anotacao",
|
|
keyTarget: anotacaoKey,
|
|
contents: [
|
|
TargetContent(
|
|
align: ContentAlign.bottom,
|
|
child: const Text(
|
|
"Registre as suas anotações aqui!",
|
|
style: TextStyle(color: Colors.white, fontSize: 18),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
// Passo rolagem
|
|
TargetFocus(
|
|
identify: "rolagem",
|
|
keyTarget: rolagemKey,
|
|
contents: [
|
|
TargetContent(
|
|
align: ContentAlign.bottom,
|
|
child: const Text(
|
|
"Para visualizar o conteúdo, role a página!",
|
|
style: TextStyle(color: Colors.white, fontSize: 18),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
];
|
|
|
|
|
|
|
|
TutorialCoachMark(
|
|
targets: targets,
|
|
colorShadow: Colors.black,
|
|
textSkip: "Pular",
|
|
paddingFocus: 10,
|
|
opacityShadow: 0.8,
|
|
onFinish: () {
|
|
print("Tutorial concluído!");
|
|
setState(() {
|
|
setaTutorial = false;
|
|
});
|
|
},
|
|
).show(
|
|
context: context
|
|
);
|
|
|
|
}
|
|
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: _mostrarControles
|
|
? AppBar(
|
|
backgroundColor: Colors.blueAccent,
|
|
foregroundColor: Colors.black,
|
|
title: Text(_courseName),
|
|
actions: [
|
|
IconButton(
|
|
key: lupaKey,
|
|
icon: const Icon(Icons.find_in_page),
|
|
tooltip: 'Ir para página',
|
|
onPressed: _totalPages > 0 ? _showPageInputDialog : null,
|
|
),
|
|
IconButton(
|
|
key: anotacaoKey,
|
|
icon: const Icon(Icons.note_alt_outlined),
|
|
tooltip: 'Anotações',
|
|
onPressed: _showAnnotationDialog,
|
|
),
|
|
const SizedBox(width: 8),
|
|
],
|
|
)
|
|
: PreferredSize(preferredSize: Size.zero, child: SizedBox.shrink()),
|
|
body: localPath == null
|
|
? const Center(child: CircularProgressIndicator())
|
|
: setaTutorial
|
|
? Center(
|
|
child: Image.asset(
|
|
key: rolagemKey,
|
|
'assets/images/seta.png',
|
|
width: 80,
|
|
height: 80,
|
|
),
|
|
)
|
|
: GestureDetector(
|
|
onTap: () {
|
|
setState(() {
|
|
_mostrarControles = !_mostrarControles;
|
|
});
|
|
},
|
|
child: Stack(
|
|
children: [
|
|
PDFView(
|
|
filePath: localPath!,
|
|
onRender: (_pages) {
|
|
setState(() {
|
|
_totalPages = _pages ?? 0;
|
|
});
|
|
},
|
|
onViewCreated: (controller) async {
|
|
_controller = controller;
|
|
await Future.delayed(const Duration(milliseconds: 500));
|
|
_currentPage = int.parse(await getLastLocation(widget.book_id) ?? "1");
|
|
_goToPage(_currentPage);
|
|
},
|
|
onPageChanged: (page, total) {
|
|
setState(() {
|
|
_currentPage = page ?? 0;
|
|
});
|
|
},
|
|
),
|
|
|
|
// 🔸 Detector de toque invisível cobrindo tudo
|
|
Positioned.fill(
|
|
child: GestureDetector(
|
|
behavior: HitTestBehavior.translucent,
|
|
onTap: () {
|
|
setState(() {
|
|
_mostrarControles = !_mostrarControles;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
|
|
// 🔸 Controles inferiores (visíveis só se ativo)
|
|
if (_mostrarControles)
|
|
Positioned(
|
|
bottom: 16,
|
|
left: 16,
|
|
right: 16,
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
|
decoration: BoxDecoration(
|
|
color: Colors.black.withOpacity(0.6),
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
IconButton(
|
|
icon: const Icon(Icons.chevron_left, color: Colors.white),
|
|
onPressed: _currentPage > 0 ? () => _goToPage(_currentPage - 1) : null,
|
|
),
|
|
Text(
|
|
'Página ${_currentPage + 1} de $_totalPages',
|
|
style: const TextStyle(color: Colors.white),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.chevron_right, color: Colors.white),
|
|
onPressed: _currentPage < _totalPages - 1 ? () => _goToPage(_currentPage + 1) : null,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
)
|
|
),
|
|
);
|
|
|
|
}
|
|
}
|
|
|
|
class CadernoPainter extends CustomPainter {
|
|
final int lineCount;
|
|
final double lineHeight;
|
|
|
|
CadernoPainter({this.lineCount = 6, this.lineHeight = 45});
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
final paint = Paint()
|
|
..color = Colors.black26
|
|
..strokeWidth = 1;
|
|
|
|
for (int i = 1; i <= lineCount; i++) {
|
|
final y = i * lineHeight;
|
|
canvas.drawLine(Offset(0, y), Offset(size.width, y), paint);
|
|
}
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(CustomPainter oldDelegate) => false;
|
|
}
|