Comprehensive pwE Documentation
pwE (Pray With Me) é um aplicativo revolucionário desenvolvido em Flutter, projetado especificamente para guiar o usuário na prática milenar da Lectio Divina (Leitura Orante). Em perfeita consonância com a fé católica e o Magistério da Igreja, o app une espiritualidade e alta tecnologia para criar um ambiente imersivo, focado e seguro de oração.
Project Principles and Pillars
Developed under strict architectural and design standards (as defined in the official theme in app_theme.dart), pwE is sustained by three fundamental pillars:
† Doctrinal Fidelity
All meditations, prayers, and reflections proposed by the AI rigorously follow the Catholic Magisterium. No heretical, secular, or technological terminology is exposed to the user in the meditation experience.
🔒 Absolute Privacy
Prayer is intimacy. With the Edge LLM engine, AI processing can occur entirely locally on the user's device, ensuring absolute privacy of the soul's data.
📱 Immersive Design
Use of refined mechanics (such as progressive scroll reveal and haptic hold gestures) to eliminate distractions and guide the user through the liturgical stages.
Clean Architecture & DDD
To ensure that the business logic is independent of database technologies, frameworks, or third-party APIs, pwE adopts a layered structure based on Clean Architecture and Domain-Driven Design (DDD).
Architectural Layers
The separation of concerns ensures ease of maintenance and high coverage of unit tests:
-
Domain Layer (Domain): Contains pure business rules (
Entities,UseCases, and defined contracts). See the Lectio Divina entity in the file lectio_session.dart:// lib/features/lectio_divina/domain/entities/lectio_session.dart class LectioSession { final String id; final DateTime date; final String passage; final String emotion; final String meditation; final String prayer; final String contemplationResolution; final int streakDays; const LectioSession({ required this.id, required this.date, required this.passage, required this.emotion, required this.meditation, required this.prayer, required this.contemplationResolution, required this.streakDays, }); } -
Data Layer (Data): Contains the infrastructure (
Models,DataSources, and concrete repository implementations as in llm_remote_datasource.dart):// lib/features/lectio_divina/data/datasources/llm_remote_datasource.dart abstract class LlmRemoteDataSource { Future<MeditationModel> generateMeditation(MeditationRequestModel request); } class LlmRemoteDataSourceImpl implements LlmRemoteDataSource { final ApiClient apiClient; LlmRemoteDataSourceImpl({required this.apiClient}); @override Future<MeditationModel> generateMeditation(MeditationRequestModel request) async { final response = await apiClient.post( '/v1/spiritual-advisor/meditate', data: request.toJson(), ); return MeditationModel.fromJson(response.data); } } -
Presentation Layer (Presentation): Contains the user interface (Widgets and Pages) and complex state management based on **Bloc** (see in lectio_bloc.dart):
// lib/features/lectio_divina/presentation/bloc/lectio_bloc.dart class LectioBloc extends Bloc<LectioEvent, LectioState> { final GetGuidedMeditation getGuidedMeditation; final SaveSession saveSession; LectioBloc({ required this.getGuidedMeditation, required this.saveSession, }) : super(LectioInitial()) { on<StartSessionEvent>(_onStartSession); on<AdvanceToStepEvent>(_onAdvanceToStep); on<CompleteSessionEvent>(_onCompleteSession); } void _onStartSession(StartSessionEvent event, Emitter<LectioState> emit) { emit(LectioInProgress(step: LectioStep.reading, passage: event.passage)); } }
All services, repositories, use cases, and blocs are registered in the central injection container located in injection_container.dart through the **GetIt** package:
// lib/core/di/injection_container.dart
final sl = GetIt.instance;
Future<void> init() async {
// Features - Lectio Divina (BLoCs)
sl.registerFactory(() => LectioBloc(getGuidedMeditation: sl(), saveSession: sl()));
// Use cases
sl.registerLazySingleton(() => GetGuidedMeditation(sl()));
sl.registerLazySingleton(() => SaveSession(sl()));
// Repositories
sl.registerLazySingleton<IARepository>(() => IARepositoryImpl(remoteDataSource: sl()));
// Data sources
sl.registerLazySingleton<LlmRemoteDataSource>(() => LlmRemoteDataSourceImpl(apiClient: sl()));
// External (Core)
sl.registerLazySingleton(() => ApiClient());
}
Fluxo Imersivo da Lectio Divina
Lectio Divina in pwE consolidates the four classic stages into a continuous view using the CustomScrollView mechanic, eliminating screen fatigue:
Lectio (Reading)
The user carefully reads the sacred text of the day (daily liturgy) or inputs/scans a desired passage.
Meditatio (Meditation)
The app suggests meditation points through AI, connecting the sacred text to faith analogies and experiences.
Oratio (Prayer)
The user responds to the Word in the form of supplication, adoration, or thanksgiving, with prompts based on the Psalms.
Contemplatio (Contemplation)
The user makes a practical life resolution and keeps the prayer in the silence of their personal diary.
Focused UI Components
These UI elements create an atmosphere of introspection in the page immersive_reading_page.dart:
- ProgressiveTextReveal: Hides future segments using blur until the user scrolls or interacts with the text, helping them focus on the current reading.
- HoldToSaveButton: Requires pressing the button for 2 continuous seconds with haptic feedback to ensure intent to advance steps, preventing accidental clicks in the silence of prayer.
- ThinkingFeedback: Displays rotating motivational and spiritual messages while the AI processes the prompt (e.g., "Uniting your prayer to the heavenly choirs...").
Veja um exemplo prático de implementação do HoldToSaveButton, com lógica tátil e preenchimento gradual por tempo em Flutter:
// lib/features/lectio_divina/presentation/widgets/hold_to_save_button.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class HoldToSaveButton extends StatefulWidget {
final VoidCallback onTrigger;
final String label;
const HoldToSaveButton({Key? key, required this.onTrigger, required this.label}) : super(key: key);
@override
_HoldToSaveButtonState createState() => _HoldToSaveButtonState();
}
class _HoldToSaveButtonState extends State<HoldToSaveButton> with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
);
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
HapticFeedback.heavyImpact();
widget.onTrigger();
_controller.reset();
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onLongPressStart: (_) {
HapticFeedback.mediumImpact();
_controller.forward();
},
onLongPressEnd: (_) {
if (_controller.status != AnimationStatus.completed) {
_controller.reverse();
}
},
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Container(
height: 56,
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.amber.shade700, width: 1.5),
gradient: LinearGradient(
colors: [Colors.amber.shade800, Colors.amber.shade900],
stops: [0.0, _controller.value],
),
),
child: Center(
child: Text(
_controller.value > 0 ? 'Mantenha pressionado... ${(_controller.value * 100).toInt()}%' : widget.label,
style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.white),
),
),
);
},
),
);
}
}
Maturity System (Fading Scaffolding)
In order not to overwhelm the user and to encourage autonomous prayer, pwE implements the innovative strategy of Fading Scaffolding. The AI adapts the level of verbosity and depth of its prompts according to the spiritual progress of the faithful.
The Three Stages of Maturity
Beginner
Detailed pedagogical guidance. The AI teaches the meaning of each stage and provides detailed illustrative analogies for beginners in the practice.
Practitioner
Focus on faith provocations. Eliminates academic introductions and proposes direct points of personal questioning and application in daily charity.
Contemplative
Minimalism and Silence. The AI provides a maximum of two inspiring lines based on the tradition of the Church Fathers, encouraging interior silence.
Evolution occurs naturally with the streak count. However, the user can manually set their maturity level in the settings if they want stronger guidance or prefer contemplative silence immediately.
Zero Invasion & Privacy: Edge LLM
One of the biggest differentiators of pwE is its ability to operate 100% autonomously and privately. Thanks to the Edge LLM engine, prayers written in the intimacy of the room never need to leave the smartphone.
Local Inference Technical Specifications
- Model Used: Gemma 4 (2B class) optimized and quantized for mobile devices.
- Engine: Based on the **LiteRT-LM** library running on fast and efficient native method channels (`MethodChannel`). Managed by llm_local_datasource.dart.
- Minimum Hardware: Smartphones with 4GB of free RAM, GPU/NPU graphics acceleration support, and ~1.3GB of free space for the quantized model.
- Privacy Visual Indicator: Whenever the AI is processed locally, the app displays a Golden Shield, certifying the isolation of the processing.
Below is the practical implementation of LlmLocalDataSourceImpl, demonstrating native invocation and automatic fallback to the cloud in case of error:
// lib/features/lectio_divina/data/datasources/llm_local_datasource.dart
import 'package:flutter/services.dart';
import 'package:pwe/features/lectio_divina/data/models/meditation_model.dart';
import 'package:pwe/features/lectio_divina/data/models/meditation_request_model.dart';
import 'package:pwe/features/lectio_divina/data/datasources/llm_remote_datasource.dart';
abstract class LlmLocalDataSource {
Future<MeditationModel> generateMeditation(MeditationRequestModel request);
}
class LlmLocalDataSourceImpl implements LlmLocalDataSource {
static const MethodChannel _channel = MethodChannel('pwe.app/gemma-inference');
final LlmRemoteDataSource remoteFallback;
final bool isGemmaEnabled;
LlmLocalDataSourceImpl({
required this.remoteFallback,
required this.isGemmaEnabled,
});
@override
Future<MeditationModel> generateMeditation(MeditationRequestModel request) async {
if (!isGemmaEnabled) {
return remoteFallback.generateMeditation(request);
}
try {
final String? response = await _channel.invokeMethod<String>(
'executeGemmaInference',
{
'prompt': request.toPrompt(),
'max_tokens': 200,
'temperature': 0.3,
},
);
if (response == null || response.isEmpty) {
throw PlatformException(code: 'EMPTY_RESPONSE', message: 'Gemma local retornou vazio.');
}
return MeditationModel.fromLocalText(response, request.passage);
} catch (e) {
// Fallback transparente em caso de indisponibilidade ou falha de inferência local
return remoteFallback.generateMeditation(request);
}
}
}
If the local model is not found or if the device does not support inference, the app makes a transparent and secure transition to the cloud API (Gemini), ensuring the continuity of the Lectio Divina without interruptions.
Bible Passage Capture via Photo (OCR)
In addition to typing passages and the dynamically collected daily liturgy, pwE allows instant capture of printed biblical texts using local visual artificial intelligence.
OCR Architecture & Mechanics
Integrating **Google ML Kit Vision**, the process is entirely offline and agile:
1. Capture & Edit
The user photographs the excerpt from the physical Bible and edits the image (crop and rotate) in the pre-processing screen to focus exclusively on the liturgical text.
2. Text Extraction
OCR processing extracts the sentences with precision, applying automatic spelling and punctuation refinement for fluid reading.
3. Remote Identification
The recognized excerpt is sent to the biblical context analyzer, which automatically detects the corresponding book, chapter, and verse.
Below is the practical example of integration with the ML Kit OCR for offline extraction of biblical text from physical pages of the Bible:
// lib/features/lectio_divina/data/datasources/ocr_local_datasource.dart
import 'dart:io';
import 'package:google_ml_kit/google_ml_kit.dart';
abstract class OcrLocalDataSource {
Future<String> extractTextFromImage(File imageFile);
}
class OcrLocalDataSourceImpl implements OcrLocalDataSource {
final TextRecognizer _textRecognizer = GoogleMlKit.vision.textRecognizer();
@override
Future<String> extractTextFromImage(File imageFile) async {
final inputImage = InputImage.fromFile(imageFile);
try {
final RecognizedText recognizedText = await _textRecognizer.processImage(inputImage);
// Filtra e limpa o texto para remover ruídos e marcas indesejadas do reconhecimento
String rawText = recognizedText.text;
return _refineBiblicalText(rawText);
} catch (e) {
throw Exception('Erro ao processar imagem via ML Kit OCR: ${e.toString()}');
}
}
String _refineBiblicalText(String input) {
return input
.replaceAll('\n', ' ')
.replaceAll(RegExp(r'\s+'), ' ')
.trim();
}
void dispose() {
_textRecognizer.close();
}
}
Integration Contracts & API (Swagger)
pwE follows the highest software architecture standards in the market for both its local and cloud connections. Below, explore the interactive, code-oriented documentation for the main endpoints and platform channels in the ecosystem.
Sends the text of the Holy Scripture (whether via direct typing, daily liturgy, or captured locally by OCR) and the spiritual-affective state of the faithful to the cloud AI spiritual advisor. The response is formulated strictly under the guidelines of the Magisterium and Fading Scaffolding maturity.
Body Parameters (JSON)
| Parameter | Type | Description |
|---|---|---|
| passage* required | string | The complete biblical text of the verse or chapter selected for meditation. |
| emotion | string | Emotional state of the faithful (e.g., "anxious", "grateful", "troubled") to contextualize the pastoral response. |
| maturity_level* required | string | App progress level that defines the verbosity and depth (beginner, practitioner, contemplative). |
import 'package:pwe/core/network/api_client.dart';
import 'package:pwe/features/lectio_divina/data/models/meditation_request.dart';
import 'package:pwe/features/lectio_divina/data/models/meditation_response.dart';
final apiClient = GetIt.I<ApiClient>();
final request = MeditationRequest(
passage: "O Senhor é o meu pastor, nada me faltará. Em verdes pastagens me faz repousar. (Salmo 22, 1-2)",
emotion: "ansioso",
maturityLevel: MaturityLevel.beginner,
);
final Either<Failure, MeditationResponse> result =
await apiClient.post('/v1/spiritual-advisor/meditate', data: request.toJson());
result.fold(
(failure) => print('Erro espiritual/rede: ${failure.message}'),
(meditation) => print('Meditação gerada: ${meditation.guidance}'),
);
curl -X POST "https://api.pwe.app/v1/spiritual-advisor/meditate" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer SEU_TOKEN_AQUI" \
-d '{
"passage": "O Senhor é o meu pastor, nada me faltará. (Salmo 22, 1)",
"emotion": "ansioso",
"maturity_level": "beginner"
}'
{
"passage": "O Senhor é o meu pastor, nada me faltará. Em verdes pastagens me faz repousar. Conduz-me junto às águas refrescantes, restaura as forças de minha alma. (Salmo 22, 1-3)",
"emotion": "ansioso",
"maturity_level": "beginner"
}
{
"status": "success",
"data": {
"passage_reference": "Salmo 22, 1-3",
"maturity_applied": "beginner",
"spiritual_guidance": "Meu filho em Cristo, a ansiedade tenta turvar as águas da tua alma, mas o Bom Pastor te convida ao repouso no Seu amor. Diante da agitação do mundo, lembra-te de que Ele tem o controle absoluto de todas as coisas. Entrega-Lhe as tuas preocupações no silêncio da oração. Nas palavras de São Padre Pio: 'Reze, espere e não se preocupe. A preocupação é inútil. Deus é misericordioso e ouvirá sua oração.' Que o teu coração descanse hoje na certeza da Providência Divina.",
"reflection_points": [
"Como você tem buscado as 'verdes pastagens' da oração no meio das tribulações cotidianas?",
"De que maneira a entrega confiante ao Bom Pastor pode acalmar a tempestade da sua ansiedade hoje?",
"Faça um ato de fé simples: 'Jesus, eu confio em Vós, mesmo quando não vejo o caminho.'"
]
}
}
Consumes the official API to load the liturgical texts of the Roman Catholic Church valid for the current date or an optional date. Returns First Reading, Responsorial Psalm, Second Reading (if any), and Gospel to support Lectio Divina in a structured way.
Parameters da Query (Request URL)
| Parameter | Type | Description |
|---|---|---|
| date | string | Target date in ISO-8601 YYYY-MM-DD format. Default is current server date. |
| translation | string | Desired translation and Bible version (e.g., cnbb, ave_maria, jerusalem). Default: cnbb. |
import 'package:pwe/features/lectio_divina/domain/repositories/liturgy_repository.dart';
import 'package:pwe/features/lectio_divina/domain/entities/liturgical_day.dart';
final liturgyRepo = GetIt.I<LiturgyRepository>();
final Either<Failure, LiturgicalDay> result =
await liturgyRepo.getTodayLiturgy(translation: 'cnbb');
result.fold(
(failure) => print('Erro ao coletar liturgia: ${failure.message}'),
(liturgy) => print('Evangelho do dia (${liturgy.reference}): ${liturgy.content}'),
);
curl -X GET "https://api.pwe.app/v1/liturgy/today?translation=cnbb" \
-H "Accept: application/json"
{
"date": "2026-05-21",
"liturgical_season": "Tempo Pascal",
"liturgical_color": "Branco",
"celebration": "Quinta-feira da 7ª Semana da Páscoa",
"readings": {
"first_reading": {
"reference": "Atos 22, 30; 23, 6-11",
"title": "Paulo testemunha em Jerusalém e é enviado a Roma",
"content": "Naqueles dias, querendo saber com certeza de que os judeus o acusavam, o comandante soltou Paulo e mandou reunir os sumos sacerdotes e todo o conselho dos anciãos..."
},
"psalm": {
"reference": "Salmo 15 (16)",
"response": "Guardai-me, ó Deus, porque em vós me refugio!",
"verses": [
"Preservai-me, ó Deus, porque em vós me refugio! Eu digo ao Senhor: 'Vós sois o meu Deus'.",
"Ó Senhor, vós sois a minha herança e o meu cálice; nas vossas mãos está o meu destino."
]
},
"evangelho": {
"reference": "João 17, 20-26",
"title": "Que eles sejam um em nós, para que o mundo creia",
"content": "Naquele tempo, Jesus levantou os olhos ao céu e rezou, dizendo: 'Não rogo somente por eles, mas também por aqueles que vão creer em mim pela sua palavra...'"
}
}
}
Native platform communication channel (MethodChannel) to asynchronously delegate the processing of Large Language Model inference (Gemma 2B quantized via LiteRT-LM) to 100% isolated and offline execution on the user's smartphone. Ensures sovereignty and absolute privacy.
Input Arguments (Method Call)
| Argument | Type | Description |
|---|---|---|
| prompt* required | string | Prompt containing the passage and strict Catholic context to feed into local Gemma. |
| max_tokens | int | Limit of tokens to be generated by the model. Default: 150. |
| temperature | double | Decoder thermal randomness factor. Default: 0.3. |
import 'package:flutter/services.dart';
const MethodChannel _channel = MethodChannel('pwe.app/gemma-inference');
Future<String?> generateLocalGuidance({
required String prompt,
int maxTokens = 150,
double temperature = 0.3,
}) async {
try {
final String? result = await _channel.invokeMethod<String>(
'executeGemmaInference',
{
'prompt': prompt,
'max_tokens': maxTokens,
'temperature': temperature,
},
);
return result;
} on PlatformException catch (e) {
print("Erro na inferência local Gemma: ${e.message}");
return null;
}
}
package com.pwe.app.inference
import android.content.Context
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import com.google.mediapipe.tasks.genai.llminference.LlmInference
class GemmaInferencePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
private lateinit var channel: MethodChannel
private var llmInference: LlmInference? = null
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(binding.binaryMessenger, "pwe.app/gemma-inference")
channel.setMethodCallHandler(this)
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
if (call.method == "executeGemmaInference") {
val prompt = call.argument<String>("prompt") ?: ""
val maxTokens = call.argument<Int>("max_tokens") ?: 150
val temp = call.argument<Double>("temperature") ?: 0.3
try {
// Inferência executada localmente de forma isolada
val response = llmInference?.generateResponse(prompt)
result.success(response)
} catch (e: Exception) {
result.error("GENAI_ERROR", e.localizedMessage, null)
}
} else {
result.notImplemented()
}
}
}
{
"prompt": "Contexto: Atue como um orientador espiritual católico fiel ao Magistério da Igreja. Passagem: João 17, 20-26. Gere 2 pontos curtos de meditação.",
"max_tokens": 150,
"temperature": 0.3
}
{
"status": "success",
"engine": "LiteRT-LM (Gemma 2B)",
"inference_time_ms": 342,
"result": "1. Unidade na Caridade: Jesus roga ao Pai por nossa unidade como sinal visível da Sua missão divina. Rezemos pela coesão da Igreja hoje.\n2. O Amor Divino em Nós: A oração de Cristo pede que o amor com que o Pai O amou habite em nós. Deixemo-nos possuir por esse amor no silêncio da Contemplatio."
}
Architecture Decisions (ADRs)
pwE maintains historical records of the main software architecture decisions (located in the docs/adr/ folder), ensuring technical traceability.
Status: Aceito | Objetivo: Separar a lógica do negócio (pureza do domínio) de agentes de infraestrutura.
It was decided that the Domain layer does not have any import from the Flutter framework or Firebase. Any external communication is defined via interface contracts and resolved dynamically in the GetIt container.
The separation of concerns allows all application intelligence to be thoroughly tested in a simple console environment, reducing build time in CI/CD.
Status: Aceito | Objetivo: Garantir estabilidade e velocidade na execução local do Gemma 4.
Ready-to-use packages like `flutter_gemma` and `mediapipe_genai` were evaluated. However, due to reports of OOM (Out of Memory) and lack of support for background threads in iOS/Android, we chose to create native platform channels (**MethodChannel** on Android with Kotlin using the native LiteRT-LM API).
This allows the use of physical GPU/NPU acceleration from modern chips with full stability.
Status: Proposed | Objective: 100% offline indexing and zero spiritual data leaks.
RAG needs to vectorize the user's personal diary entries for long-term semantic search. Doing this in the cloud would cause privacy issues.
The decision was to use **MobileBERT** quantized on-device via LiteRT, allowing the generation of 384-dimensional embeddings in milliseconds and without a network connection.
Status: Accepted | Objective: Eliminate tight coupling and UI state race conditions in the central BLoC.
The monolithic LectioBloc was decomposed into 6 sub-BLoCs with atomic, immutable responsibilities: LectioSessionBloc, DailyLiturgyBloc, ExegesisBloc, PrayerJournalBloc, AiOratioBloc, and SubscriptionBloc. This segregation improved testability and enabled over 930 automated tests.
Status: Accepted | Objective: Ensure spiritual sovereignty and privacy-by-design compliance.
Any calls to remote LLMs require prior transparent user consent via a dedicated dialog. The app provides a clear alternative for classic contemplative Lectio Divina without AI, or 100% on-device AI (Golden Shield).
Status: Accepted | Objective: Total protection of private prayers and Spiritual Notebook reflections.
All local Hive persistence boxes containing Lectio Divina notes are encrypted at rest with AES-256-GCM, random initialization vectors (IV), and PBKDF2 key derivation anchored in Android Keystore and Apple Keychain.
Status: Accepted | Objective: Architectural hygiene and blocking simulation use cases in production.
The security vault feature had its DI container purged from the core and isolated into security_vault_injection_container.dart. Test and bypass simulation use cases were strictly safeguarded under if (!kDebugMode).
Status: Accepted | Objective: Respect Lectio Divina pedagogy and harmonize screens with the Sacred Design System.
AI engine selection was decoupled from cold start and relocated organically to the conclusion of onboarding (FTUE), adopting the Sacred Design System (Noble Parchment / Shalom), Cinzel/Lora typography, and cross dividers.
Status: Accepted | Objective: Structure autonomous, concurrent technical governance for 16 specialists.
Formalized multi-agent architecture with asynchronous subagents declared in .agents/agents/, featuring standardized YAML schemas, isolated workspaces, and specialized tools across Engineering, Theology, FinOps, Design/A11y, QA, and DevOps.
Histórico de Refactorings (Logs)
Follow the technical logs of major evolutions and engineering improvements applied to the project (found in docs/refactor/).
Downloading a 1.3GB model is critical on mobile networks. The refactoring implemented:
- Partitioned Download: Use of stable buffers to avoid OOM during local writing.
- Integrity Validation: SHA256 hash calculation post-download before extraction.
- GlobalDownloadTracker: A widget that floats above the entire application showing the progress percentage, allowing the user to navigate or pray while the download completes in the background.
The raw ML Kit processor captured graphical noise from edges and shadows of physical Bible pages. The refactoring introduced:
- Adaptive Thresholding Binarization: Fast conversion of the captured photo to high-contrast black and white via native Canvas before submitting it to OCR.
- Lexical Correction Algorithm: A local micro-parser that crosses the captured text with common liturgical expressions to correct subtle character recognition failures.
Troubleshooting
Interactive section for engineers and testers to quickly resolve common environment and build issues in the pwE project.
Errors occur when the Gradle Wrapper or Android Gradle Plugin does not match the required SDK level. To update to a stable and recommended version:
# Navigate to the project's android directory
cd android
# Update the Gradle Wrapper to the compatible stable version (e.g., 8.13)
./gradlew wrapper --gradle-version 8.13
Also make sure that the gradle-wrapper.properties file has the distribution link correctly configured for the stable version in HTTPS.
To ensure that the model's binary file `.litertlm` (~1.3GB) was not corrupted during the user's opt-in download, the app validates the SHA256 hash.
If loading fails continuously, confirm the signature configured in your Firebase Remote Config dashboard:
# Expected variables in Remote Config
enable_gemma_local: true
gemma_model_sha256: "COLOCAR_HASH_SHA256_REAL_DO_MODELO_AQUI"
gemma_model_download_url: "https://seu-cdn-seguro.com/models/gemma-2b-it.litertlm"
If the hash differs, the `GemmaLocalDataSource` will reject local initialization and immediately trigger the fallback to Gemini in the cloud for safety.
When running unit and state integration tests in BLoC (e.g., `lectio_bloc_test.dart`), make sure to inject mocked use cases via **Mockito** or **Mocktail** and register in test GetIt to avoid calling real network APIs.
# Run all unit tests of the project cleanly
flutter test --coverage
Diagrama de Contexto e Container (C4 Model)
This C4 model diagram details the high-level integrations of the pwE app with local and external cloud services.
graph TB
User((Usuário Católico))
pwE[App pwE - Mobile]
subgraph External_Systems [Sistemas Externos]
Firebase[Firebase: Auth, Firestore, Crashlytics]
LLM[IA: OpenAI / Gemini / Gemma]
BibleAPI[API de Bíblia / Liturgia Diária]
MLKit[Google ML Kit - OCR Local]
end
User -->|Pratica Lectio Divina| pwE
pwE -->|Autentica e Sincroniza| Firebase
pwE -->|Solicita Meditações/Orações| LLM
pwE -->|Busca Passagens e Liturgia| BibleAPI
pwE -->|Processa Imagens Offline| MLKit
Diagrama de Sequência da Sessão
Visualize the complete lifecycle of a prayer session, tracking data and command traffic through the software layers.
sequenceDiagram
autonumber
participant U as Usuário
participant UI as Presentation (Bloc/UI)
participant D as Domain (UseCases)
participant R as Data (Repositories/DS)
participant IA as IA (Gemini/Gemma)
participant B as DB (Firestore/Hive)
U->>UI: Inicia Sessão (Informa Emoção)
UI->>D: SuggestPassageUseCase(emotion)
D->>R: IARepository.getSuggestion()
R->>IA: Prompt (Spiritual Advisor Context)
IA-->>R: Sugestão de Passagem + Motivo
R-->>D: DailyPassage Suggestion
D-->>UI: LectioInProgress(suggestedPassage)
U->>UI: Lê e Inicia momento Lectio
UI->>U: Exibe Texto da Escritura
U->>UI: Avança para Meditatio
UI->>D: GetGuidedMeditationUseCase(passage)
D->>R: IARepository.getMeditationPoints()
R->>IA: Prompt (Meditatio alinhada ao Magistério)
IA-->>R: Pontos de Meditação Concreta
R-->>D: MeditationPoints
D-->>UI: LectioInProgress(guidingPrompt)
U->>UI: Avança para Oratio & Escreve preces
UI->>D: GetPrayerPromptUseCase()
D->>R: IARepository.getPrayerInspiration()
R->>IA: Prompt baseado na Tradição Católica
IA-->>R: Inspiração de Oração
R-->>D: PrayerPrompt
D-->>UI: LectioInProgress(prayerPrompt)
U->>UI: Escreve resolução (Contemplatio)
U->>UI: Conclui Sessão (HoldToSaveButton)
UI->>D: SaveSessionUseCase(session)
D->>R: SessionRepository.save()
R->>B: Persiste localmente e na nuvem
B-->>R: Sucesso
R-->>D: Sucesso
D-->>UI: LectioCompleted
UI->>U: Exibe Ecrã de Celebração de Streak
State Machine: LectioBloc
Rigorous management of states and asynchronous screen flows in pwE using the Event, State, and BLoC triad.
stateDiagram-v2
[*] --> LectioInitial: Inicialização da Feature
LectioInitial --> LectioInProgress_Lectio: StartSessionEvent
state LectioInProgress {
[*] --> LectioInProgress_Lectio
LectioInProgress_Lectio --> LectioInProgress_Meditatio: AdvanceToStep(1)
LectioInProgress_Meditatio --> LectioInProgress_Oratio: AdvanceToStep(2)
LectioInProgress_Oratio --> LectioInProgress_Contemplatio: AdvanceToStep(3)
LectioInProgress_Contemplatio --> [*]: CompleteSessionEvent
}
LectioInProgress --> LectioCompleted: Session Saved Successfully
LectioInProgress --> LectioError: Failure (API/Network/DB)
LectioError --> LectioInitial: Reset / Tentar Novamente
LectioCompleted --> [*]
Folder Structure and Clean Architecture Mapping
The directory tree of the pwE repository reflects the intention of the layers and the domain isolation.
Árvore de Pastas
lib/
├── core/ # Shared Core
│ ├── di/ # GetIt Container (injection_container.dart)
│ ├── error/ # Failures and Exceptions (Either)
│ ├── network/ # REST Clients and network configurations
│ ├── routes/ # Route configurations with GoRouter
│ └── theme/ # Visual styles and app palettes
├── features/ # Isolated domain modules
│ ├── lectio_divina/ # Main domain of Prayerful Reading
│ │ ├── data/ # DTOs, Repositories, and data sources
│ │ ├── domain/ # Entities, business rules, and UseCases
│ │ └── presentation/ # BLoC controllers, widgets, and pages
│ ├── auth/ # Login and security management
│ └── profile/ # Maturity control and preferences
└── main.dart # App configurations and bootstrap