Update: 2026-05-08 06:19:56

This commit is contained in:
Hamza-Ayed
2026-05-08 06:19:56 +03:00
parent df92a44878
commit 753497649a
18 changed files with 1310 additions and 21 deletions

View File

@@ -1,3 +1,6 @@
import java.util.Properties
import java.io.FileInputStream
plugins {
id("com.android.application")
// START: FlutterFire Configuration
@@ -8,10 +11,17 @@ plugins {
id("dev.flutter.flutter-gradle-plugin")
}
// Load keystore properties from key.properties file
val keystorePropertiesFile = rootProject.file("key.properties")
val keystoreProperties = Properties()
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
android {
namespace = "com.example.musadaq_app"
namespace = "com.musadaq.app"
compileSdk = flutter.compileSdkVersion
ndkVersion = "27.0.12077973"
ndkVersion = "28.2.13676358"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
@@ -22,11 +32,19 @@ android {
jvmTarget = JavaVersion.VERSION_11.toString()
}
signingConfigs {
create("release") {
if (keystorePropertiesFile.exists()) {
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
storeFile = file(keystoreProperties["storeFile"] as String)
storePassword = keystoreProperties["storePassword"] as String
}
}
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.musadaq_app"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
applicationId = "com.musadaq.app"
minSdk = 24
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
@@ -35,9 +53,17 @@ android {
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
signingConfig = if (keystorePropertiesFile.exists()) {
signingConfigs.getByName("release")
} else {
signingConfigs.getByName("debug")
}
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}

View File

@@ -7,9 +7,9 @@
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:512384487867:android:eac271c0b0ea64b708749e",
"mobilesdk_app_id": "1:512384487867:android:6098e34775ad6c7708749e",
"android_client_info": {
"package_name": "com.example.musadaq_app"
"package_name": "com.musadaq.app"
}
},
"oauth_client": [],

View File

@@ -0,0 +1,22 @@
# Flutter ProGuard Rules for Musadaq
# Keep Flutter engine
-keep class io.flutter.** { *; }
-keep class io.flutter.plugins.** { *; }
# Keep Firebase
-keep class com.google.firebase.** { *; }
# Keep Dio
-keep class io.github.nicekun.** { *; }
-dontwarn okhttp3.**
-dontwarn okio.**
# Keep CameraAwesome
-keep class com.apparence.camerawesome.** { *; }
# Keep ObjectBox
-keep class io.objectbox.** { *; }
# Ignore missing Play Core classes referenced by Flutter Engine
-dontwarn com.google.android.play.core.**

View File

@@ -16,7 +16,7 @@
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
<application
android:label="musadaq_app"
android:label="مُصادَق"
android:name="${applicationName}"
android:icon="@mipmap/launcher_icon"
android:enableOnBackInvokedCallback="true">

View File

@@ -1,4 +1,4 @@
package com.example.musadaq_app
package com.musadaq.app
import io.flutter.embedding.android.FlutterFragmentActivity

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">مُصادَق</string>
</resources>

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"flutter":{"platforms":{"android":{"default":{"projectId":"musadaq-c12ca","appId":"1:512384487867:android:eac271c0b0ea64b708749e","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"musadaq-c12ca","appId":"1:512384487867:ios:03bd28c6088a4aa008749e","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"musadaq-c12ca","configurations":{"android":"1:512384487867:android:eac271c0b0ea64b708749e","ios":"1:512384487867:ios:03bd28c6088a4aa008749e"}}}}}}
{"flutter":{"platforms":{"android":{"default":{"projectId":"musadaq-c12ca","appId":"1:512384487867:android:6098e34775ad6c7708749e","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"musadaq-c12ca","appId":"1:512384487867:ios:c743b83778682d8308749e","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"musadaq-c12ca","configurations":{"android":"1:512384487867:android:6098e34775ad6c7708749e","ios":"1:512384487867:ios:c743b83778682d8308749e"}}}}}}

View File

@@ -9,7 +9,7 @@
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.example.musadaqApp</string>
<string>com.musadaq.app</string>
<key>PROJECT_ID</key>
<string>musadaq-c12ca</string>
<key>STORAGE_BUCKET</key>
@@ -25,6 +25,6 @@
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:512384487867:ios:03bd28c6088a4aa008749e</string>
<string>1:512384487867:ios:c743b83778682d8308749e</string>
</dict>
</plist>

View File

@@ -0,0 +1,237 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:freerasp/freerasp.dart';
import 'package:get/get.dart';
/// Device Security Service
/// Enterprise-grade jailbreak/root/tamper detection for Musadaq using freeRASP.
///
/// Detects: root, jailbreak, debugger, emulator, hooking frameworks,
/// tampered apps, unofficial stores, missing passcode.
///
/// Policy: On detection → block app with security warning (no data access).
class DeviceSecurityService extends GetxService {
final isCompromised = false.obs;
final threatDetails = <String>[].obs;
final securityChecked = false.obs;
@override
void onInit() {
super.onInit();
_initSecurity();
}
/// Initialize freeRASP
Future<void> _initSecurity() async {
try {
await _initFreeRASP();
} catch (e) {
debugPrint('[Security] freeRASP init failed: $e');
}
securityChecked.value = true;
}
/// Initialize freeRASP with full threat callbacks
Future<void> _initFreeRASP() async {
final callback = ThreatCallback(
onAppIntegrity: () => _onThreatDetected('تلاعب بالتطبيق'),
onObfuscationIssues: () => _onThreatDetected('عدم وجود تشفير الكود'),
onDebug: () => _onThreatDetected('وضع التصحيح مفعّل'),
onDeviceBinding: () => _onThreatDetected('تغيير جهاز غير مصرح'),
onDeviceID: () => _onThreatDetected('معرّف جهاز مزوّر'),
onHooks: () => _onThreatDetected('أدوات اختراق (Hooking)'),
onPasscode: () => _onThreatDetected('الجهاز بدون قفل شاشة'),
onPrivilegedAccess: () => _onThreatDetected('جهاز مروّت (Root/Jailbreak)'),
onSecureHardwareNotAvailable: () =>
_onThreatDetected('عدم توفر عتاد آمن'),
onSimulator: () => _onThreatDetected('محاكي (Emulator)'),
onUnofficialStore: () => _onThreatDetected('تثبيت من متجر غير رسمي'),
);
// Configure talsec
final config = TalsecConfig(
androidConfig: AndroidConfig(
packageName: 'com.musadaq.app',
signingCertHashes: ['4F:10:B4:E9:29:E0:5E:0A:3E:AE:B0:31:4C:C1:3A:DB:CB:E6:FF:DF:6A:F5:85:FC:68:FE:C1:E4:B6:29:6B:6F'],
supportedStores: ['com.android.vending'], // Google Play only
),
iosConfig: IOSConfig(
bundleIds: ['com.musadaq.app'],
teamId: 'REPLACE_WITH_YOUR_TEAM_ID',
),
watcherMail: 'security@musadaq.jo',
isProd: true,
);
await Talsec.instance.start(config);
Talsec.instance.attachListener(callback);
debugPrint('[Security] freeRASP initialized');
}
/// Handle threat detection
void _onThreatDetected(String threat) {
debugPrint('[Security] ⚠️ THREAT: $threat');
threatDetails.add(threat);
isCompromised.value = true;
// Block the app immediately
_showSecurityBlock();
}
/// Show a non-dismissible security warning that blocks the app
void _showSecurityBlock() {
// Only show once
if (Get.isDialogOpen == true) return;
Get.dialog(
PopScope(
canPop: false,
child: Scaffold(
backgroundColor: const Color(0xFF1A1A2E),
body: SafeArea(
child: Center(
child: Padding(
padding: const EdgeInsets.all(32),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Shield icon
Container(
width: 100,
height: 100,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.red.withValues(alpha: 0.15),
),
child: const Icon(
Icons.shield_rounded,
size: 56,
color: Colors.red,
),
),
const SizedBox(height: 32),
// Title
const Text(
'تحذير أمني',
style: TextStyle(
color: Colors.white,
fontSize: 28,
fontWeight: FontWeight.bold,
fontFamily: 'El Messiri',
),
),
const SizedBox(height: 16),
// Message
const Text(
'تم اكتشاف أن هذا الجهاز غير آمن.\n'
'لحماية بياناتك المالية والضريبية،\n'
'لا يمكن تشغيل مُصادَق على هذا الجهاز.',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white70,
fontSize: 15,
height: 1.6,
),
),
const SizedBox(height: 24),
// Threat details
Obx(() => Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.red.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.red.withValues(alpha: 0.3)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'التهديدات المكتشفة:',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
fontSize: 13,
),
),
const SizedBox(height: 8),
...threatDetails.map((t) => Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Row(
children: [
const Icon(Icons.warning_amber,
color: Colors.red, size: 16),
const SizedBox(width: 8),
Text(t,
style: const TextStyle(
color: Colors.white60,
fontSize: 13)),
],
),
)),
],
),
)),
const SizedBox(height: 32),
// Close button (exits the app)
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: () => SystemNavigator.pop(),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
),
icon: const Icon(Icons.exit_to_app,
color: Colors.white),
label: const Text(
'إغلاق التطبيق',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
),
const SizedBox(height: 16),
// Help text
const Text(
'إذا كنت تعتقد أن هذا خطأ، تواصل مع\nsupport@musadaq.jo',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white38, fontSize: 12),
),
],
),
),
),
),
),
),
barrierDismissible: false,
);
}
/// Quick check if device is safe (for use in API calls)
bool get isSafe => !isCompromised.value;
/// Get security status for display
Map<String, dynamic> getSecurityStatus() {
return {
'is_safe': isSafe,
'checked': securityChecked.value,
'threats': threatDetails.toList(),
'threat_count': threatDetails.length,
};
}
}

View File

@@ -0,0 +1,147 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:shorebird_code_push/shorebird_code_push.dart';
/// Shorebird Code Push Service
/// Handles OTA (Over-The-Air) updates for Dart code.
/// Checks for patches on app launch and notifies users when updates are available.
class ShorebirdUpdateService extends GetxService {
final _updater = ShorebirdUpdater();
final isUpdateAvailable = false.obs;
final isDownloading = false.obs;
final currentPatchNumber = 0.obs;
@override
void onInit() {
super.onInit();
_checkForUpdate();
}
/// Check if Shorebird is available (only works with shorebird release builds)
bool get isShorebirdAvailable => _updater.isAvailable;
/// Get current patch number
Future<void> loadCurrentPatch() async {
if (!isShorebirdAvailable) return;
try {
final patch = await _updater.readCurrentPatch();
currentPatchNumber.value = patch?.number ?? 0;
} catch (_) {}
}
/// Check for updates on launch
Future<void> _checkForUpdate() async {
if (!isShorebirdAvailable) {
debugPrint('[Shorebird] Not available (debug build)');
return;
}
try {
await loadCurrentPatch();
debugPrint('[Shorebird] Current patch: ${currentPatchNumber.value}');
final status = await _updater.checkForUpdate();
if (status == UpdateStatus.outdated) {
isUpdateAvailable.value = true;
debugPrint('[Shorebird] Update available!');
_showUpdateDialog();
} else {
debugPrint('[Shorebird] App is up to date');
}
} catch (e) {
debugPrint('[Shorebird] Error checking for update: $e');
}
}
/// Download and apply the update
Future<void> downloadUpdate() async {
if (!isShorebirdAvailable) return;
isDownloading.value = true;
try {
await _updater.update();
isDownloading.value = false;
isUpdateAvailable.value = false;
// Show restart prompt
Get.defaultDialog(
title: '✅ تم التحديث',
middleText: 'تم تحميل التحديث بنجاح.\nأعد تشغيل التطبيق لتطبيق التغييرات.',
textConfirm: 'حسناً',
confirmTextColor: Colors.white,
buttonColor: const Color(0xFF0F4C81),
onConfirm: () => Get.back(),
titleStyle: const TextStyle(fontWeight: FontWeight.bold),
radius: 14,
);
} catch (e) {
isDownloading.value = false;
debugPrint('[Shorebird] Download failed: $e');
Get.snackbar(
'خطأ',
'فشل تحميل التحديث',
backgroundColor: Colors.red,
colorText: Colors.white,
);
}
}
/// Show update available dialog
void _showUpdateDialog() {
Get.defaultDialog(
title: '🔄 تحديث متاح',
middleText: 'يتوفر تحديث جديد لمُصادَق.\nهل تريد تحميله الآن؟',
textConfirm: 'تحديث',
textCancel: 'لاحقاً',
confirmTextColor: Colors.white,
buttonColor: const Color(0xFF0F4C81),
onConfirm: () {
Get.back();
downloadUpdate();
},
titleStyle: const TextStyle(fontWeight: FontWeight.bold),
radius: 14,
);
}
/// Manual check (can be called from settings)
Future<void> manualCheckForUpdate() async {
if (!isShorebirdAvailable) {
Get.snackbar(
'تنبيه',
'التحديث التلقائي متاح فقط في نسخة الإصدار',
backgroundColor: Colors.orange,
colorText: Colors.white,
);
return;
}
Get.snackbar(
'جارٍ الفحص...',
'يتم البحث عن تحديثات',
backgroundColor: const Color(0xFF0F4C81),
colorText: Colors.white,
showProgressIndicator: true,
duration: const Duration(seconds: 2),
);
try {
final status = await _updater.checkForUpdate();
if (status == UpdateStatus.outdated) {
isUpdateAvailable.value = true;
_showUpdateDialog();
} else {
Get.snackbar(
'✅ محدّث',
'التطبيق يعمل بأحدث إصدار (Patch ${currentPatchNumber.value})',
backgroundColor: const Color(0xFF10B981),
colorText: Colors.white,
);
}
} catch (e) {
Get.snackbar('خطأ', 'فشل البحث عن تحديثات',
backgroundColor: Colors.red, colorText: Colors.white);
}
}
}

View File

@@ -3,6 +3,8 @@ import 'package:get/get.dart';
import '../controllers/settings_controller.dart';
import '../../../app/routes/app_pages.dart';
import '../../../core/utils/app_snackbar.dart';
import '../../../core/services/shorebird_update_service.dart';
import '../../../core/services/device_security_service.dart';
class SettingsView extends GetView<SettingsController> {
const SettingsView({super.key});
@@ -148,6 +150,18 @@ class SettingsView extends GetView<SettingsController> {
isDark: isDark,
),
const Divider(height: 1),
Builder(builder: (_) {
final security = Get.find<DeviceSecurityService>();
return Obx(() => _buildInfoTile(
icon: security.isSafe
? Icons.verified_user_rounded
: Icons.gpp_bad_rounded,
title: 'حالة الأمان',
trailing: security.isSafe ? '✅ آمن' : '⚠️ غير آمن',
isDark: isDark,
));
}),
const Divider(height: 1),
_buildInfoTile(
icon: Icons.diamond_rounded,
title: 'الاشتراكات والباقات',
@@ -170,6 +184,17 @@ class SettingsView extends GetView<SettingsController> {
isDark: isDark,
onTap: () {},
),
const Divider(height: 1),
_buildInfoTile(
icon: Icons.system_update_rounded,
title: 'البحث عن تحديثات',
trailing: 'فحص →',
isDark: isDark,
onTap: () {
final shorebird = Get.find<ShorebirdUpdateService>();
shorebird.manualCheckForUpdate();
},
),
]),
const SizedBox(height: 32),
_buildLogoutButton(),

View File

@@ -51,7 +51,7 @@ class DefaultFirebaseOptions {
static const FirebaseOptions android = FirebaseOptions(
apiKey: 'AIzaSyB7Gc_RNvFaFCsuN5acHK2SNkY5iMDecqk',
appId: '1:512384487867:android:eac271c0b0ea64b708749e',
appId: '1:512384487867:android:6098e34775ad6c7708749e',
messagingSenderId: '512384487867',
projectId: 'musadaq-c12ca',
storageBucket: 'musadaq-c12ca.firebasestorage.app',
@@ -59,10 +59,11 @@ class DefaultFirebaseOptions {
static const FirebaseOptions ios = FirebaseOptions(
apiKey: 'AIzaSyBLKc35OqzY6oQA5507E2uHCCHQbRWAC_M',
appId: '1:512384487867:ios:03bd28c6088a4aa008749e',
appId: '1:512384487867:ios:c743b83778682d8308749e',
messagingSenderId: '512384487867',
projectId: 'musadaq-c12ca',
storageBucket: 'musadaq-c12ca.firebasestorage.app',
iosBundleId: 'com.example.musadaqApp',
iosBundleId: 'com.musadaq.app',
);
}
}

View File

@@ -6,6 +6,8 @@ import 'app/routes/app_pages.dart';
import 'core/services/push_notification_service.dart';
import 'core/services/upload_progress_service.dart';
import 'core/services/home_widget_service.dart';
import 'core/services/shorebird_update_service.dart';
import 'core/services/device_security_service.dart';
import 'app/theme/app_theme.dart';
@pragma('vm:entry-point')
@@ -24,9 +26,13 @@ void main() async {
// 2. Register background handler
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
// 3. Register global services
// 3. Security check (MUST be first)
Get.put(DeviceSecurityService(), permanent: true);
// 4. Register global services
Get.put(UploadProgressService(), permanent: true);
Get.put(HomeWidgetService(), permanent: true);
Get.put(ShorebirdUpdateService(), permanent: true);
runApp(const MusadaqApp());
}

View File

@@ -592,6 +592,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
freerasp:
dependency: "direct main"
description:
name: freerasp
sha256: "76a3fb6f8e3fdd7d83e224866998523e7fb79d5779321983e484a6cfbf4b01b5"
url: "https://pub.dev"
source: hosted
version: "6.12.0"
frontend_server_client:
dependency: transitive
description:
@@ -1264,6 +1272,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.0"
shorebird_code_push:
dependency: "direct main"
description:
name: shorebird_code_push
sha256: "55d5eea098a4293199d22dfd263ce1d6c10820ceb42c2acb950f971a8bcee0ce"
url: "https://pub.dev"
source: hosted
version: "2.0.6"
sky_engine:
dependency: transitive
description: flutter

View File

@@ -63,6 +63,12 @@ dependencies:
firebase_core: ^4.7.0
firebase_messaging: ^16.2.0
# ─── Code Push (OTA Updates) ────────────────────────
shorebird_code_push: ^2.0.0
# ─── Security (Root/Jailbreak/Tamper Detection) ─────
freerasp: ^6.6.0
dev_dependencies:
flutter_test:
sdk: flutter
@@ -85,6 +91,7 @@ flutter_launcher_icons:
flutter:
uses-material-design: true
assets:
- shorebird.yaml
- assets/images/logo.jpg
- assets/images/onboarding_1.png
- assets/images/onboarding_2.png

View File

@@ -0,0 +1,14 @@
# This file is used to configure the Shorebird updater used by your app.
# Learn more at https://docs.shorebird.dev
# This file does not contain any sensitive information and should be checked into version control.
# Your app_id is the unique identifier assigned to your app.
# It is used to identify your app when requesting patches from Shorebird's servers.
# It is not a secret and can be shared publicly.
app_id: 34bbf488-9870-4717-b6c7-408485fcf264
# auto_update controls if Shorebird should automatically update in the background on launch.
# If auto_update: false, you will need to use package:shorebird_code_push to trigger updates.
# https://pub.dev/packages/shorebird_code_push
# Uncomment the following line to disable automatic updates.
# auto_update: false