ajustes de captura de tela ios e android

This commit is contained in:
João Pedro Oliveira 2026-01-06 15:19:11 -03:00
parent bebed14863
commit d9893fa59e
30 changed files with 321 additions and 254 deletions

View File

0
books_viewer/android/Get Normal file
View File

View File

0
books_viewer/android/Run Normal file
View File

View File

@ -31,12 +31,17 @@ if (flutterVersionName == null) {
android { android {
namespace = "com.myrobot.MakerBook" namespace = "com.myrobot.MakerBook"
compileSdk = 34 // flutter.compileSdkVersion compileSdk = 36 // flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion ndkVersion = flutter.ndkVersion
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_17
coreLibraryDesugaringEnabled true
}
kotlinOptions {
jvmTarget = "17"
} }
defaultConfig { defaultConfig {
@ -45,7 +50,7 @@ android {
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdk = flutter.minSdkVersion minSdk = flutter.minSdkVersion
targetSdk = 34 //flutter.targetSdkVersion targetSdk = 36 //flutter.targetSdkVersion
versionCode = flutterVersionCode.toInteger() versionCode = flutterVersionCode.toInteger()
versionName = flutterVersionName versionName = flutterVersionName
} }
@ -74,6 +79,10 @@ android {
} }
dependencies {
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.0.4"
}
flutter { flutter {
source = "../.." source = "../.."
} }

View File

@ -3,16 +3,43 @@ allprojects {
google() google()
mavenCentral() mavenCentral()
} }
tasks.withType(JavaCompile).configureEach {
sourceCompatibility = JavaVersion.VERSION_17.toString()
targetCompatibility = JavaVersion.VERSION_17.toString()
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions {
jvmTarget = "17"
}
}
} }
rootProject.buildDir = "../build" rootProject.buildDir = "../build"
subprojects { subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}" project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(":app") project.evaluationDependsOn(":app")
} }
subprojects { p ->
// Força especificamente o plugin qr_code_scanner
if (p.name == "qr_code_scanner") {
p.tasks.withType(JavaCompile).configureEach {
sourceCompatibility = JavaVersion.VERSION_17.toString()
targetCompatibility = JavaVersion.VERSION_17.toString()
}
p.tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions { jvmTarget = "17" }
}
}
}
tasks.register("clean", Delete) { tasks.register("clean", Delete) {
delete rootProject.buildDir delete rootProject.buildDir
} }
buildscript {
ext.kotlin_version = '2.0.20'
}

View File

@ -1,3 +1,4 @@
org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
org.gradle.jvmargs=-Xmx4096m
kotlin.jvm.target=17

View File

@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip

View File

@ -18,8 +18,9 @@ pluginManagement {
plugins { plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.3.0" apply false id "com.android.application" version "8.6.0" apply false
id "org.jetbrains.kotlin.android" version "1.7.10" apply false id "com.android.library" version "8.6.0" apply false
id "org.jetbrains.kotlin.android" version "2.1.0" apply false
} }
include ":app" include ":app"

View File

@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project # Uncomment this line to define a global platform for your project
# platform :ios, '13.0' platform :ios, '15.5'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View File

@ -198,6 +198,7 @@
9705A1C41CF9048500538489 /* Embed Frameworks */, 9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
AE54976BBDDFFE7ECF874480 /* [CP] Embed Pods Frameworks */, AE54976BBDDFFE7ECF874480 /* [CP] Embed Pods Frameworks */,
851D672B5FE59B27299B483A /* [CP] Copy Pods Resources */,
); );
buildRules = ( buildRules = (
); );
@ -329,6 +330,23 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
851D672B5FE59B27299B483A /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = { 9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1; alwaysOutOfDate = 1;

View File

@ -3,11 +3,91 @@ import UIKit
@main @main
@objc class AppDelegate: FlutterAppDelegate { @objc class AppDelegate: FlutterAppDelegate {
private var secureField: UITextField?
private var secureContainer: UIView?
override func application( override func application(
_ application: UIApplication, _ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool { ) -> Bool {
let controller = window?.rootViewController as! FlutterViewController
let channel = FlutterMethodChannel(name: "ios_secure_screen", binaryMessenger: controller.binaryMessenger)
channel.setMethodCallHandler { [weak self] call, result in
guard let self = self else { return }
switch call.method {
case "enable":
self.enableSecureMode()
result(nil)
case "disable":
self.disableSecureMode()
result(nil)
default:
result(FlutterMethodNotImplemented)
}
}
GeneratedPluginRegistrant.register(with: self) GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions) return super.application(application, didFinishLaunchingWithOptions: launchOptions)
} }
private func enableSecureMode() {
guard let window = self.window else { return }
guard let flutterVC = window.rootViewController else { return }
let flutterView = flutterVC.view!
if secureContainer != nil { return }
let field = UITextField(frame: window.bounds)
field.isSecureTextEntry = true
// IMPORTANTE: precisa ficar interativo, senão mata os toques do FlutterView
field.isUserInteractionEnabled = true
field.isEnabled = true
field.textColor = .clear
field.tintColor = .clear
field.backgroundColor = .clear
field.borderStyle = .none
field.autocorrectionType = .no
field.spellCheckingType = .no
guard let container = field.subviews.first else { return }
container.frame = window.bounds
container.isUserInteractionEnabled = true
flutterView.removeFromSuperview()
container.addSubview(flutterView)
flutterView.frame = container.bounds
flutterView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
window.addSubview(field)
field.frame = window.bounds
field.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.secureField = field
self.secureContainer = container
}
private func disableSecureMode() {
guard let window = self.window else { return }
guard let flutterVC = window.rootViewController else { return }
let flutterView = flutterVC.view!
// Se não está habilitado, sai
guard let field = secureField else { return }
// Remove flutter do container e volta pra window
flutterView.removeFromSuperview()
window.insertSubview(flutterView, belowSubview: field)
flutterView.frame = window.bounds
flutterView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
// Remove o campo seguro
field.removeFromSuperview()
self.secureField = nil
self.secureContainer = nil
}
} }

View File

@ -1,8 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<scenes> <scenes>
<!--Flutter View Controller--> <!--Flutter View Controller-->
@ -14,13 +16,14 @@
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/> <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides> </layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC"> <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/> <rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/> <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view> </view>
</viewController> </viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="-16" y="-40"/>
</scene> </scene>
</scenes> </scenes>
</document> </document>

View File

@ -47,5 +47,7 @@
<true/> <true/>
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>
<string>Usamos a câmera para ler QR Code e permitir o login seguro no Maker Book.</string> <string>Usamos a câmera para ler QR Code e permitir o login seguro no Maker Book.</string>
<key>NSMicrophoneUsageDescription</key>
<string>O Maker Book não grava áudio. Esta permissão pode ser exibida pelo iOS por causa de bibliotecas de reprodução de áudio usadas no app.</string>
</dict> </dict>
</plist> </plist>

View File

@ -1,22 +1,54 @@
import 'dart:io'; //import 'package:MakerBook/secure_screen/screenshot_guard.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_windowmanager/flutter_windowmanager.dart'; import 'package:screen_protector/screen_protector.dart';
import 'pages/home_page.dart'; import 'pages/home_page.dart';
Future<void> main() async { Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
// Android tem esse plugin // Android FLAG_SECURE
if (Platform.isAndroid) { // iOS blur no app switcher + bloqueio de screen recording
await FlutterWindowManager.addFlags(FlutterWindowManager.FLAG_SECURE); await ScreenProtector.preventScreenshotOn();
} await ScreenProtector.protectDataLeakageOn();
runApp(const MyApp()); runApp(const MyApp());
} }
class MyApp extends StatelessWidget { class MyApp extends StatefulWidget {
const MyApp({super.key}); const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) async {
// iOS: protege quando vai para background / app switcher
if (state == AppLifecycleState.paused ||
state == AppLifecycleState.inactive) {
await ScreenProtector.protectDataLeakageOn();
}
// remove blur ao voltar
if (state == AppLifecycleState.resumed) {
await ScreenProtector.protectDataLeakageOff();
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(

View File

@ -2,10 +2,11 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:audioplayers/audioplayers.dart'; //import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:just_audio/just_audio.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:MakerBook/pages/epub_page.dart'; import 'package:MakerBook/pages/epub_page.dart';
import 'package:MakerBook/pages/qr_code_scanner.dart'; import 'package:MakerBook/pages/qr_code_scanner.dart';
@ -54,8 +55,11 @@ class _LoginPageState extends State<LoginPage> {
await baixarOuAtualizarArquivo(book_id, bookPath, ext, modifiedAt); await baixarOuAtualizarArquivo(book_id, bookPath, ext, modifiedAt);
//final player = AudioPlayer();
//await player.play(AssetSource('sounds/open_book.mp3'));
final player = AudioPlayer(); final player = AudioPlayer();
await player.play(AssetSource('sounds/open_book.mp3')); await player.setAsset('assets/sounds/open_book.mp3');
await player.play();
Navigator.push( Navigator.push(
context, context,

View File

@ -1,4 +1,3 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter/webview_flutter.dart';
@ -10,17 +9,27 @@ class PrivacyPage extends StatefulWidget {
} }
class _PrivacyPageState extends State<PrivacyPage> { class _PrivacyPageState extends State<PrivacyPage> {
late final WebViewController _controller;
bool _loading = true; bool _loading = true;
//late WebViewController _controller;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// Necessário para Android quando usando WebView em SDKs antigos _controller = WebViewController()
if (Platform.isAndroid) { ..setJavaScriptMode(JavaScriptMode.unrestricted)
WebView.platform = SurfaceAndroidWebView(); ..setNavigationDelegate(
} NavigationDelegate(
onPageFinished: (_) {
setState(() => _loading = false);
},
onWebResourceError: (error) {
// Opcional: se quiser mostrar algo quando falhar
// print('WebView error: ${error.description}');
},
),
)
..loadRequest(Uri.parse('https://makereducacional.com.br/privacy.html'));
} }
@override @override
@ -34,22 +43,9 @@ class _PrivacyPageState extends State<PrivacyPage> {
), ),
body: Stack( body: Stack(
children: [ children: [
WebView( WebViewWidget(controller: _controller),
initialUrl: 'https://makereducacional.com.br/privacy.html',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (controller) {
//_controller = controller;
},
onPageFinished: (_) {
setState(() {
_loading = false;
});
},
),
if (_loading) if (_loading)
const Center( const Center(child: CircularProgressIndicator()),
child: CircularProgressIndicator(),
),
], ],
), ),
); );

View File

@ -1,62 +1,53 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart'; import 'package:mobile_scanner/mobile_scanner.dart';
class QRCodeScannerPage extends StatefulWidget { class QRCodeScannerPage extends StatefulWidget {
const QRCodeScannerPage({Key? key}) : super(key: key); const QRCodeScannerPage({Key? key}) : super(key: key);
@override @override
_QRCodeScannerPageState createState() => _QRCodeScannerPageState(); State<QRCodeScannerPage> createState() => _QRCodeScannerPageState();
} }
class _QRCodeScannerPageState extends State<QRCodeScannerPage> { class _QRCodeScannerPageState extends State<QRCodeScannerPage> {
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
QRViewController? controller;
bool lido = false; bool lido = false;
@override
void dispose() {
controller?.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('Leia o QR Code')), appBar: AppBar(title: const Text('Leia o QR Code')),
body: Stack( body: Stack(
children: [ children: [
// Câmera // 📷 Câmera
QRView( MobileScanner(
key: qrKey, fit: BoxFit.cover,
onQRViewCreated: _onQRViewCreated, onDetect: (capture) {
if (lido) return;
final barcode = capture.barcodes.firstOrNull;
final value = barcode?.rawValue;
if (value == null) return;
lido = true;
debugPrint('[LOG] Leitura QRCode: $value');
Navigator.pop(context, value);
},
), ),
// Escurecimento com buraco no centro // 🕶 Overlay com janela central
Positioned.fill( const Positioned.fill(
child: ScannerOverlay(), child: ScannerOverlay(),
), ),
], ],
), ),
); );
} }
void _onQRViewCreated(QRViewController controller) {
print("[LOG] QRCode criado");
this.controller = controller;
controller.scannedDataStream.listen((scanData) {
if (!lido) {
lido = true;
print("[LOG] Leitura QRCode: ${scanData.code}");
Navigator.pop(context, scanData.code); // Retorna o código escaneado
}
});
}
} }
class ScannerOverlay extends StatelessWidget { class ScannerOverlay extends StatelessWidget {
const ScannerOverlay({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) { return LayoutBuilder(builder: (context, constraints) {
@ -68,10 +59,10 @@ class ScannerOverlay extends StatelessWidget {
return Stack( return Stack(
children: [ children: [
// Camada escura semi-transparente // Camada escura
Container(color: Colors.black.withOpacity(0.5)), Container(color: Colors.black.withOpacity(0.5)),
// Janela recortada no centro // Janela central
Positioned( Positioned(
left: left, left: left,
top: top, top: top,

View File

@ -0,0 +1,16 @@
import 'dart:io';
import 'package:flutter/services.dart';
class IosSecureScreen {
static const MethodChannel _ch = MethodChannel('ios_secure_screen');
static Future<void> enable() async {
if (!Platform.isIOS) return;
await _ch.invokeMethod('enable');
}
static Future<void> disable() async {
if (!Platform.isIOS) return;
await _ch.invokeMethod('disable');
}
}

View File

@ -1,10 +1,10 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter_windowmanager/flutter_windowmanager.dart'; //import 'package:flutter_windowmanager/flutter_windowmanager.dart';
class SecureScreen { class SecureScreen {
static Future<void> enable() async { static Future<void> enable() async {
if (Platform.isAndroid) { if (Platform.isAndroid) {
await FlutterWindowManager.addFlags(FlutterWindowManager.FLAG_SECURE); //await FlutterWindowManager.addFlags(FlutterWindowManager.FLAG_SECURE);
} }
} }
} }

View File

@ -7,7 +7,7 @@ import 'package:MakerBook/utils/global.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:vocsy_epub_viewer/epub_viewer.dart'; //import 'package:vocsy_epub_viewer/epub_viewer.dart';
class EpubViewerPage extends StatefulWidget { class EpubViewerPage extends StatefulWidget {
final int book_id; final int book_id;
@ -28,14 +28,14 @@ class _EpubViewerPageState extends State<EpubViewerPage> {
_prepareTempEpub(); _prepareTempEpub();
// Registra um listener para capturar o lastLocation quando o leitor informar // Registra um listener para capturar o lastLocation quando o leitor informar
VocsyEpub.locatorStream.listen((locator) async { /*VocsyEpub.locatorStream.listen((locator) async {
print('Localização atual: $locator'); print('Localização atual: $locator');
// Armazena o locator como um Map<String, dynamic> em vez de string diretamente // Armazena o locator como um Map<String, dynamic> em vez de string diretamente
lastLocation = locator; lastLocation = locator;
await saveLastLocation(widget.book_id, lastLocation); await saveLastLocation(widget.book_id, lastLocation);
Navigator.pop(context); Navigator.pop(context);
}); });*/
//openLocalEpub(); //openLocalEpub();
} }
@ -49,7 +49,7 @@ class _EpubViewerPageState extends State<EpubViewerPage> {
await file.writeAsBytes(data.buffer.asUint8List()); await file.writeAsBytes(data.buffer.asUint8List());
// Config obrigatória // Config obrigatória
VocsyEpub.setConfig( /*VocsyEpub.setConfig(
themeColor: Colors.blue, themeColor: Colors.blue,
identifier: 'test-book', identifier: 'test-book',
scrollDirection: EpubScrollDirection.VERTICAL, scrollDirection: EpubScrollDirection.VERTICAL,
@ -57,7 +57,7 @@ class _EpubViewerPageState extends State<EpubViewerPage> {
enableTts: false, enableTts: false,
); );
VocsyEpub.open(path); VocsyEpub.open(path);*/
} }
@override @override
@ -84,19 +84,19 @@ class _EpubViewerPageState extends State<EpubViewerPage> {
lastLocation = await getLastLocation(widget.book_id); lastLocation = await getLastLocation(widget.book_id);
// Configuração do leitor // Configuração do leitor
VocsyEpub.setConfig( /*VocsyEpub.setConfig(
themeColor: Theme.of(context).primaryColor, themeColor: Theme.of(context).primaryColor,
identifier: widget.book_id.toString(), identifier: widget.book_id.toString(),
scrollDirection: EpubScrollDirection.ALLDIRECTIONS, scrollDirection: EpubScrollDirection.ALLDIRECTIONS,
allowSharing: false, allowSharing: false,
enableTts: false, enableTts: false,
); );*/
// Abrindo o arquivo EPUB e usando o lastLocation se estiver disponível // Abrindo o arquivo EPUB e usando o lastLocation se estiver disponível
VocsyEpub.open( /*VocsyEpub.open(
tempFile.path, tempFile.path,
lastLocation: lastLocation != null ? EpubLocator.fromJson(jsonDecode(lastLocation!)) : null, lastLocation: lastLocation != null ? EpubLocator.fromJson(jsonDecode(lastLocation!)) : null,
); );*/
} }

View File

@ -1,120 +0,0 @@
import 'dart:convert';
import 'dart:io';
import 'package:MakerBook/utils/encrypt.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:vocsy_epub_viewer/epub_viewer.dart';
class EpubViewer2Page extends StatefulWidget {
final int book_id;
const EpubViewer2Page({super.key, required this.book_id});
@override
_EpubViewer2PageState createState() => _EpubViewer2PageState();
}
class _EpubViewer2PageState extends State<EpubViewer2Page> {
String? lastLocation;
String? epubPath;
@override
void initState() {
super.initState();
_prepareTempEpub();
//_initializeEpubController();
// Registra um listener para capturar o lastLocation quando o leitor informar
VocsyEpub.locatorStream.listen((locator) async {
print('Localização atual: $locator');
// Armazena o locator como um Map<String, dynamic> em vez de string diretamente
lastLocation = locator;
await _saveLastLocation();
});
}
@override
void dispose() {
super.dispose();
}
// Função para salvar a última posição de leitura
Future<void> _saveLastLocation() async {
if (lastLocation == null) return;
final directory = await getApplicationDocumentsDirectory();
final file = File('${directory.path}/${widget.book_id}.epub');
// Recarrega o conteúdo protegido atual, atualiza o lastLocation, e salva novamente
String protectedContent = await file.readAsString();
Map<String, dynamic> fileData = unprotectFile(protectedContent);
String updatedProtectedContent = await protectFile(fileData['epubBytes'], lastLocation!);
await file.writeAsString(updatedProtectedContent);
Navigator.pop(context);
}
// Prepara o arquivo temporário para o leitor EPUB
Future<void> _prepareTempEpub_bkp() async {
final directoryReal = await getApplicationDocumentsDirectory();
final file = File('${directoryReal.path}/${widget.book_id}.epub');
final directory = await getTemporaryDirectory();
final tempFile = File('${directory.path}/${widget.book_id}_temp.epub');
// o conteúdo protegido e remove a proteção
String protectedContent = await file.readAsString();
Map<String, dynamic> fileData = unprotectFile(protectedContent);
List<int> unprotectedBytes = fileData['epubBytes'];
lastLocation = fileData['lastLocation']; // Recupera o lastLocation
// Salva o arquivo descriptografado no diretório temporário
await tempFile.writeAsBytes(unprotectedBytes);
// Configuração do leitor
VocsyEpub.setConfig(
themeColor: Theme.of(context).primaryColor,
identifier: widget.book_id.toString(),
scrollDirection: EpubScrollDirection.ALLDIRECTIONS,
allowSharing: false,
enableTts: false,
);
// Abrindo o arquivo EPUB e usando o lastLocation se estiver disponível
VocsyEpub.open(
tempFile.path,
lastLocation: lastLocation != null ? EpubLocator.fromJson(jsonDecode(lastLocation!)) : null,
);
}
// Prepara o arquivo temporário para o leitor EPUB
Future<void> _prepareTempEpub() async {
final directoryReal = await getApplicationDocumentsDirectory();
final file = File('${directoryReal.path}/${widget.book_id}.epub');
// Configuração do leitor
VocsyEpub.setConfig(
themeColor: Theme.of(context).primaryColor,
identifier: widget.book_id.toString(),
scrollDirection: EpubScrollDirection.ALLDIRECTIONS,
allowSharing: false,
enableTts: false,
);
// Abrindo o arquivo EPUB e usando o lastLocation se estiver disponível
VocsyEpub.open(
file.path,
lastLocation: lastLocation != null ? EpubLocator.fromJson(jsonDecode(lastLocation!)) : null,
);
}
@override
Widget build(BuildContext context) {
return const Center(child: CircularProgressIndicator());
}
}

View File

@ -1,6 +1,7 @@
// widgets/pdf_viewer.dart // widgets/pdf_viewer.dart
import 'dart:convert'; import 'dart:convert';
import 'package:MakerBook/secure_screen/ios_secure_screen.dart';
import 'package:MakerBook/utils/global.dart'; import 'package:MakerBook/utils/global.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_pdfview/flutter_pdfview.dart'; import 'package:flutter_pdfview/flutter_pdfview.dart';
@ -34,6 +35,7 @@ class _PdfViewerState extends State<PdfViewer> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
IosSecureScreen.enable(); // <- liga a tela segura no iOS
_loadPdf(); _loadPdf();
_verificaSeMostraTutorial(); _verificaSeMostraTutorial();
} }
@ -41,6 +43,7 @@ class _PdfViewerState extends State<PdfViewer> {
@override @override
void dispose() { void dispose() {
_saveLastPage(); _saveLastPage();
IosSecureScreen.enable(); // <- liga a tela segura no iOS
super.dispose(); super.dispose();
} }
@ -380,7 +383,8 @@ class _PdfViewerState extends State<PdfViewer> {
void _verificaSeMostraTutorial() async { void _verificaSeMostraTutorial() async {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
bool jaViu = true; // prefs.getBool('tutorialPdfVisto') ?? false; bool jaViu = prefs.getBool('tutorialPdfVisto') ?? false;
//bool jaViu = false;
setState(() { setState(() {
setaTutorial = !jaViu; setaTutorial = !jaViu;

View File

@ -8,28 +8,40 @@ class WebViewer extends StatefulWidget {
const WebViewer({super.key, required this.book_id}); const WebViewer({super.key, required this.book_id});
@override @override
_WebViewerState createState() => _WebViewerState(); State<WebViewer> createState() => _WebViewerState();
} }
class _WebViewerState extends State<WebViewer> { class _WebViewerState extends State<WebViewer> {
late WebViewController _controller; late final WebViewController _controller;
bool _loading = true; bool _loading = true;
final String baseUrl = "http://192.168.99.115:8080/#fmt=EPUB&mode=read_book&book_id="; final String baseUrl =
"http://192.168.99.115:8080/#fmt=EPUB&mode=read_book&book_id=";
@override @override
void initState() { void initState() {
super.initState(); super.initState();
if (Platform.isAndroid) { final fullUrl = baseUrl + widget.book_id;
WebView.platform = SurfaceAndroidWebView();
} // Controller novo (API 4.x)
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setUserAgent("Mozilla/5.0 (Mobile; Flutter)")
..setNavigationDelegate(
NavigationDelegate(
onPageFinished: (_) {
setState(() {
_loading = false;
});
},
),
)
..loadRequest(Uri.parse(fullUrl));
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final fullUrl = baseUrl + widget.book_id;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text("Leitor Online"), title: const Text("Leitor Online"),
@ -38,21 +50,11 @@ class _WebViewerState extends State<WebViewer> {
), ),
body: Stack( body: Stack(
children: [ children: [
WebView( WebViewWidget(controller: _controller),
initialUrl: fullUrl,
javascriptMode: JavascriptMode.unrestricted,
userAgent: "Mozilla/5.0 (Mobile; Flutter)",
onWebViewCreated: (controller) {
_controller = controller;
},
onPageFinished: (_) {
setState(() {
_loading = false;
});
},
),
if (_loading) if (_loading)
const Center(child: CircularProgressIndicator()), const Center(
child: CircularProgressIndicator(),
),
], ],
), ),
); );

View File

@ -6,10 +6,6 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <audioplayers_linux/audioplayers_linux_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) { void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin");
audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar);
} }

View File

@ -3,7 +3,6 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_linux
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST

View File

@ -5,12 +5,18 @@
import FlutterMacOS import FlutterMacOS
import Foundation import Foundation
import audioplayers_darwin import audio_session
import just_audio
import mobile_scanner
import path_provider_foundation import path_provider_foundation
import shared_preferences_foundation import shared_preferences_foundation
import webview_flutter_wkwebview
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin"))
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin"))
} }

View File

@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 1.0.3+14 version: 1.0.4+18
environment: environment:
sdk: '>=3.4.3 <4.0.0' sdk: '>=3.4.3 <4.0.0'
@ -32,18 +32,22 @@ dependencies:
sdk: flutter sdk: flutter
flutter_pdfview: ^1.0.4 flutter_pdfview: ^1.0.4
http: ^1.4.0 http: ^1.4.0
flutter_windowmanager: ^0.2.0 #flutter_windowmanager: ^0.2.0
qr_code_scanner: ^1.0.0 #qr_code_scanner: ^1.0.1
vocsy_epub_viewer: ^3.0.0 mobile_scanner: ^6.0.2
#vocsy_epub_viewer: ^3.0.0
encrypt: ^5.0.0 encrypt: ^5.0.0
epub_view: ^3.2.0 epub_view: ^3.2.0
webview_flutter: ^3.0.4 webview_flutter: ^4.13.0
flutter_svg: ^1.1.6 flutter_svg: ^2.2.3
flutter_launcher_icons: ^0.11.0 flutter_launcher_icons: ^0.11.0
google_fonts: ^4.0.4 google_fonts: ^6.3.3
audioplayers: ^5.2.1 #audioplayers: ^6.5.1
just_audio: ^0.9.38
tutorial_coach_mark: ^1.3.2 tutorial_coach_mark: ^1.3.2
shared_preferences: ^2.2.0 shared_preferences: ^2.2.0
screen_protector: ^1.4.2
#screenshot_callback: ^3.0.0
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
@ -66,7 +70,7 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your # activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint # package. See that file for information about deactivating specific lint
# rules and activating additional ones. # rules and activating additional ones.
flutter_lints: ^3.0.0 flutter_lints: ^6.0.0
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec

View File

@ -6,9 +6,6 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <audioplayers_windows/audioplayers_windows_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
AudioplayersWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
} }

View File

@ -3,7 +3,6 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_windows
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST