Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

android {
compileSdkVersion 31
compileSdkVersion 34

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
Expand All @@ -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'
}

Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<String>, 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) {
Expand Down Expand Up @@ -80,11 +174,13 @@ class FlutterAnkidroidPlugin: FlutterPlugin, MethodCallHandler {
"findDuplicateNotesWithKey" -> {
val mid = call.argument<Long>("mid")!!
val key = call.argument<String>("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)
}
Expand All @@ -95,15 +191,15 @@ class FlutterAnkidroidPlugin: FlutterPlugin, MethodCallHandler {
val dupes = api.findDuplicateNotes(mid, keys)

val list = mutableListOf<List<Map<String, Any>>>()
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 {
Expand Down Expand Up @@ -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()
))
}

Expand Down Expand Up @@ -182,7 +278,7 @@ class FlutterAnkidroidPlugin: FlutterPlugin, MethodCallHandler {
"getFieldList" -> {
val modelId = call.argument<Long>("modelId")!!

result.success(api.getFieldList(modelId).toList())
result.success(api.getFieldList(modelId)!!.toList())
}

"modelList" -> result.success(api.modelList)
Expand Down Expand Up @@ -224,4 +320,5 @@ class FlutterAnkidroidPlugin: FlutterPlugin, MethodCallHandler {
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}

}
2 changes: 1 addition & 1 deletion example/android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.7.10'
ext.kotlin_version = '1.9.20'
repositories {
google()
mavenCentral()
Expand Down
30 changes: 7 additions & 23 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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"
28 changes: 19 additions & 9 deletions lib/flutter_ankidroid.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Ankidroid> createAnkiIsolate() async {
WidgetsFlutterBinding.ensureInitialized();

await askForPermission();

final rPort = ReceivePort();
final isolate = await FlutterIsolate.spawn(_isolateFunction, rPort.sendPort);
Expand All @@ -30,11 +33,18 @@ class Ankidroid {
return Ankidroid._(isolate, ankiPort);
}

static Future<void> 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<bool> askForPermission() async {

const m = MethodChannel("flutter_ankidroid");
bool ret = await m.invokeMethod("requestPremission");

return ret;

}

void killIsolate() => _isolate.kill();
Expand Down
8 changes: 7 additions & 1 deletion lib/util/note_info.dart
Original file line number Diff line number Diff line change
@@ -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<String> fields;

final List<String> tags;


const NoteInfo(this.id, this.fields, this.tags);

NoteInfo.from(Map<dynamic, dynamic> map) :
id = map['id']! as int, fields = List<String>.from(map['fields']!), tags = List<String>.from(map['tags']!);
id = map['id']! as int,
fields = List<String>.from(map['fields']!),
tags = List<String>.from(map['tags']!);

static List<NoteInfo> fromList(List<Map<dynamic, dynamic>> list) {
return list.map((e) => NoteInfo.from(e)).toList();
Expand Down
1 change: 0 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down