Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
f6d0111
feat(logs): add LogsMenuItem UI component
Delgado74 Oct 25, 2025
065ab14
feat(logs): add persistent LogsService with automatic debugPrint inte…
Delgado74 Oct 25, 2025
a3fef38
feat(logs): add LogsScreen UI to display, clear and export logs
Delgado74 Oct 25, 2025
7fd5b2a
feat(logs): add Logs menu option in drawer for user accessibility
Delgado74 Oct 25, 2025
5ca4407
i18n(logs): add Spanish translations for logs feature
Delgado74 Oct 25, 2025
7273bf5
i18n(logs): add English translations for logs feature
Delgado74 Oct 25, 2025
6bcb180
i18n(logs): add Italian translations for logs feature
Delgado74 Oct 25, 2025
cd6ea6d
chore(logs): initialize LogsService at app startup
Delgado74 Oct 25, 2025
3d53d8c
test(logs): add widget tests for LogsScreen
Delgado74 Oct 25, 2025
67c92de
Updte intl_en.arb
Delgado74 Oct 25, 2025
b4a8a82
Update intl_es.arb
Delgado74 Oct 25, 2025
e9b2fca
Update intl_it.arb
Delgado74 Oct 25, 2025
05e500b
Update custom_drawer_overlay.dart
Delgado74 Oct 25, 2025
14df3b3
Refactor logs_screen_test.dart with mock services
Delgado74 Oct 25, 2025
6cb33a6
Fix session timeout message in intl_en.arb
Delgado74 Oct 28, 2025
dd0d75d
Fix formatting in intl_es.arb file
Delgado74 Oct 28, 2025
e73ffc1
Fix session timeout message in Italian localization
Delgado74 Oct 28, 2025
a427929
feat: remove debug mode restrictions for chat tabs and disputes view
AndreaDiazCorreia Oct 25, 2025
9a05c74
fix:message encryption
AndreaDiazCorreia Oct 26, 2025
12c8932
Update changelog and zapstore file (#345)
grunch Oct 27, 2025
2b413a6
fix: bad name of artifcacts for desktop build fixed (#344)
arkanoider Oct 27, 2025
0460f6e
Build names should use dash as separator (#346)
grunch Oct 27, 2025
11f0ea5
bumps to v1.0.3
grunch Oct 27, 2025
dafca38
docs: improve Android signing setup documentation and examples (#347)
AndreaDiazCorreia Oct 27, 2025
8269c2e
fix: apply CodeRabbit critical fixes and remove dead widget
Oct 28, 2025
5811f79
fix: apply CodeRabbit critical fixes and remove dead widget
Oct 28, 2025
06349a4
Merge branch 'MostroP2P:main' into Logs
Delgado74 Oct 28, 2025
9841a61
Delete lib/features/logs/logs_menu_item.dart
Delgado74 Oct 28, 2025
cc92620
Update logs route to use pageBuilder with transition
Delgado74 Oct 28, 2025
5a28533
Update drawer Logs menu item to use go_router navigation
Oct 28, 2025
7a16fd1
Merge branch 'MostroP2P:main' into Logs
Delgado74 Oct 30, 2025
9368861
Update main.dart
Oct 30, 2025
a457c3c
- Added `logs_provider.dart` to manage log state and persistence.
Oct 31, 2025
5a96ec8
"feat: add native Android logcat capture to logs system
Nov 1, 2025
1428f1d
Improve code quality and maintainability
Nov 3, 2025
1cc6d1c
Merge branch 'MostroP2P:main' into Logs
Delgado74 Nov 3, 2025
8eeda0b
refactor: improve log capture robustness, reactive providers, and loc…
Nov 4, 2025
7475e7c
fix(logs): clean up provider and fix async init/dispose issues
Nov 4, 2025
08a6184
refactor(logs): simplify architecture by removing LogsNotifier duplic…
Nov 4, 2025
4bcc887
chore(logs): translate and clean comments, apply CodeRabbit suggestion
Nov 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 70 additions & 64 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,73 +1,79 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Delgado74 por quΓ© es necesario cambiar el manifest? me puedes comentar la ventaja de realizar estos cambios?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fue un error de concepto a la hora de implementar el PR. Incluso hay muchas modificaciones. AsΓ­ que sobre la base de las recomendaciones del equipo estoy trabajando ahora en una soluciΓ³n que solo introduzca 3 archivos nuevos y solo modifica 3 archivos. Creando un PR mΓ‘s sencillo. Gracias por la oportunidad

<application
android:label="Mostro"
android:name="${applicationName}"
android:icon="@mipmap/launcher_icon"
android:allowBackup="false"
android:fullBackupContent="false"
android:enableOnBackInvokedCallback="true">

<meta-data android:name="flutter_deeplinking_enabled" android:value="false" />
xmlns:tools="http://schemas.android.com/tools">

<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<application
android:label="Mostro"
android:name="${applicationName}"
android:icon="@mipmap/launcher_icon"
android:allowBackup="false"
android:fullBackupContent="false"
android:enableOnBackInvokedCallback="true">

<!-- Deep Link Support for mostro: scheme -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="mostro" />
</intent-filter>
</activity>
<meta-data
android:name="flutter_deeplinking_enabled"
android:value="false" />

<service
android:name="id.flutter.flutter_background_service.BackgroundService"
android:exported="false"
android:foregroundServiceType="dataSync"
android:permission="android.permission.FOREGROUND_SERVICE"
tools:replace="android:exported" />
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">

<receiver
android:exported="false"
android:name="com.dexterous.flutterlocalnotifications.ActionBroadcastReceiver" />
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_bg_service_small" />
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />

</application>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.INTERNET" />
<!-- Deep Link Support for mostro: scheme -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="mostro" />
</intent-filter>
</activity>

<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT" />
<data android:mimeType="text/plain" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="lightning" />
</intent>
</queries>
<service
android:name="id.flutter.flutter_background_service.BackgroundService"
android:exported="false"
android:foregroundServiceType="dataSync"
tools:replace="android:exported" />

<receiver
android:name="com.dexterous.flutterlocalnotifications.ActionBroadcastReceiver"
android:exported="false" />

<meta-data
android:name="flutterEmbedding"
android:value="2" />

<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_bg_service_small" />

</application>

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.INTERNET" />

<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT" />
<data android:mimeType="text/plain" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="lightning" />
</intent>
</queries>
</manifest>
106 changes: 105 additions & 1 deletion android/app/src/main/kotlin/network/mostro/app/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,109 @@
package network.mostro.app

import android.os.Handler
import android.os.Looper
import android.util.Log
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.EventChannel
import java.io.BufferedReader
import java.io.InputStreamReader
import java.util.concurrent.atomic.AtomicBoolean

class MainActivity: FlutterActivity()
class MainActivity : FlutterActivity() {

private val EVENT_CHANNEL = "native_logcat_stream"
private var logcatProcess: Process? = null
private val isCapturing = AtomicBoolean(false)
private val handler = Handler(Looper.getMainLooper())

override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)

// EventChannel for automatic native log streaming
EventChannel(flutterEngine.dartExecutor.binaryMessenger, EVENT_CHANNEL)
.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
startLogCapture(events)
}

override fun onCancel(arguments: Any?) {
stopLogCapture()
}
})
}

private fun startLogCapture(eventSink: EventChannel.EventSink?) {
// Use compareAndSet for thread-safe check-and-set operation
if (!isCapturing.compareAndSet(false, true)) return

Thread({
var reader: BufferedReader? = null
try {
// Capture logs only for this app with timestamp
logcatProcess = Runtime.getRuntime().exec(
arrayOf(
"logcat",
"-v", "time",
"--pid=${android.os.Process.myPid()}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

en el comentario del commit especΓ­fico ya quedΓ³ outdated pero aquΓ­ estΓ‘, lo pego de nuevo por si acaso:

AquΓ­ introduces una falla de seguridad porque el flag --pid no funciona en Android < 7.0 y es inconsistente en versiones posteriores. En muchos dispositivos esto captura logs de todas las aplicaciones del sistema, incluyendo WhatsApp, bancos, etc. No debe estar capturando logcat sin filtrar sino usar tags especΓ­ficos para la app de mostro

)
)

reader = BufferedReader(
InputStreamReader(logcatProcess?.inputStream)
)

// Use inline assignment in while loop
while (isCapturing.get()) {
val line = reader.readLine() ?: break

if (line.isNotEmpty()) {
handler.post {
eventSink?.success(line)
}
}
}
} catch (e: Exception) {
Log.e("MostroLogCapture", "Error capturing native logs", e)
handler.post {
eventSink?.error("LOGCAT_ERROR", e.message, null)
}
} finally {
reader?.close()
logcatProcess?.destroy()
// Forcibly kill if not terminated after brief wait
try {
if (logcatProcess?.waitFor(100, java.util.concurrent.TimeUnit.MILLISECONDS) == false) {
logcatProcess?.destroyForcibly()
}
} catch (e: Exception) {
Log.w("MostroLogCapture", "Error waiting for process termination", e)
}
isCapturing.set(false)
}
}, "mostro-logcat").start()
}

private fun stopLogCapture() {
isCapturing.set(false)
logcatProcess?.let { process ->
process.destroy()
// Force kill if still alive after brief wait
Thread {
try {
if (!process.waitFor(200, java.util.concurrent.TimeUnit.MILLISECONDS)) {
process.destroyForcibly()
}
} catch (e: Exception) {
Log.w("MostroLogCapture", "Process cleanup interrupted", e)
}
}.start()
}
logcatProcess = null
}

override fun onDestroy() {
stopLogCapture()
super.onDestroy()
}
}
12 changes: 12 additions & 0 deletions lib/core/app_routes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import 'package:mostro_mobile/shared/widgets/notification_listener_widget.dart';
import 'package:logger/logger.dart';
import 'package:mostro_mobile/generated/l10n.dart';

import '../features/logs/logs_screen.dart';

GoRouter createRouter(WidgetRef ref) {
return GoRouter(
navigatorKey: GlobalKey<NavigatorState>(),
Expand Down Expand Up @@ -203,6 +205,16 @@ GoRouter createRouter(WidgetRef ref) {
child: const AboutScreen(),
),
),
GoRoute(
name: 'logs',
path: '/logs',
pageBuilder: (context, state) =>
buildPageWithDefaultTransition<void>(
context: context,
state: state,
child: const LogsScreen(),
),
),
GoRoute(
path: '/walkthrough',
pageBuilder: (context, state) =>
Expand Down
1 change: 1 addition & 0 deletions lib/core/app_theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class AppTheme {
static const Color statusSettledText = Color(0xFFC084FC);
static const Color statusInactiveBackground = Color(0xFF1F2937); // Colors.grey.shade800
static const Color statusInactiveText = Color(0xFFD1D5DB); // Colors.grey.shade300
static const Color statusNative = Color(0xFFFF9800);

// Text colors
static const Color secondaryText = Color(0xFFBDBDBD); // Colors.grey.shade400
Expand Down
82 changes: 82 additions & 0 deletions lib/features/logs/logs_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import 'dart:collection';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mostro_mobile/features/logs/logs_service.dart';

// Define possible states for logs
enum LogsState {
loading,
enabled,
disabled
}

// Main service provider (reactive via ChangeNotifier)
final logsServiceProvider = ChangeNotifierProvider<LogsService>((ref) {
final service = LogsService();
ref.onDispose(() {
service.dispose();
});
return service;
});

// Provider for reactive logs list
final logsProvider = Provider<UnmodifiableListView<String>>((ref) {
final service = ref.watch(logsServiceProvider);
return service.logs;
});


// Updated to use LogsState
final logsEnabledProvider = StateNotifierProvider<LogsEnabledNotifier, LogsState>((ref) {
final service = ref.watch(logsServiceProvider);
return LogsEnabledNotifier(service);
});

// Updated to use LogsState
final nativeLogsEnabledProvider = StateNotifierProvider<NativeLogsEnabledNotifier, LogsState>((ref) {
final service = ref.watch(logsServiceProvider);
return NativeLogsEnabledNotifier(service);
});

// Updated LogsEnabledNotifier to use LogsState
class LogsEnabledNotifier extends StateNotifier<LogsState> {
final LogsService _logsService;

LogsEnabledNotifier(this._logsService) : super(LogsState.loading) {
_loadState();
}

Future<void> _loadState() async {
final isEnabled = await _logsService.isLogsEnabled();
state = isEnabled ? LogsState.enabled : LogsState.disabled;
}

Future<void> toggle(bool enabled) async {
state = LogsState.loading;
await _logsService.setLogsEnabled(enabled);
state = enabled ? LogsState.enabled : LogsState.disabled;
}

bool get isEnabled => state == LogsState.enabled;
}

// Updated NativeLogsEnabledNotifier to use LogsState
class NativeLogsEnabledNotifier extends StateNotifier<LogsState> {
final LogsService _logsService;

NativeLogsEnabledNotifier(this._logsService) : super(LogsState.loading) {
_loadState();
}

Future<void> _loadState() async {
final isEnabled = await _logsService.isNativeLogsEnabled();
state = isEnabled ? LogsState.enabled : LogsState.disabled;
}

Future<void> toggle(bool enabled) async {
state = LogsState.loading;
await _logsService.setNativeLogsEnabled(enabled);
state = enabled ? LogsState.enabled : LogsState.disabled;
}

bool get isEnabled => state == LogsState.enabled;
}
Loading
Loading