From e991326666d361c8e0b76c4fd30ddeb9a93ac125 Mon Sep 17 00:00:00 2001 From: Krrish Sehgal <133865424+krrish-sehgal@users.noreply.github.com> Date: Thu, 31 Oct 2024 02:18:25 +0530 Subject: [PATCH 1/5] addded paste method channel in native ios folder --- ios/Runner/AppDelegate.swift | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index b636303481..be8434e74b 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,5 +1,5 @@ -import UIKit import Flutter +import UIKit @main @objc class AppDelegate: FlutterAppDelegate { @@ -7,7 +7,36 @@ import Flutter _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { + // Register the method channel + let controller = window?.rootViewController as! FlutterViewController + let clipboardImageChannel = FlutterMethodChannel(name: "clipboard_image_channel", + binaryMessenger: controller.binaryMessenger) + + clipboardImageChannel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in + if call.method == "getClipboardImage" { + self.getClipboardImage(result: result) + } else { + result(FlutterMethodNotImplemented) + } + } + GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } -} + + private func getClipboardImage(result: FlutterResult) { + // Check if the clipboard contains an image + if let image = UIPasteboard.general.image { + // Convert the image to PNG data + if let imageData = image.pngData() { + // Encode the image data to a Base64 string + let base64String = imageData.base64EncodedString() + result(base64String) // Send the Base64 string back to Flutter + } else { + result(FlutterError(code: "NO_IMAGE", message: "Could not convert image to data", details: nil)) + } + } else { + result(FlutterError(code: "NO_IMAGE", message: "Clipboard does not contain an image", details: nil)) + } + } +} \ No newline at end of file From 3e4a8bb1e3261fff1285d5fc5c795257b574ceac Mon Sep 17 00:00:00 2001 From: Krrish Sehgal <133865424+krrish-sehgal@users.noreply.github.com> Date: Tue, 19 Nov 2024 20:30:46 +0530 Subject: [PATCH 2/5] android call block working --- android/app/build.gradle | 6 +- android/app/src/main/AndroidManifest.xml | 43 +++++- .../main/kotlin/com/apps/blt/MainActivity.kt | 121 +++++++++++---- .../com/apps/blt/NotificationManagerImpl.kt | 23 +++ .../com/apps/blt/SpamCallBlockerService.kt | 68 ++++++++ .../kotlin/com/apps/blt/SpamNumberManager.kt | 14 ++ lib/src/pages/home/home.dart | 37 +++++ lib/src/pages/home/report_bug.dart | 2 +- .../pages/spam_call_blocker/blocker_home.dart | 145 ++++++++++++++++++ .../spam_call_blocker/database_helper.dart | 60 ++++++++ lib/src/routes/routes_import.dart | 1 + lib/src/routes/routing.dart | 20 +++ macos/Flutter/GeneratedPluginRegistrant.swift | 2 +- pubspec.yaml | 1 + 14 files changed, 503 insertions(+), 40 deletions(-) create mode 100644 android/app/src/main/kotlin/com/apps/blt/NotificationManagerImpl.kt create mode 100644 android/app/src/main/kotlin/com/apps/blt/SpamCallBlockerService.kt create mode 100644 android/app/src/main/kotlin/com/apps/blt/SpamNumberManager.kt create mode 100644 lib/src/pages/spam_call_blocker/blocker_home.dart create mode 100644 lib/src/pages/spam_call_blocker/database_helper.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index a299894e4c..523fd548d0 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -36,7 +36,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.apps.blt" - minSdkVersion 21 + minSdkVersion 29 targetSdkVersion 34 multiDexEnabled true versionCode flutterVersionCode.toInteger() @@ -71,5 +71,7 @@ dependencies { implementation 'androidx.core:core-ktx:1.10.1' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.activity:activity-ktx:1.7.2' - + implementation 'org.greenrobot:eventbus:3.2.0' + implementation 'com.jakewharton.timber:timber:4.7.1' + implementation 'pub.devrel:easypermissions:3.0.0' } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index c3c463a8b4..c7f1ea9248 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,21 @@ + + + + + + + + + + + - + - - @@ -66,7 +79,6 @@ - @@ -78,7 +90,6 @@ - @@ -89,12 +100,30 @@ + + + + + + + + + - + + + + + + + + diff --git a/android/app/src/main/kotlin/com/apps/blt/MainActivity.kt b/android/app/src/main/kotlin/com/apps/blt/MainActivity.kt index ebb9a12f8a..2a9b8b6691 100644 --- a/android/app/src/main/kotlin/com/apps/blt/MainActivity.kt +++ b/android/app/src/main/kotlin/com/apps/blt/MainActivity.kt @@ -1,48 +1,111 @@ package com.apps.blt +import android.util.Log +import android.Manifest +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Bundle +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel import android.content.ClipData import android.content.ClipboardManager import android.graphics.Bitmap -import android.graphics.drawable.BitmapDrawable -import android.os.Build import android.provider.MediaStore import android.util.Base64 import androidx.annotation.NonNull -import androidx.annotation.RequiresApi -import io.flutter.embedding.android.FlutterActivity -import io.flutter.embedding.engine.FlutterEngine -import io.flutter.plugin.common.MethodChannel import java.io.ByteArrayOutputStream class MainActivity : FlutterActivity() { - private val CHANNEL = "clipboard_image_channel" + private val CHANNEL = "com.apps.blt/channel" + private val REQUEST_CODE_PERMISSIONS = 1001 + private val REQUIRED_PERMISSIONS = arrayOf( + Manifest.permission.READ_CALL_LOG, + Manifest.permission.ANSWER_PHONE_CALLS, + Manifest.permission.READ_PHONE_STATE, + "android.permission.CALL_SCREENING" + ) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + startSpamCallBlockerService() + + } + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (requestCode == REQUEST_CODE_PERMISSIONS) { + if (allPermissionsGranted()) { + startSpamCallBlockerService() + } else { + Log.d("PermissionCheck", "Permissions not granted: ${permissions.zip(grantResults.toTypedArray()).joinToString { "${it.first}: ${it.second}" }}") + // Handle the case where permissions are not granted + } + } + } + private fun allPermissionsGranted(): Boolean { + val result = REQUIRED_PERMISSIONS.all { + ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED + } + Log.d("PermissionCheck", "All permissions granted: $result") + return result + } + + private fun startSpamCallBlockerService() { + val intent = Intent(this, SpamCallBlockerService::class.java) + startService(intent) + + } override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) - MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> - if (call.method == "getClipboardImage") { - val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager - val clipData = clipboard.primaryClip - - if (clipData != null && clipData.itemCount > 0) { - val item = clipData.getItemAt(0) - - if (item.uri != null) { - val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, item.uri) - val stream = ByteArrayOutputStream() - bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream) - val byteArray = stream.toByteArray() - val base64String = Base64.encodeToString(byteArray, Base64.DEFAULT) - result.success(base64String) - } else { - result.error("NO_IMAGE", "Clipboard does not contain an image", null) - } - } else { - result.error("EMPTY_CLIPBOARD", "Clipboard is empty", null) - } + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call: MethodCall, result: MethodChannel.Result -> + when (call.method) { + "getClipboardImage" -> handleClipboardImage(result) + "updateSpamList" -> handleSpamList(call, result) + else -> result.notImplemented() + } + } + } + private fun handleSpamList(call: MethodCall, result: MethodChannel.Result) { + val numbers = call.argument>("numbers") + + if (numbers != null) { + SpamNumberManager.updateSpamList(numbers) + result.success("Spam list updated successfully!") + } else { + result.error("INVALID_ARGUMENT", "Numbers list is null", null) + } + } + + private fun handleClipboardImage(result: MethodChannel.Result) { + val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager + val clipData = clipboard.primaryClip + + if (clipData != null && clipData.itemCount > 0) { + val item = clipData.getItemAt(0) + + if (item.uri != null) { + val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, item.uri) + val stream = ByteArrayOutputStream() + bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream) + val byteArray = stream.toByteArray() + val base64String = Base64.encodeToString(byteArray, Base64.DEFAULT) + result.success(base64String) } else { - result.notImplemented() + result.error("NO_IMAGE", "Clipboard does not contain an image", null) } + } else { + result.error("EMPTY_CLIPBOARD", "Clipboard is empty", null) } } } diff --git a/android/app/src/main/kotlin/com/apps/blt/NotificationManagerImpl.kt b/android/app/src/main/kotlin/com/apps/blt/NotificationManagerImpl.kt new file mode 100644 index 0000000000..0eeaa6d301 --- /dev/null +++ b/android/app/src/main/kotlin/com/apps/blt/NotificationManagerImpl.kt @@ -0,0 +1,23 @@ +package com.apps.blt + +import android.content.Context +import android.os.Looper +import android.widget.Toast +interface NotificationManager { + fun showToastNotification(context: Context, message: String) +} + +class NotificationManagerImpl : NotificationManager { + override fun showToastNotification(context: Context, message: String) { + val t = Thread { + try { + Looper.prepare() + Toast.makeText(context.applicationContext, message, Toast.LENGTH_LONG).show() + Looper.loop() + } catch (e: Exception) { + e.printStackTrace() + } + } + t.start() + } +} diff --git a/android/app/src/main/kotlin/com/apps/blt/SpamCallBlockerService.kt b/android/app/src/main/kotlin/com/apps/blt/SpamCallBlockerService.kt new file mode 100644 index 0000000000..0ace07a94e --- /dev/null +++ b/android/app/src/main/kotlin/com/apps/blt/SpamCallBlockerService.kt @@ -0,0 +1,68 @@ +package com.apps.blt + +import android.telecom.CallScreeningService +import android.telecom.Call +import android.util.Log +import org.greenrobot.eventbus.EventBus +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import android.net.Uri + +class MessageEvent(val message: String) {} + +class SpamCallBlockerService : CallScreeningService() { + private val notificationManager = NotificationManagerImpl() + + override fun onCreate() { + super.onCreate() + Log.d("SpamCallBlockerService", "Service started") + } + + override fun onScreenCall(callDetails: Call.Details) { + Log.d("SpamCallBlockerService", "onScreenCall triggered") + val phoneNumber = getPhoneNumber(callDetails) + Log.d("SpamCallBlockerService", "Intercepted call from: $phoneNumber") + var response = CallResponse.Builder() + response = handlePhoneCall(response, phoneNumber) + + respondToCall(callDetails, response.build()) + logCallInterception(phoneNumber, response.build()) + } + + private fun handlePhoneCall( + response: CallResponse.Builder, + phoneNumber: String + ): CallResponse.Builder { + if (SpamNumberManager.isSpamNumber(phoneNumber)) { + response.apply { + setRejectCall(true) + setDisallowCall(true) + setSkipCallLog(false) + displayToast(String.format("Rejected call from %s", phoneNumber)) + } + } else { + displayToast(String.format("Incoming call from %s", phoneNumber)) + } + return response + } + + private fun getPhoneNumber(callDetails: Call.Details): String { + return callDetails.handle.toString().removeTelPrefix().parseCountryCode() + } + + private fun displayToast(message: String) { + notificationManager.showToastNotification(applicationContext, message) + EventBus.getDefault().post(MessageEvent(message)) + } + + private fun logCallInterception(phoneNumber: String, response: CallResponse) { + val currentTime = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(Date()) + val action = "action" + val logMessage = "[$currentTime] $action call from $phoneNumber" + Log.d("SpamCallBlockerService", logMessage) + } + + fun String.removeTelPrefix() = this.replace("tel:", "") + fun String.parseCountryCode(): String = Uri.decode(this) +} diff --git a/android/app/src/main/kotlin/com/apps/blt/SpamNumberManager.kt b/android/app/src/main/kotlin/com/apps/blt/SpamNumberManager.kt new file mode 100644 index 0000000000..b125bda352 --- /dev/null +++ b/android/app/src/main/kotlin/com/apps/blt/SpamNumberManager.kt @@ -0,0 +1,14 @@ +package com.apps.blt + +object SpamNumberManager { + private val spamNumbers = mutableSetOf() + + fun updateSpamList(numbers: List) { + spamNumbers.clear() + spamNumbers.addAll(numbers) + } + + fun isSpamNumber(number: String): Boolean { + return spamNumbers.contains(number) + } +} diff --git a/lib/src/pages/home/home.dart b/lib/src/pages/home/home.dart index a81ad7c1c0..dbd94ec053 100644 --- a/lib/src/pages/home/home.dart +++ b/lib/src/pages/home/home.dart @@ -424,6 +424,43 @@ class _HomeState extends ConsumerState { }), ), ), + ListTile( + title: Container( + width: double.infinity, + height: 50, + child: Builder(builder: (context) { + return TextButton( + child: Text( + "Spam Call Blocker", + style: GoogleFonts.ubuntu( + textStyle: TextStyle( + color: Colors.white, + fontSize: 20, + ), + ), + ), + style: ButtonStyle( + shape: WidgetStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ), + ), + backgroundColor: WidgetStateProperty.all( + isDarkMode.isDarkMode + ? Color.fromRGBO(126, 33, 58, 1) + : Color(0xFFDC4654), + ), + ), + onPressed: () async { + Navigator.pushNamed( + context, + RouteManager.spamCallBlockerPage, + ); + }, + ); + }), + ), + ), // Sizzle Button ListTile( title: Container( diff --git a/lib/src/pages/home/report_bug.dart b/lib/src/pages/home/report_bug.dart index f6d96b1724..ed1fd3e356 100644 --- a/lib/src/pages/home/report_bug.dart +++ b/lib/src/pages/home/report_bug.dart @@ -76,7 +76,7 @@ class _ReportFormState extends ConsumerState { bool showLabel = false; List _labels = []; late List _labelsState; - static const platform = MethodChannel('clipboard_image_channel'); + static const platform = MethodChannel('com.apps.blt/channel'); bool _isSnackBarVisible = false; void showSnackBar(BuildContext context, String message) { diff --git a/lib/src/pages/spam_call_blocker/blocker_home.dart b/lib/src/pages/spam_call_blocker/blocker_home.dart new file mode 100644 index 0000000000..2cf56c03a9 --- /dev/null +++ b/lib/src/pages/spam_call_blocker/blocker_home.dart @@ -0,0 +1,145 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'database_helper.dart'; // Your DatabaseHelper class for SQLite operations + +class SpamCallBlockerPage extends StatefulWidget { + @override + _SpamCallBlockerPageState createState() => _SpamCallBlockerPageState(); +} + +class _SpamCallBlockerPageState extends State { + final List _numbers = []; + final TextEditingController _controller = TextEditingController(); + static const platform = MethodChannel('com.apps.blt/channel'); // MethodChannel for native communication + + @override + void initState() { + super.initState(); + _loadNumbers(); + } + + Future _loadNumbers() async { + try { + final numbers = await DatabaseHelper.getNumbers(); + setState(() { + _numbers.clear(); + _numbers.addAll(numbers); + }); + _sendSpamListToNative(); + } catch (e) { + _showError("Failed to load numbers: $e"); + } + } + + Future _addNumber() async { + final newNumber = _controller.text.trim(); + if (newNumber.isEmpty || !_validatePhoneNumber(newNumber)) { + _showError("Please enter a valid phone number."); + return; + } + + if (_numbers.contains(newNumber)) { + _showError("This number is already in the list."); + return; + } + + setState(() { + _numbers.add(newNumber); + _controller.clear(); + }); + + try { + await DatabaseHelper.insertNumber(newNumber); + _sendSpamListToNative(); + } catch (e) { + _showError("Failed to add number: $e"); + } + } + + Future _removeNumber(String number) async { + setState(() { + _numbers.remove(number); + }); + + try { + await DatabaseHelper.deleteNumber(number); + _sendSpamListToNative(); + } catch (e) { + _showError("Failed to remove number: $e"); + } + } + + Future _sendSpamListToNative() async { + try { + var response = await platform.invokeMethod('updateSpamList', { + 'numbers': _numbers, + }); + print(response); + } catch (e) { + _showError("Failed to update spam list on the native side: $e"); + } + } + + bool _validatePhoneNumber(String number) { + final regex = RegExp(r'^\+?[0-9]{10,15}$'); // Allows country code (e.g., +91) + return regex.hasMatch(number); + } + + void _showError(String message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + backgroundColor: Colors.red, + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Spam Call Blocker'), + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + // Input Field for Adding Numbers + TextField( + controller: _controller, + decoration: InputDecoration( + labelText: 'Enter number', + border: OutlineInputBorder(), + suffixIcon: IconButton( + icon: Icon(Icons.add), + onPressed: _addNumber, + ), + ), + keyboardType: TextInputType.phone, + ), + SizedBox(height: 20), + Expanded( + child: _numbers.isEmpty + ? Center(child: Text("No numbers added.")) + : ListView.builder( + itemCount: _numbers.length, + itemBuilder: (context, index) { + return ListTile( + title: Text( + _numbers[index], + style: TextStyle(fontSize: 16), + ), + trailing: IconButton( + icon: Icon(Icons.remove_circle, color: Colors.red), + onPressed: () => _removeNumber(_numbers[index]), + ), + ); + }, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/src/pages/spam_call_blocker/database_helper.dart b/lib/src/pages/spam_call_blocker/database_helper.dart new file mode 100644 index 0000000000..3e96e65af7 --- /dev/null +++ b/lib/src/pages/spam_call_blocker/database_helper.dart @@ -0,0 +1,60 @@ +import 'package:sqflite/sqflite.dart'; +import 'package:path/path.dart'; + +class DatabaseHelper { + static Database? _database; + + // Create a singleton pattern + static Future get database async { + if (_database != null) return _database!; + + // If the database doesn't exist, create it + _database = await _initDB(); + return _database!; + } + + // Initialize the database + static Future _initDB() async { + String path = join(await getDatabasesPath(), 'spam_numbers.db'); + + return await openDatabase( + path, + onCreate: (db, version) { + return db.execute( + 'CREATE TABLE numbers(id INTEGER PRIMARY KEY AUTOINCREMENT, number TEXT)', + ); + }, + version: 1, + ); + } + + // Insert a number into the database + static Future insertNumber(String number) async { + final db = await database; + await db.insert( + 'numbers', + {'number': number}, + conflictAlgorithm: ConflictAlgorithm.replace, + ); + } + + // Get all numbers from the database + static Future> getNumbers() async { + final db = await database; + final List> maps = await db.query('numbers'); + + return List.generate(maps.length, (i) { + return maps[i]['number'] as String; + }); + } + + // Delete a number from the database + static Future deleteNumber(String number) async { + final db = await database; + await db.delete( + 'numbers', + where: 'number = ?', + whereArgs: [number], + ); + } +} diff --git a/lib/src/routes/routes_import.dart b/lib/src/routes/routes_import.dart index 9cd1f53792..bb71d8b558 100644 --- a/lib/src/routes/routes_import.dart +++ b/lib/src/routes/routes_import.dart @@ -34,3 +34,4 @@ export 'package:blt/src/pages/contributors_info.dart'; export 'package:blt/src/pages/drawer/projects.dart'; export 'package:blt/src/pages/sizzle/sizzle_login.dart'; export 'package:blt/src/pages/sizzle/sizzle_home.dart'; +export '../pages/spam_call_blocker/blocker_home.dart'; diff --git a/lib/src/routes/routing.dart b/lib/src/routes/routing.dart index 457f733645..f4f5d13751 100644 --- a/lib/src/routes/routing.dart +++ b/lib/src/routes/routing.dart @@ -26,6 +26,7 @@ class RouteManager { static const String closedIssues = "/closedIssues"; static const String chatBotPage = "/chatBot"; static const String sponsorPage = "/sponsor"; + static const String spamCallBlockerPage = "/spamCallBlocker"; static const String showBugHunt = "/showBugHunt"; static const String showPrevBugHunt = "/showPrevBugHunt"; static const String bugHuntDescPageHunt = "/bugHuntDescPage"; @@ -479,6 +480,25 @@ class RouteManager { }, transitionDuration: const Duration(milliseconds: 750), ); + case spamCallBlockerPage: + return PageRouteBuilder( + pageBuilder: (context, animation, secondaryAnimation) => + SpamCallBlockerPage(), + transitionsBuilder: (context, animation, secondaryAnimation, child) { + const begin = Offset(1.0, 0); + const end = Offset.zero; + const curve = Curves.ease; + + var tween = + Tween(begin: begin, end: end).chain(CurveTween(curve: curve)); + + return SlideTransition( + position: animation.drive(tween), + child: child, + ); + }, + transitionDuration: const Duration(milliseconds: 750), + ); case chatBotPage: return PageRouteBuilder( diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index a443e6532c..bc31de5d5c 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -14,7 +14,7 @@ import pasteboard import path_provider_foundation import sentry_flutter import shared_preferences_foundation -import sqflite +import sqflite_darwin import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { diff --git a/pubspec.yaml b/pubspec.yaml index 3b4d121003..42aef5677e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,6 +39,7 @@ dependencies: permission_handler: ^11.3.1 awesome_notifications: ^0.9.3+1 local_notifier: ^0.1.6 + sqflite: ^2.4.1 dev_dependencies: flutter_launcher_icons: ">=0.9.0 <0.13.0" From cdaaa45d001df5c2344087f4b6946a3bb762b087 Mon Sep 17 00:00:00 2001 From: Krrish Sehgal <133865424+krrish-sehgal@users.noreply.github.com> Date: Tue, 19 Nov 2024 20:49:12 +0530 Subject: [PATCH 3/5] fix ios image paste --- ios/Runner/AppDelegate.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index be8434e74b..90da482d35 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -9,7 +9,7 @@ import UIKit ) -> Bool { // Register the method channel let controller = window?.rootViewController as! FlutterViewController - let clipboardImageChannel = FlutterMethodChannel(name: "clipboard_image_channel", + let clipboardImageChannel = FlutterMethodChannel(name: "com.apps.blt/channel", binaryMessenger: controller.binaryMessenger) clipboardImageChannel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in From 2cd0aaa14aae7f7ecff002d3ff991dae2362e3a9 Mon Sep 17 00:00:00 2001 From: Krrish Sehgal <133865424+krrish-sehgal@users.noreply.github.com> Date: Tue, 19 Nov 2024 20:56:08 +0530 Subject: [PATCH 4/5] reverted minsdk --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 523fd548d0..3217b5fed1 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -36,7 +36,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.apps.blt" - minSdkVersion 29 + minSdkVersion 21 targetSdkVersion 34 multiDexEnabled true versionCode flutterVersionCode.toInteger() From e216b2c25c8fa6850546ac016a174cd2914f3af2 Mon Sep 17 00:00:00 2001 From: Krrish Sehgal <133865424+krrish-sehgal@users.noreply.github.com> Date: Tue, 26 Nov 2024 16:36:20 +0530 Subject: [PATCH 5/5] fixed mutableSetOf --- android/app/src/main/kotlin/com/apps/blt/SpamNumberManager.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/android/app/src/main/kotlin/com/apps/blt/SpamNumberManager.kt b/android/app/src/main/kotlin/com/apps/blt/SpamNumberManager.kt index b125bda352..c9e25ea5d4 100644 --- a/android/app/src/main/kotlin/com/apps/blt/SpamNumberManager.kt +++ b/android/app/src/main/kotlin/com/apps/blt/SpamNumberManager.kt @@ -1,5 +1,7 @@ package com.apps.blt +import kotlin.collections.mutableSetOf + object SpamNumberManager { private val spamNumbers = mutableSetOf()