diff --git a/android/build.gradle b/android/build.gradle index b9370cc..fd1d8f9 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -25,7 +25,7 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { - compileSdkVersion 31 + compileSdkVersion 34 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 @@ -48,6 +48,6 @@ android { repositories { maven { url 'https://jitpack.io' } } dependencies { - implementation 'com.github.ankidroid:Anki-Android:api-v1.1.0' + implementation 'com.github.ankidroid:Anki-Android:v2.17.4' } diff --git a/android/src/main/kotlin/com/aaalkoud/flutter_ankidroid/FlutterAnkidroidPlugin.kt b/android/src/main/kotlin/com/aaalkoud/flutter_ankidroid/FlutterAnkidroidPlugin.kt index 163385b..6c17421 100644 --- a/android/src/main/kotlin/com/aaalkoud/flutter_ankidroid/FlutterAnkidroidPlugin.kt +++ b/android/src/main/kotlin/com/aaalkoud/flutter_ankidroid/FlutterAnkidroidPlugin.kt @@ -1,8 +1,11 @@ package com.aaalkoud.flutter_ankidroid +import android.app.Activity +import android.app.Application import android.content.Intent import android.content.Context import android.content.pm.PackageManager +import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import java.io.File @@ -13,26 +16,117 @@ import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.PluginRegistry import io.flutter.plugin.common.MethodChannel.Result +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.plugin.common.PluginRegistry.Registrar +import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding + + /** FlutterAnkidroidPlugin */ -class FlutterAnkidroidPlugin: FlutterPlugin, MethodCallHandler { - +public class FlutterAnkidroidPlugin : FlutterPlugin, MethodCallHandler, RequestPermissionsResultListener, ActivityAware { + + private var act: Activity? = null + private lateinit var channel : MethodChannel private lateinit var context: Context private lateinit var api : AddContentApi + private var ankiPermissionCode = 4321 + private var ankiPermissionName = "com.ichi2.anki.permission.READ_WRITE_DATABASE" + private var permissionRequestResult : Result? = null + + + override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - channel = MethodChannel(flutterPluginBinding.binaryMessenger, "flutter_ankidroid") + context = flutterPluginBinding.applicationContext api = AddContentApi(context) + + channel = MethodChannel(flutterPluginBinding.flutterEngine.dartExecutor, "flutter_ankidroid") channel.setMethodCallHandler(this) + + } + override fun onAttachedToActivity(binding : ActivityPluginBinding) { + act = binding.activity + binding.addRequestPermissionsResultListener(this) + } + override fun onDetachedFromActivityForConfigChanges() { + act = null; + } + override fun onReattachedToActivityForConfigChanges(binding : ActivityPluginBinding) { + act = binding.activity + binding.addRequestPermissionsResultListener(this) + } + override fun onDetachedFromActivity() { + act = null; + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray): Boolean{ + + var permissionGranted : Boolean = false + + when(requestCode) { + ankiPermissionCode -> { + if ( null != grantResults ) { + permissionGranted = grantResults.isNotEmpty() && + grantResults.get(0) == PackageManager.PERMISSION_GRANTED + } + } + } + permissionRequestResult!!.success(permissionGranted) + return permissionGranted + } + + /* + * Checks if the user already gave permission to interact with anki + * Returns true if permission is granted, false otherwise + */ + fun checkPermission(): Boolean{ + + val permission = ContextCompat.checkSelfPermission(context, ankiPermissionName) + val granted = permission == PackageManager.PERMISSION_GRANTED + + return granted + + } + + /* + * Shows a native permission request to interact with ankidroid + */ + fun requestPermission(result: Result){ + + val granted = checkPermission() + + if(granted){ + result.success(granted) + return + } + else { + ActivityCompat.requestPermissions(act!!, arrayOf(ankiPermissionName), ankiPermissionCode) + } + } override fun onMethodCall(call: MethodCall, result: Result) { - val permission = ContextCompat.checkSelfPermission(context, "com.ichi2.anki.permission.READ_WRITE_DATABASE") - if (permission != PackageManager.PERMISSION_GRANTED) { + + var granted : Boolean = checkPermission() + + if(call.method == "checkPermission"){ + result.success(granted); + return + } + else if(call.method == "requestPremission"){ + permissionRequestResult = result + requestPermission(result) + return + } + + if (!granted) { result.error("Permission to use and modify AnkiDroid database not granted!", "Permission to use and modify AnkiDroid database not granted!", null) + return } when (call.method) { @@ -80,11 +174,13 @@ class FlutterAnkidroidPlugin: FlutterPlugin, MethodCallHandler { "findDuplicateNotesWithKey" -> { val mid = call.argument("mid")!! val key = call.argument("key")!! - val dupes = api.findDuplicateNotes(mid, key).map {hashMapOf( - "id" to it.id, - "fields" to it.fields.toList(), - "tags" to it.tags.toList() - )} + val dupes = api.findDuplicateNotes(mid, key).map { + hashMapOf( + "id" to it!!.getId(), + "fields" to it!!.getFields().toList(), + "tags" to it!!.getTags().toList() + ) + } result.success(dupes) } @@ -95,15 +191,15 @@ class FlutterAnkidroidPlugin: FlutterPlugin, MethodCallHandler { val dupes = api.findDuplicateNotes(mid, keys) val list = mutableListOf>>() - for (i in 0 until dupes.size()) { - val innerList = dupes.valueAt(i) + for (i in 0 until dupes!!.size()) { + val innerList = dupes!!.valueAt(i) if (innerList != null) { list.add(innerList.map {hashMapOf( - "id" to it.id, - "fields" to it.fields.toList(), - "tags" to it.tags.toList() + "id" to it!!.getId(), + "fields" to it!!.getFields().toList(), + "tags" to it!!.getTags().toList() )}) } else { @@ -139,9 +235,9 @@ class FlutterAnkidroidPlugin: FlutterPlugin, MethodCallHandler { val note = api.getNote(noteId) result.success(hashMapOf( - "id" to note.id, - "fields" to note.fields.toList(), - "tags" to note.tags.toList() + "id" to note!!.getId(), + "fields" to note!!.getFields().toList(), + "tags" to note!!.getTags().toList() )) } @@ -182,7 +278,7 @@ class FlutterAnkidroidPlugin: FlutterPlugin, MethodCallHandler { "getFieldList" -> { val modelId = call.argument("modelId")!! - result.success(api.getFieldList(modelId).toList()) + result.success(api.getFieldList(modelId)!!.toList()) } "modelList" -> result.success(api.modelList) @@ -224,4 +320,5 @@ class FlutterAnkidroidPlugin: FlutterPlugin, MethodCallHandler { override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { channel.setMethodCallHandler(null) } + } diff --git a/example/android/build.gradle b/example/android/build.gradle index f7eb7f6..d1cb9b1 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.7.10' + ext.kotlin_version = '1.9.20' repositories { google() mavenCentral() diff --git a/example/pubspec.lock b/example/pubspec.lock index 45b5799..f629789 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.18.0" crypto: dependency: transitive description: @@ -61,14 +61,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" lints: dependency: transitive description: @@ -81,26 +73,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.9.1" - request_permission: - dependency: transitive - description: - name: request_permission - sha256: f058e75a3ff29a6f5d5ed1042673d7d4d7e5abcda12256675cf88d095dfa84f6 - url: "https://pub.dev" - source: hosted - version: "2.1.4" + version: "1.11.0" sky_engine: dependency: transitive description: flutter @@ -131,5 +115,5 @@ packages: source: hosted version: "2.1.4" sdks: - dart: ">=3.0.5 <4.0.0" + dart: ">=3.2.0-0 <4.0.0" flutter: ">=3.3.0" diff --git a/lib/flutter_ankidroid.dart b/lib/flutter_ankidroid.dart index 81f0078..cddab48 100644 --- a/lib/flutter_ankidroid.dart +++ b/lib/flutter_ankidroid.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:async/async.dart'; import 'package:flutter_isolate/flutter_isolate.dart'; -import 'package:request_permission/request_permission.dart'; import 'util/future_result.dart'; export 'util/note_info.dart' show NoteInfo; @@ -17,11 +16,15 @@ class Ankidroid { /// If you hot restart your app, the isolate won't be killed, and vscode /// will show another isolate in your call stack. not that big of a deal. - /// Btw, if anyone knows how to give the isolate a name please help + /// + /// Note: `askForPermission` needs to be called before trying to use any + /// functions of this isolate. + /// + /// Dev note: Btw, if anyone knows how to give the isolate a name please help + /// This is an open issue in the flutter_isolate package + /// https://github.com/rmawatson/flutter_isolate/issues/108 static Future createAnkiIsolate() async { WidgetsFlutterBinding.ensureInitialized(); - - await askForPermission(); final rPort = ReceivePort(); final isolate = await FlutterIsolate.spawn(_isolateFunction, rPort.sendPort); @@ -30,11 +33,18 @@ class Ankidroid { return Ankidroid._(isolate, ankiPort); } - static Future askForPermission() async { - final perms = RequestPermission.instace; - if (!await perms.hasAndroidPermission('com.ichi2.anki.permission.READ_WRITE_DATABASE')) { - await perms.requestAndroidPermission('com.ichi2.anki.permission.READ_WRITE_DATABASE'); - } + /// Ask for permission to communicate with ankidroid + /// This opens a dialog that the user needs to agree. + /// + /// Note: this needs to be called before trying to use any functions of an + /// ankidroid isolate. + static Future askForPermission() async { + + const m = MethodChannel("flutter_ankidroid"); + bool ret = await m.invokeMethod("requestPremission"); + + return ret; + } void killIsolate() => _isolate.kill(); diff --git a/lib/util/note_info.dart b/lib/util/note_info.dart index a9e18eb..0da4b7b 100644 --- a/lib/util/note_info.dart +++ b/lib/util/note_info.dart @@ -1,13 +1,19 @@ /// You can use this with functions that return Java NoteInfos or Lists of NoteInfos class NoteInfo { + final int id; + final List fields; + final List tags; + const NoteInfo(this.id, this.fields, this.tags); NoteInfo.from(Map map) : - id = map['id']! as int, fields = List.from(map['fields']!), tags = List.from(map['tags']!); + id = map['id']! as int, + fields = List.from(map['fields']!), + tags = List.from(map['tags']!); static List fromList(List> list) { return list.map((e) => NoteInfo.from(e)).toList(); diff --git a/pubspec.yaml b/pubspec.yaml index 792bea4..8708296 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,7 +12,6 @@ dependencies: sdk: flutter flutter_isolate: ^2.0.4 async: ^2.11.0 - request_permission: ^2.1.4 dev_dependencies: flutter_lints: ^2.0.0