From 1a7678ee0f43ebe2308cbc9c31eeac1b7385a7ed Mon Sep 17 00:00:00 2001 From: My-Responsitories <107370289+My-Responsitories@users.noreply.github.com> Date: Sat, 30 May 2026 12:10:35 +0000 Subject: [PATCH] refa: jni (#2244) * refa: jni * refa: jni --- .../com/example/piliplus/AndroidHelper.java | 84 +++++ .../com/example/piliplus/MainActivity.kt | 137 +------ .../gradle/wrapper/gradle-wrapper.properties | 2 +- lib/main.dart | 6 - lib/pages/about/view.dart | 3 +- lib/pages/main/view.dart | 5 +- lib/pages/member/view.dart | 14 +- lib/pages/music/view.dart | 28 +- lib/utils/android/android_helper.dart | 19 + lib/utils/android/bindings.g.dart | 342 ++++++++++++++++++ lib/utils/device_utils.dart | 3 +- lib/utils/reply_utils.dart | 10 +- lib/utils/utils.dart | 6 +- pubspec.lock | 19 +- pubspec.yaml | 5 + tool/README.md | 1 + tool/jnigen.dart | 23 ++ 17 files changed, 530 insertions(+), 177 deletions(-) create mode 100644 android/app/src/main/java/com/example/piliplus/AndroidHelper.java create mode 100644 lib/utils/android/android_helper.dart create mode 100644 lib/utils/android/bindings.g.dart create mode 100644 tool/README.md create mode 100644 tool/jnigen.dart diff --git a/android/app/src/main/java/com/example/piliplus/AndroidHelper.java b/android/app/src/main/java/com/example/piliplus/AndroidHelper.java new file mode 100644 index 000000000..f8e7b91ec --- /dev/null +++ b/android/app/src/main/java/com/example/piliplus/AndroidHelper.java @@ -0,0 +1,84 @@ +package com.example.piliplus; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.Icon; +import android.net.Uri; +import android.os.Build; +import android.provider.Settings; + +import androidx.annotation.Keep; + +import com.github.dart_lang.jni_flutter.JniFlutterPlugin; + +import org.jetbrains.annotations.NotNull; + +@Keep +public final class AndroidHelper { + private AndroidHelper() { + } + + private static Context getContext() { + return JniFlutterPlugin.getApplicationContext(); + } + + public static void back() { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_HOME); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + getContext().startActivity(intent); + } + + public static int sdkInt() { + return Build.VERSION.SDK_INT; + } + + public static void openLinkVerifySettings() { + Context context = getContext(); + Uri uri = Uri.parse("package:" + context.getPackageName()); + try { + Intent intent; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + intent = new Intent(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS, uri); + } else { + intent = new Intent(Intent.ACTION_MAIN, uri); + intent.setClassName( + "com.android.settings", + "com.android.settings.applications.InstalledAppOpenByDefaultActivity" + ); + } + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } catch (Throwable ignored) { + Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, uri); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } + } + + public static void createShortcut(@NotNull String id, @NotNull String uri, @NotNull String label, @NotNull String icon) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Context context = getContext(); + ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class); + if (shortcutManager != null && shortcutManager.isRequestPinShortcutSupported()) { + Bitmap bitmap = BitmapFactory.decodeFile(icon); + ShortcutInfo shortcut = new ShortcutInfo.Builder(context, id) + .setShortLabel(label) + .setIcon(Icon.createWithAdaptiveBitmap(bitmap)) + .setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse(uri))) + .build(); + // TODO: WorkerThread + Intent pinIntent = shortcutManager.createShortcutResultIntent(shortcut); + PendingIntent pendingIntent = PendingIntent.getBroadcast( + context, 0, pinIntent, PendingIntent.FLAG_IMMUTABLE + ); + shortcutManager.requestPinShortcut(shortcut, pendingIntent.getIntentSender()); + } + } + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/example/piliplus/MainActivity.kt b/android/app/src/main/kotlin/com/example/piliplus/MainActivity.kt index 2d2ad8995..d60e3ac52 100644 --- a/android/app/src/main/kotlin/com/example/piliplus/MainActivity.kt +++ b/android/app/src/main/kotlin/com/example/piliplus/MainActivity.kt @@ -1,22 +1,15 @@ package com.example.piliplus -import android.app.PendingIntent -import android.app.SearchManager import android.content.Intent import android.content.pm.PackageManager -import android.content.pm.ShortcutInfo -import android.content.pm.ShortcutManager import android.content.res.Configuration -import android.graphics.BitmapFactory -import android.graphics.drawable.Icon import android.os.Build -import android.provider.MediaStore -import android.provider.Settings -import androidx.core.net.toUri +import android.os.Bundle +import android.view.WindowManager.LayoutParams import com.ryanheise.audioservice.AudioServiceActivity +import io.flutter.SystemChrome import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel -import io.flutter.SystemChrome class MainActivity : AudioServiceActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { @@ -25,115 +18,15 @@ class MainActivity : AudioServiceActivity() { val methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "PiliPlus") methodChannel.setMethodCallHandler { call, result -> when (call.method) { - "back" -> back() - - "linkVerifySettings" -> { - val uri = ("package:" + context.packageName).toUri() - try { - val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - Intent(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS, uri) - } else { - Intent("android.intent.action.MAIN", uri).setClassName( - "com.android.settings", - "com.android.settings.applications.InstalledAppOpenByDefaultActivity" - ) - } - context.startActivity(intent) - } catch (_: Throwable) { - val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, uri) - context.startActivity(intent) - } - } - - "music" -> { - val title = call.argument("title") - val intent = Intent(MediaStore.INTENT_ACTION_MEDIA_SEARCH).apply { - putExtra(SearchManager.QUERY, title) - putExtra(MediaStore.EXTRA_MEDIA_TITLE, title) - call.argument("artist") - ?.let { putExtra(MediaStore.EXTRA_MEDIA_ARTIST, it) } - call.argument("album") - ?.let { putExtra(MediaStore.EXTRA_MEDIA_ALBUM, it) } - - addCategory(Intent.CATEGORY_DEFAULT) - } - try { - if (packageManager.resolveActivity( - intent, - PackageManager.MATCH_DEFAULT_ONLY - ) != null - ) { - startActivity(intent) - result.success(true) - return@setMethodCallHandler - } - } catch (_: Throwable) { - } - try { - intent.action = MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH - if (packageManager.resolveActivity( - intent, - PackageManager.MATCH_DEFAULT_ONLY - ) != null - ) { - startActivity(intent) - result.success(true) - return@setMethodCallHandler - } - } catch (_: Throwable) { - } - result.success(false) - } - - "createShortcut" -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - try { - val shortcutManager = - context.getSystemService(ShortcutManager::class.java) - if (shortcutManager.isRequestPinShortcutSupported) { - val id = call.argument("id")!! - val uri = call.argument("uri")!! - val label = call.argument("label")!! - val icon = call.argument("icon")!! - val bitmap = BitmapFactory.decodeFile(icon) - val shortcut = - ShortcutInfo.Builder(context, id) - .setShortLabel(label) - .setIcon(Icon.createWithAdaptiveBitmap(bitmap)) - .setIntent(Intent(Intent.ACTION_VIEW, uri.toUri())) - .build() - val pinIntent = - shortcutManager.createShortcutResultIntent(shortcut) - val pendingIntent = PendingIntent.getBroadcast( - context, 0, pinIntent, PendingIntent.FLAG_IMMUTABLE - ) - shortcutManager.requestPinShortcut( - shortcut, - pendingIntent.intentSender - ) - } - } catch (_: Exception) { - } - } - } - - "sdkInt" -> { - result.success(Build.VERSION.SDK_INT) - } - "SystemChrome.setEnabledSystemUIMode" -> { SystemChrome.onMethodCall( - this, - "SystemChrome.setEnabledSystemUIMode", - call.argument("arguments") + this, "SystemChrome.setEnabledSystemUIMode", call.argument("arguments") ) } "SystemChrome.setEnabledSystemUIOverlays" -> { SystemChrome.onMethodCall( - this, - "SystemChrome.setEnabledSystemUIOverlays", - call.argument("arguments") + this, "SystemChrome.setEnabledSystemUIOverlays", call.argument("arguments") ) } @@ -142,12 +35,12 @@ class MainActivity : AudioServiceActivity() { } } - private fun back() { - val intent = Intent(Intent.ACTION_MAIN).apply { - addCategory(Intent.CATEGORY_HOME) - flags = Intent.FLAG_ACTIVITY_NEW_TASK + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + window.attributes.layoutInDisplayCutoutMode = + LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES } - startActivity(intent) } override fun onDestroy() { @@ -156,13 +49,11 @@ class MainActivity : AudioServiceActivity() { } override fun onPictureInPictureModeChanged( - isInPictureInPictureMode: Boolean, - newConfig: Configuration? + isInPictureInPictureMode: Boolean, newConfig: Configuration? ) { super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) - MethodChannel( - flutterEngine!!.dartExecutor.binaryMessenger, - "floating" - ).invokeMethod("onPipChanged", isInPictureInPictureMode) + MethodChannel(flutterEngine!!.dartExecutor.binaryMessenger, "floating").invokeMethod( + "onPipChanged", isInPictureInPictureMode + ) } } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index db0a6f9fe..497e99979 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/lib/main.dart b/lib/main.dart index 5ae01fa15..5bf4a63b6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,7 +18,6 @@ import 'package:PiliPlus/services/service_locator.dart'; import 'package:PiliPlus/utils/cache_manager.dart'; import 'package:PiliPlus/utils/calc_window_position.dart'; import 'package:PiliPlus/utils/date_utils.dart'; -import 'package:PiliPlus/utils/device_utils.dart'; import 'package:PiliPlus/utils/extension/theme_ext.dart'; import 'package:PiliPlus/utils/json_file_handler.dart'; import 'package:PiliPlus/utils/path_utils.dart'; @@ -84,10 +83,6 @@ Future _initAppPath() async { appSupportDirPath = (await getApplicationSupportDirectory()).path; } -Future _initSdkInt() async { - DeviceUtils.sdkInt = await Utils.channel.invokeMethod('sdkInt'); -} - void main() async { WidgetsFlutterBinding.ensureInitialized(); MediaKit.ensureInitialized(); @@ -109,7 +104,6 @@ void main() async { if (PlatformUtils.isMobile) { await Future.wait([ - if (Platform.isAndroid) _initSdkInt(), if (Pref.horizontalScreen) ?fullMode() else ?portraitUpMode(), setupServiceLocator(), ]); diff --git a/lib/pages/about/view.dart b/lib/pages/about/view.dart index e04858265..7dab10a89 100644 --- a/lib/pages/about/view.dart +++ b/lib/pages/about/view.dart @@ -12,6 +12,7 @@ import 'package:PiliPlus/pages/mine/controller.dart'; import 'package:PiliPlus/services/logger.dart'; import 'package:PiliPlus/utils/accounts.dart'; import 'package:PiliPlus/utils/accounts/account.dart'; +import 'package:PiliPlus/utils/android/bindings.g.dart'; import 'package:PiliPlus/utils/app_scheme.dart'; import 'package:PiliPlus/utils/cache_manager.dart'; import 'package:PiliPlus/utils/date_utils.dart'; @@ -179,7 +180,7 @@ Commit Hash: ${BuildConfig.commitHash}''', ), if (Platform.isAndroid) ListTile( - onTap: () => Utils.channel.invokeMethod('linkVerifySettings'), + onTap: AndroidHelper.openLinkVerifySettings, leading: const Icon(MdiIcons.linkBoxOutline), title: const Text('打开受支持的链接'), trailing: Icon( diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index a5c7323a0..148acdf72 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -10,6 +10,7 @@ import 'package:PiliPlus/models/common/nav_bar_config.dart'; import 'package:PiliPlus/pages/home/view.dart'; import 'package:PiliPlus/pages/main/controller.dart'; import 'package:PiliPlus/plugin/pl_player/controller.dart'; +import 'package:PiliPlus/utils/android/bindings.g.dart'; import 'package:PiliPlus/utils/app_scheme.dart'; import 'package:PiliPlus/utils/extension/context_ext.dart'; import 'package:PiliPlus/utils/extension/size_ext.dart'; @@ -18,7 +19,6 @@ import 'package:PiliPlus/utils/mobile_observer.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage_key.dart'; -import 'package:PiliPlus/utils/utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; @@ -202,9 +202,10 @@ class _MainAppState extends PopScopeState await trayManager.setContextMenu(trayMenu); } + @pragma('vm:prefer-inline') static void _onBack() { if (Platform.isAndroid) { - Utils.channel.invokeMethod('back'); + AndroidHelper.back(); } } diff --git a/lib/pages/member/view.dart b/lib/pages/member/view.dart index 5359b9d34..2d1ebf1ab 100644 --- a/lib/pages/member/view.dart +++ b/lib/pages/member/view.dart @@ -33,6 +33,7 @@ import 'package:PiliPlus/pages/member_shop/view.dart'; import 'package:PiliPlus/pages/member_video_web/archive/view.dart'; import 'package:PiliPlus/pages/member_video_web/season_series/view.dart'; import 'package:PiliPlus/pages/webview/view.dart'; +import 'package:PiliPlus/utils/android/android_helper.dart'; import 'package:PiliPlus/utils/date_utils.dart'; import 'package:PiliPlus/utils/extension/context_ext.dart'; import 'package:PiliPlus/utils/extension/string_ext.dart'; @@ -704,14 +705,11 @@ class _MemberPageState extends State { '${_userController.userAvatar!}@200w_200h.webp'.http2https, )); SmartDialog.dismiss(); - await Utils.channel.invokeMethod( - 'createShortcut', - { - 'id': _userController.mid.toString(), - 'uri': 'bilibili://space/${_userController.mid}', - 'label': _userController.username!, - 'icon': file.path, - }, + PiliAndroidHelper.createShortcut( + _userController.mid.toString(), + 'bilibili://space/${_userController.mid}', + _userController.username!, + file.path, ); } catch (e) { SmartDialog.showToast(e.toString()); diff --git a/lib/pages/music/view.dart b/lib/pages/music/view.dart index 099511100..185b76a3f 100644 --- a/lib/pages/music/view.dart +++ b/lib/pages/music/view.dart @@ -1,4 +1,3 @@ -import 'dart:io' show Platform; import 'dart:math' as math; import 'package:PiliPlus/common/widgets/badge.dart'; @@ -428,15 +427,10 @@ class _MusicDetailPageState extends CommonDynPageState { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - GestureDetector( - onTap: () => _searchMusic(item), - onLongPress: () => Utils.copyText(item.musicTitle!), - behavior: HitTestBehavior.opaque, - child: MarqueeText( - item.musicTitle!, - spacing: 30, - style: textTheme.titleMedium, - ), + MarqueeText( + item.musicTitle!, + spacing: 30, + style: textTheme.titleMedium, ), Wrap( spacing: 8, @@ -558,18 +552,4 @@ class _MusicDetailPageState extends CommonDynPageState { ), ); } - - Future _searchMusic(MusicDetail item) async { - final res = - Platform.isAndroid && - (await Utils.channel.invokeMethod('music', { - 'title': item.musicTitle, - 'artist': item.originArtist ?? item.originArtistList, - 'album': item.album, - }) ?? - false); - if (!res) { - Utils.copyText(item.musicTitle!); - } - } } diff --git a/lib/utils/android/android_helper.dart b/lib/utils/android/android_helper.dart new file mode 100644 index 000000000..855d19e9c --- /dev/null +++ b/lib/utils/android/android_helper.dart @@ -0,0 +1,19 @@ +import 'package:PiliPlus/utils/android/bindings.g.dart'; +import 'package:jni/jni.dart'; + +abstract final class PiliAndroidHelper { + static void createShortcut(String id, String uri, String label, String path) { + final jId = id.toJString(); + final jUri = uri.toJString(); + final jLabel = label.toJString(); + final jPath = path.toJString(); + try { + AndroidHelper.createShortcut(jId, jUri, jLabel, jPath); + } finally { + jId.release(); + jUri.release(); + jLabel.release(); + jPath.release(); + } + } +} diff --git a/lib/utils/android/bindings.g.dart b/lib/utils/android/bindings.g.dart new file mode 100644 index 000000000..7d4129e22 --- /dev/null +++ b/lib/utils/android/bindings.g.dart @@ -0,0 +1,342 @@ +// AUTO GENERATED BY JNIGEN 0.17.0. DO NOT EDIT! + +// ignore_for_file: annotate_overrides +// ignore_for_file: argument_type_not_assignable +// ignore_for_file: camel_case_extensions +// ignore_for_file: camel_case_types +// ignore_for_file: constant_identifier_names +// ignore_for_file: comment_references +// ignore_for_file: doc_directive_unknown +// ignore_for_file: file_names +// ignore_for_file: inference_failure_on_untyped_parameter +// ignore_for_file: invalid_internal_annotation +// ignore_for_file: invalid_use_of_internal_member +// ignore_for_file: library_prefixes +// ignore_for_file: lines_longer_than_80_chars +// ignore_for_file: no_leading_underscores_for_library_prefixes +// ignore_for_file: no_leading_underscores_for_local_identifiers +// ignore_for_file: non_constant_identifier_names +// ignore_for_file: only_throw_errors +// ignore_for_file: overridden_fields +// ignore_for_file: prefer_double_quotes +// ignore_for_file: unintended_html_in_doc_comment +// ignore_for_file: unnecessary_cast +// ignore_for_file: unnecessary_non_null_assertion +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: unused_element +// ignore_for_file: unused_field +// ignore_for_file: unused_import +// ignore_for_file: unused_local_variable +// ignore_for_file: unused_shown_name +// ignore_for_file: use_super_parameters + +import 'dart:core' as core$_; +import 'dart:core' show Object, String; + +import 'package:jni/_internal.dart' as jni$_; +import 'package:jni/jni.dart' as jni$_; + +const _$jniVersionCheck = jni$_.JniVersionCheck(1, 0); + +/// from: `java.lang.Runnable` +extension type Runnable._(jni$_.JObject _$this) implements jni$_.JObject { + static final _class = jni$_.JClass.forName(r'java/lang/Runnable'); + + /// The type which includes information such as the signature of this class. + static const jni$_.JType type = $Runnable$Type$(); + + /// Maps a specific port to the implemented interface. + static final core$_.Map _$impls = {}; + static jni$_.JObjectPtr _$invoke( + core$_.int port, + jni$_.JObjectPtr descriptor, + jni$_.JObjectPtr args, + ) { + return _$invokeMethod( + port, + jni$_.MethodInvocation.fromAddresses( + 0, + descriptor.address, + args.address, + ), + ); + } + + static final jni$_.Pointer< + jni$_.NativeFunction< + jni$_.JObjectPtr Function(jni$_.Int64, jni$_.JObjectPtr, jni$_.JObjectPtr) + > + > + _$invokePointer = jni$_.Pointer.fromFunction(_$invoke); + + static jni$_.Pointer _$invokeMethod( + core$_.int $p, + jni$_.MethodInvocation $i, + ) { + try { + final $d = $i.methodDescriptor.toDartString(releaseOriginal: true); + final $a = $i.args; + if ($d == r'run()V') { + _$impls[$p]!.run(); + return jni$_.nullptr; + } + } catch (e) { + return jni$_.ProtectedJniExtensions.newDartException(e); + } + return jni$_.nullptr; + } + + static void implementIn( + jni$_.JImplementer implementer, + $Runnable $impl, + ) { + late final jni$_.RawReceivePort $p; + $p = jni$_.RawReceivePort(($m) { + if ($m == null) { + _$impls.remove($p.sendPort.nativePort); + $p.close(); + return; + } + final $i = jni$_.MethodInvocation.fromMessage($m); + final $r = _$invokeMethod($p.sendPort.nativePort, $i); + jni$_.ProtectedJniExtensions.returnResult($i.result, $r); + }); + implementer.add( + r'java.lang.Runnable', + $p, + _$invokePointer, + [ + if ($impl.run$async) r'run()V', + ], + ); + final $a = $p.sendPort.nativePort; + _$impls[$a] = $impl; + } + + factory Runnable.implement( + $Runnable $impl, + ) { + final $i = jni$_.JImplementer(); + implementIn($i, $impl); + return $i.implement(); + } +} + +extension Runnable$$Methods on Runnable { + static final _id_run = Runnable._class.instanceMethodId( + r'run', + r'()V', + ); + + static final _run = + jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + ) + > + >('globalEnv_CallVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + ) + >(); + + /// from: `public abstract void run()` + void run() { + final _$$selfRef = reference; + _run(_$$selfRef.pointer, _id_run.pointer).check(); + } +} + +abstract base mixin class $Runnable { + factory $Runnable({ + required void Function() run, + core$_.bool run$async, + }) = _$Runnable; + + void run(); + core$_.bool get run$async => false; +} + +final class _$Runnable with $Runnable { + _$Runnable({ + required void Function() run, + this.run$async = false, + }) : _run = run; + + final void Function() _run; + final core$_.bool run$async; + + void run() { + return _run(); + } +} + +final class $Runnable$Type$ extends jni$_.JType { + @jni$_.internal + const $Runnable$Type$(); + + @jni$_.internal + @core$_.override + String get signature => r'Ljava/lang/Runnable;'; +} + +/// from: `com.example.piliplus.AndroidHelper` +extension type AndroidHelper._(jni$_.JObject _$this) implements jni$_.JObject { + static final _class = jni$_.JClass.forName( + r'com/example/piliplus/AndroidHelper', + ); + + /// The type which includes information such as the signature of this class. + static const jni$_.JType type = $AndroidHelper$Type$(); + static final _id_back = _class.staticMethodId( + r'back', + r'()V', + ); + + static final _back = + jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + ) + > + >('globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + ) + >(); + + /// from: `static public void back()` + static void back() { + final _$$classRef = _class.reference; + _back(_$$classRef.pointer, _id_back.pointer).check(); + } + + static final _id_sdkInt = _class.staticMethodId( + r'sdkInt', + r'()I', + ); + + static final _sdkInt = + jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + ) + > + >('globalEnv_CallStaticIntMethod') + .asFunction< + jni$_.JniResult Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + ) + >(); + + /// from: `static public int sdkInt()` + static core$_.int sdkInt() { + final _$$classRef = _class.reference; + return _sdkInt(_$$classRef.pointer, _id_sdkInt.pointer).integer; + } + + static final _id_openLinkVerifySettings = _class.staticMethodId( + r'openLinkVerifySettings', + r'()V', + ); + + static final _openLinkVerifySettings = + jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + ) + > + >('globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + ) + >(); + + /// from: `static public void openLinkVerifySettings()` + static void openLinkVerifySettings() { + final _$$classRef = _class.reference; + _openLinkVerifySettings( + _$$classRef.pointer, + _id_openLinkVerifySettings.pointer, + ).check(); + } + + static final _id_createShortcut = _class.staticMethodId( + r'createShortcut', + r'(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V', + ); + + static final _createShortcut = + jni$_.ProtectedJniExtensions.lookup< + jni$_.NativeFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.VarArgs< + ( + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer, + ) + >, + ) + > + >('globalEnv_CallStaticVoidMethod') + .asFunction< + jni$_.JThrowablePtr Function( + jni$_.Pointer, + jni$_.JMethodIDPtr, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer, + jni$_.Pointer, + ) + >(); + + /// from: `static public void createShortcut(java.lang.String id, java.lang.String uri, java.lang.String label, java.lang.String icon)` + static void createShortcut( + jni$_.JString id, + jni$_.JString uri, + jni$_.JString label, + jni$_.JString icon, + ) { + final _$$classRef = _class.reference; + final _$id = id.reference; + final _$uri = uri.reference; + final _$label = label.reference; + final _$icon = icon.reference; + _createShortcut( + _$$classRef.pointer, + _id_createShortcut.pointer, + _$id.pointer, + _$uri.pointer, + _$label.pointer, + _$icon.pointer, + ).check(); + } +} + +final class $AndroidHelper$Type$ extends jni$_.JType { + @jni$_.internal + const $AndroidHelper$Type$(); + + @jni$_.internal + @core$_.override + String get signature => r'Lcom/example/piliplus/AndroidHelper;'; +} diff --git a/lib/utils/device_utils.dart b/lib/utils/device_utils.dart index 47cf7be69..c60d26acd 100644 --- a/lib/utils/device_utils.dart +++ b/lib/utils/device_utils.dart @@ -1,8 +1,9 @@ +import 'package:PiliPlus/utils/android/bindings.g.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:flutter/widgets.dart' show WidgetsBinding, Size; abstract final class DeviceUtils { - static late int sdkInt; + static final int sdkInt = AndroidHelper.sdkInt(); static bool get isTablet { return size.shortestSide >= 600; diff --git a/lib/utils/reply_utils.dart b/lib/utils/reply_utils.dart index 1ea505f7f..46ae61b0c 100644 --- a/lib/utils/reply_utils.dart +++ b/lib/utils/reply_utils.dart @@ -46,11 +46,11 @@ abstract final class ReplyUtils { required int type, required int id, required String message, - dynamic root, - dynamic parent, - dynamic ctime, - List? pictures, - dynamic mid, + required int root, + required int parent, + required int ctime, + required List pictures, + required int mid, bool isManual = false, }) async { // CommAntifraud diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 2e936878c..b2d79a07f 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -1,17 +1,13 @@ import 'dart:convert' show JsonEncoder, base64; import 'dart:math' show Random; -import 'package:PiliPlus/common/constants.dart'; import 'package:catcher_2/catcher_2.dart'; -import 'package:flutter/services.dart' - show Clipboard, ClipboardData, MethodChannel; +import 'package:flutter/services.dart' show Clipboard, ClipboardData; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; abstract final class Utils { static final random = Random(); - static const channel = MethodChannel(Constants.appName); - static const jsonEncoder = JsonEncoder.withIndent(' '); static final numericRegex = RegExp(r'^[\d\.]+$'); diff --git a/pubspec.lock b/pubspec.lock index 23e979405..a296d0a52 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -269,6 +269,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.4" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" cli_util: dependency: transitive description: @@ -948,7 +956,7 @@ packages: source: hosted version: "0.6.1" jni: - dependency: transitive + dependency: "direct main" description: name: jni sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f @@ -963,6 +971,15 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + jnigen: + dependency: "direct dev" + description: + path: "pkgs/jnigen" + ref: HEAD + resolved-ref: "14f4ce655c0cebb203777b8200303a9bc1f0b04e" + url: "https://github.com/dart-lang/native.git" + source: git + version: "0.17.0-wip" js: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 3d8db0972..f5d909082 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -112,6 +112,7 @@ dependencies: image_cropper: ^12.0.0 image_picker: ^1.1.2 intl: ^0.20.2 + jni: ^1.0.0 json_annotation: ^4.11.0 live_photo_maker: git: @@ -244,6 +245,10 @@ dev_dependencies: flutter_launcher_icons: ^0.14.4 flutter_lints: ^6.0.0 flutter_native_splash: ^2.4.6 + jnigen: + git: + url: https://github.com/dart-lang/native.git + path: pkgs/jnigen flutter_launcher_icons: android: true diff --git a/tool/README.md b/tool/README.md new file mode 100644 index 000000000..c6fb71ecb --- /dev/null +++ b/tool/README.md @@ -0,0 +1 @@ +run `dart run tool/jnigen.dart` \ No newline at end of file diff --git a/tool/jnigen.dart b/tool/jnigen.dart new file mode 100644 index 000000000..9d33d62e3 --- /dev/null +++ b/tool/jnigen.dart @@ -0,0 +1,23 @@ +import 'dart:io' show Platform; + +import 'package:jnigen/jnigen.dart'; + +void main(List args) { + final packageRoot = Platform.script.resolve('../'); + generateJniBindings( + Config( + outputConfig: OutputConfig( + dartConfig: DartCodeOutputConfig( + path: packageRoot.resolve('lib/utils/android/bindings.g.dart'), + structure: .singleFile, + ), + ), + androidSdkConfig: AndroidSdkConfig(addGradleDeps: true), + sourcePath: [packageRoot.resolve('android/app/src/main/java')], + classes: [ + 'com.example.piliplus', + 'java.lang.Runnable', + ], + ), + ); +}