Adapt to new unyo-core api
This commit is contained in:
117
AGENTS.md
Normal file
117
AGENTS.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# AGENTS.md
|
||||
|
||||
Flutter FFI package bridging to Aniyomi (Kotlin/Java) via JNI.
|
||||
|
||||
## Project Type
|
||||
|
||||
- **Flutter package** (not an app) with FFI JNI bindings
|
||||
- Uses `package:jni` and `package:jnigen` for Java interop
|
||||
- Main export: `lib/aniyomi_bridge.dart`
|
||||
|
||||
## Flutter Version
|
||||
|
||||
Managed via FVM (Flutter Version Management):
|
||||
- **Flutter 3.38.1** (see `.fvmrc`)
|
||||
- Use `fvm flutter` commands or ensure Flutter 3.38.1 is active
|
||||
|
||||
## Key Commands
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
fvm flutter pub get
|
||||
|
||||
# Run tests
|
||||
fvm flutter test
|
||||
|
||||
# Generate JNI bindings (jnigen)
|
||||
fvm flutter pub run jnigen --config jnigen/aniyomibridge.yaml
|
||||
fvm flutter pub run jnigen --config jnigen/headers.yaml
|
||||
# ... (see jnigen/*.yaml for all configs)
|
||||
|
||||
# Run example app
|
||||
fvm flutter run -C example
|
||||
|
||||
# Analyze
|
||||
fvm flutter analyze
|
||||
|
||||
# Format
|
||||
fvm dart format .
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
lib/
|
||||
├── aniyomi_bridge.dart # Main API class (AniyomiBridge)
|
||||
├── jni_isolate.dart # JNI isolate worker (JVM runs in separate isolate)
|
||||
├── jni_isolate_message.dart # Message types for isolate communication
|
||||
├── models/ # Dart models (e.g., extension.dart)
|
||||
└── jmodels/ # JNI-generated Dart bindings (AUTO-GENERATED)
|
||||
├── janiyomibridge.dart # Generated from AniyomiBridge Java class
|
||||
├── jsanime.dart # Generated from SAnime
|
||||
├── jsepisode.dart # Generated from SEpisode
|
||||
├── jvideo.dart # Generated from Video
|
||||
└── ... (other j*.dart files)
|
||||
|
||||
jnigen/
|
||||
├── aniyomibridge.yaml # Config for generating janiyomibridge.dart
|
||||
├── headers.yaml # Config for jheaders.dart (okhttp3.Headers)
|
||||
├── sanime.yaml, video.yaml, etc. # Per-class generation configs
|
||||
|
||||
assets/
|
||||
└── aniyomibridge-core.jar # Java library (source of JNI bindings)
|
||||
|
||||
jre/
|
||||
├── linux/customjre/ # Custom JRE for Linux builds
|
||||
└── macos/customjre/ # Custom JRE for macOS builds
|
||||
```
|
||||
|
||||
## Code Generation (JNIGen)
|
||||
|
||||
The `lib/jmodels/j*.dart` files are **auto-generated** from Java classes via JNIGen:
|
||||
|
||||
- Source JAR: `assets/aniyomibridge-core.jar`
|
||||
- Configs: `jnigen/*.yaml` (one per Java class)
|
||||
- Generated classes mirror `eu.kanade.tachiyomi.*` and `dev.k3vinb5.aniyomibridge.*`
|
||||
|
||||
**Never manually edit `jmodels/j*.dart` files**—regenerate via:
|
||||
```bash
|
||||
for config in jnigen/*.yaml; do
|
||||
fvm flutter pub run jnigen --config "$config"
|
||||
done
|
||||
```
|
||||
|
||||
## JRE Requirements
|
||||
|
||||
This package requires a **custom JRE** bundled with the host application:
|
||||
|
||||
```bash
|
||||
# Debug builds
|
||||
./installJREDebug.sh linux # or macos
|
||||
|
||||
# Release builds
|
||||
./installJRERelease.sh linux # or macos
|
||||
```
|
||||
|
||||
The scripts copy `jre/<platform>/customjre/` into the Flutter build output.
|
||||
|
||||
## Testing
|
||||
|
||||
- Test directory: `test/` (currently empty)
|
||||
- Example app: `example/` (test the package here)
|
||||
|
||||
## Key Dependencies
|
||||
|
||||
- `jni: ^0.14.2` / `jnigen: ^0.14.2` — JNI bindings
|
||||
- `logger: ^2.6.2` — Logging
|
||||
- `path_provider: ^2.1.5`, `path`, `crypto` — Storage utilities
|
||||
|
||||
## Isolate Model
|
||||
|
||||
JNI operations run in a **separate isolate** (`jni_isolate.dart`) to avoid blocking the main thread. All Java calls are dispatched via `JniIsolateMessage` and results returned through `ReceivePort`.
|
||||
|
||||
## Important Notes
|
||||
|
||||
- **Do not commit `pubspec.lock`** — it's gitignored per Dart library conventions
|
||||
- `analysis_options.yaml` uses `package:flutter_lints/flutter.yaml`
|
||||
- The package exports extension loading from remote URLs (see `example/lib/main.dart`)
|
||||
625
JNI_SPECIFICATION.md
Normal file
625
JNI_SPECIFICATION.md
Normal file
@@ -0,0 +1,625 @@
|
||||
# AniyomiBridge JNI Specification
|
||||
|
||||
> **Version:** 2.0 (Post-Multi-Module Refactor)
|
||||
> **Target:** Flutter JNI integration for `aniyomibridge-core-1.0.0.jar`
|
||||
> **Main Class:** `dev.k3vinb5.aniyomibridge.AniyomiBridge`
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This document specifies the public Java API exposed by `AniyomiBridge.java` for consumption via JNI from Flutter. It covers every method, its signature, the data models that cross the JNI boundary, and the behavioral changes introduced during the multi-module refactor.
|
||||
|
||||
**Key architectural fact:** `AniyomiBridge` is a **singleton-like stateful object**. It maintains internal maps of loaded anime/manga sources keyed by `packageName`. You create one instance and call methods on it. All source lookup is done internally via these maps.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Initialization](#1-initialization)
|
||||
2. [Extension Lifecycle (Anime)](#2-extension-lifecycle-anime)
|
||||
3. [Extension Lifecycle (Manga)](#3-extension-lifecycle-manga)
|
||||
4. [Querying Loaded Extensions](#4-querying-loaded-extensions)
|
||||
5. [Preferences API](#5-preferences-api)
|
||||
6. [Content Fetching (Anime)](#6-content-fetching-anime)
|
||||
7. [Content Fetching (Manga)](#7-content-fetching-manga)
|
||||
8. [Data Models](#8-data-models)
|
||||
9. [Error Handling](#9-error-handling)
|
||||
10. [Breaking Changes from v1](#10-breaking-changes-from-v1)
|
||||
11. [JNI Implementation Notes](#11-jni-implementation-notes)
|
||||
|
||||
---
|
||||
|
||||
## 1. Initialization
|
||||
|
||||
### `static void init()`
|
||||
|
||||
Must be called **once** before any other operation. Performs global JVM-level setup:
|
||||
|
||||
- Installs ByteBuddy agent for runtime bytecode patching
|
||||
- Initializes Injekt dependency injection module
|
||||
|
||||
**JNI signature:**
|
||||
```
|
||||
(env)->CallStaticVoidMethod(aniyomiBridgeClass, initMethod);
|
||||
```
|
||||
|
||||
**Thread safety:** Safe to call from any thread, but should only be called once. Not synchronized externally — caller must ensure single invocation.
|
||||
|
||||
**Throws:** Nothing directly, but ByteBuddy installation can fail if the JVM doesn't support instrumentation.
|
||||
|
||||
---
|
||||
|
||||
## 2. Extension Lifecycle (Anime)
|
||||
|
||||
### `void loadAnimeExtension(String extensionUrl, String basePath, String packageName)`
|
||||
|
||||
Downloads an APK from `extensionUrl`, converts it to JAR via dex2jar, loads the main extension class, and registers it as an `AnimeHttpSource`.
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| `extensionUrl` | Full HTTPS URL to the `.apk` file |
|
||||
| `basePath` | Local directory path where the converted `.jar` will be cached |
|
||||
| `packageName` | The Android package name (e.g., `eu.kanade.tachiyomi.animeextension.en.animepahe`). Used as the **internal key** for all subsequent lookups. |
|
||||
|
||||
**Behavior:**
|
||||
- Downloads APK if not already present
|
||||
- Converts APK → JAR using dex2jar
|
||||
- Inspects JAR to find the main extension class
|
||||
- Loads class via isolated `URLClassLoader`
|
||||
- Applies ByteBuddy patches to the extension's JVM classes
|
||||
- If the source implements `ConfigurableAnimeSource`, automatically calls `setupPreferenceScreen()` with a managed `PreferenceScreen`
|
||||
- Stores the source in an internal `ConcurrentHashMap` keyed by `packageName`
|
||||
|
||||
**Throws:** `UnyoCoreException` on any failure (download, dex2jar, class loading, casting).
|
||||
|
||||
---
|
||||
|
||||
### `void unloadAnimeExtension(String packageName)`
|
||||
|
||||
Unloads an anime extension and cleans up resources.
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| `packageName` | The same `packageName` passed to `loadAnimeExtension` |
|
||||
|
||||
**Behavior:**
|
||||
- Closes the extension's `URLClassLoader`
|
||||
- Deletes the cached JAR file from disk
|
||||
- Removes the source from internal maps
|
||||
|
||||
**Throws:** `UnyoCoreException` on cleanup failure.
|
||||
|
||||
---
|
||||
|
||||
## 3. Extension Lifecycle (Manga)
|
||||
|
||||
### `void loadMangaExtension(String extensionUrl, String basePath, String packageName)`
|
||||
|
||||
Identical to `loadAnimeExtension` but for manga sources implementing `CatalogueSource`.
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| `extensionUrl` | Full HTTPS URL to the `.apk` file |
|
||||
| `basePath` | Local directory path where the converted `.jar` will be cached |
|
||||
| `packageName` | Android package name, used as internal key |
|
||||
|
||||
**Behavior:** Same as anime version, but casts to `CatalogueSource` and calls `ConfigurableSource.setupPreferenceScreen()` if applicable.
|
||||
|
||||
**Throws:** `UnyoCoreException`
|
||||
|
||||
---
|
||||
|
||||
### `void unloadMangaExtension(String packageName)`
|
||||
|
||||
Unloads a manga extension.
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| `packageName` | The same `packageName` passed to `loadMangaExtension` |
|
||||
|
||||
**Throws:** `UnyoCoreException`
|
||||
|
||||
---
|
||||
|
||||
## 4. Querying Loaded Extensions
|
||||
|
||||
### `List<String> getAnimeLoadedExtensions()`
|
||||
|
||||
Returns a list of loaded anime extension package names.
|
||||
|
||||
**Return:** `java.util.List<java.lang.String>` — package names of all currently loaded anime sources.
|
||||
|
||||
---
|
||||
|
||||
### `List<String> getMangaLoadedExtensions()`
|
||||
|
||||
Returns a list of loaded manga extension package names.
|
||||
|
||||
**Return:** `java.util.List<java.lang.String>` — package names of all currently loaded manga sources.
|
||||
|
||||
---
|
||||
|
||||
## 5. Preferences API
|
||||
|
||||
> **New in v2.** Previously, preferences were not exposed to Flutter. Now they are fully readable and writable.
|
||||
|
||||
When a `ConfigurableAnimeSource` or `ConfigurableSource` is loaded, `AniyomiBridge` automatically calls `setupPreferenceScreen(screen)` and creates a `PreferenceScreen` backed by file-based `SharedPreferences` (stored under `~/.aniyomibridge/shared_prefs/<source-id>.xml`).
|
||||
|
||||
### `List<PreferenceItem> getAnimePreferences(String packageName)`
|
||||
|
||||
Reads all preferences registered by an anime extension.
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| `packageName` | Package name of the loaded anime extension |
|
||||
|
||||
**Return:** `java.util.List<PreferenceItem>`
|
||||
|
||||
**Behavior:**
|
||||
- Returns empty list if source is not configurable
|
||||
- Iterates all `Preference` objects on the source's `PreferenceScreen`
|
||||
- Reads current value from `SharedPreferences` (or default if never set)
|
||||
- Returns typed values based on preference type
|
||||
|
||||
**Supported preference types:**
|
||||
|
||||
| Type | Java Type | Value Type |
|
||||
|------|-----------|------------|
|
||||
| `SwitchPreferenceCompat` | `Boolean` | `true` / `false` |
|
||||
| `ListPreference` | `String` | Selected entry value |
|
||||
| `EditTextPreference` | `String` | User input text |
|
||||
| `MultiSelectListPreference` | `Set<String>` | Selected values |
|
||||
| `Preference` (fallback) | `null` | No value |
|
||||
|
||||
---
|
||||
|
||||
### `void setAnimePreference(String packageName, String key, Object value)`
|
||||
|
||||
Sets a single preference value for an anime extension.
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| `packageName` | Package name of the loaded anime extension |
|
||||
| `key` | Preference key (from `getAnimePreferences`) |
|
||||
| `value` | Value to set. Must match the expected type: `Boolean`, `String`, or `Set<String>` |
|
||||
|
||||
**Throws:** `UnyoCoreException` if source not configurable, key not found, or type mismatch.
|
||||
|
||||
---
|
||||
|
||||
### `void setAnimePreferences(String packageName, Map<String, Object> values)`
|
||||
|
||||
Batch-updates multiple preferences for an anime extension.
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| `packageName` | Package name of the loaded anime extension |
|
||||
| `values` | `java.util.Map` of `key → value` pairs |
|
||||
|
||||
**Behavior:** Iterates the map and calls `setAnimePreference` logic for each entry. Fails fast on first error.
|
||||
|
||||
**Throws:** `UnyoCoreException`
|
||||
|
||||
> **Note:** Manga preferences API is **not yet exposed** in `AniyomiBridge.java`. If needed, the same pattern applies using `CatalogueSource` / `ConfigurableSource`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Content Fetching (Anime)
|
||||
|
||||
All content-fetching methods are **synchronous blocking calls** that internally run Kotlin coroutines via `runBlocking`. Call them from background threads in Flutter.
|
||||
|
||||
### `List<SAnime> getAnimeSearchResults(String query, int page, String packageName)`
|
||||
|
||||
Searches for anime using the specified extension.
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| `query` | Search query string |
|
||||
| `page` | Page number (1-based) |
|
||||
| `packageName` | Package name of the loaded anime extension |
|
||||
|
||||
**Return:** `java.util.List<SAnime>`
|
||||
|
||||
**Throws:** `UnyoCoreException` wrapping any network or parsing error.
|
||||
|
||||
---
|
||||
|
||||
### `List<SEpisode> getEpisodeList(SAnime anime, String packageName)`
|
||||
|
||||
Fetches the episode list for a given anime.
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| `anime` | `SAnime` object (from search results or previous call) |
|
||||
| `packageName` | Package name of the loaded anime extension |
|
||||
|
||||
**Return:** `java.util.List<SEpisode>`
|
||||
|
||||
**Throws:** `UnyoCoreException`
|
||||
|
||||
---
|
||||
|
||||
### `List<Video> getVideoList(SEpisode episode, String packageName)`
|
||||
|
||||
Fetches video URLs/metadata for a given episode.
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| `episode` | `SEpisode` object |
|
||||
| `packageName` | Package name of the loaded anime extension |
|
||||
|
||||
**Return:** `java.util.List<Video>`
|
||||
|
||||
**Throws:** `UnyoCoreException`
|
||||
|
||||
> **Important:** The `Video` class contains `okhttp3.Headers` which is **not serializable by default**. If you need to pass video data across JNI/Flutter isolates, consider using `SerializableVideo` (see Data Models).
|
||||
|
||||
---
|
||||
|
||||
## 7. Content Fetching (Manga)
|
||||
|
||||
### `List<SManga> getMangaSearchResults(String query, int page, String packageName)`
|
||||
|
||||
Searches for manga.
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| `query` | Search query string |
|
||||
| `page` | Page number (1-based) |
|
||||
| `packageName` | Package name of the loaded manga extension |
|
||||
|
||||
**Return:** `java.util.List<SManga>`
|
||||
|
||||
**Throws:** `UnyoCoreException`
|
||||
|
||||
---
|
||||
|
||||
### `List<SChapter> getChapterList(SManga manga, String packageName)`
|
||||
|
||||
Fetches chapter list for a manga.
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| `manga` | `SManga` object |
|
||||
| `packageName` | Package name of the loaded manga extension |
|
||||
|
||||
**Return:** `java.util.List<SChapter>`
|
||||
|
||||
**Throws:** `UnyoCoreException`
|
||||
|
||||
---
|
||||
|
||||
### `List<Page> getPageList(SChapter chapter, String packageName)`
|
||||
|
||||
Fetches image page URLs for a chapter.
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| `chapter` | `SChapter` object |
|
||||
| `packageName` | Package name of the loaded manga extension |
|
||||
|
||||
**Return:** `java.util.List<Page>`
|
||||
|
||||
**Throws:** `UnyoCoreException`
|
||||
|
||||
---
|
||||
|
||||
## 8. Data Models
|
||||
|
||||
### `PreferenceItem`
|
||||
|
||||
**Package:** `dev.k3vinb5.aniyomibridge.PreferenceItem`
|
||||
|
||||
**Fields:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `key` | `String` | Unique preference identifier |
|
||||
| `title` | `String` | Human-readable title |
|
||||
| `type` | `String` | One of: `SwitchPreferenceCompat`, `ListPreference`, `EditTextPreference`, `MultiSelectListPreference`, `Preference` |
|
||||
| `value` | `Object` | Current value (`Boolean`, `String`, or `Set<String>`) |
|
||||
| `entries` | `List<String>` | Display labels (for List/MultiSelect) |
|
||||
| `entryValues` | `List<String>` | Internal values (for List/MultiSelect) |
|
||||
| `summary` | `String` | Optional description text |
|
||||
|
||||
**JNI tip:** Access fields directly (they are `public`). No getters/setters.
|
||||
|
||||
---
|
||||
|
||||
### `SAnime`
|
||||
|
||||
**Package:** `eu.kanade.tachiyomi.animesource.model.SAnime`
|
||||
|
||||
**Interface fields (all `public`):**
|
||||
|
||||
| Field | Type | Notes |
|
||||
|-------|------|-------|
|
||||
| `url` | `String` | Relative or absolute URL to the anime page |
|
||||
| `title` | `String` | Display title |
|
||||
| `artist` | `String?` | Nullable |
|
||||
| `author` | `String?` | Nullable |
|
||||
| `description` | `String?` | Nullable |
|
||||
| `genre` | `String?` | Nullable; comma-separated genres |
|
||||
| `status` | `int` | `UNKNOWN=0`, `ONGOING=1`, `COMPLETED=2`, `LICENSED=3`, `PUBLISHING_FINISHED=4`, `CANCELLED=5`, `ON_HIATUS=6` |
|
||||
| `thumbnail_url` | `String?` | Cover image URL |
|
||||
| `update_strategy` | `AnimeUpdateStrategy` | Enum |
|
||||
| `fetch_type` | `FetchType` | Enum |
|
||||
| `season_number` | `double` | Season number |
|
||||
| `initialized` | `boolean` | Whether full metadata has been fetched |
|
||||
|
||||
**Method:** `List<String> getGenres()` — parses `genre` string into a list.
|
||||
|
||||
---
|
||||
|
||||
### `SEpisode`
|
||||
|
||||
**Package:** `eu.kanade.tachiyomi.animesource.model.SEpisode`
|
||||
|
||||
**Interface fields:**
|
||||
|
||||
| Field | Type | Notes |
|
||||
|-------|------|-------|
|
||||
| `url` | `String` | Episode page URL |
|
||||
| `name` | `String` | Episode title/number |
|
||||
| `date_upload` | `long` | Unix timestamp (ms) |
|
||||
| `episode_number` | `float` | Numeric episode number |
|
||||
| `scanlator` | `String?` | Nullable |
|
||||
|
||||
---
|
||||
|
||||
### `Video`
|
||||
|
||||
**Package:** `eu.kanade.tachiyomi.animesource.model.Video`
|
||||
|
||||
**Fields:**
|
||||
|
||||
| Field | Type | Notes |
|
||||
|-------|------|-------|
|
||||
| `videoUrl` | `String` | Direct video stream URL |
|
||||
| `videoTitle` | `String` | Quality label (e.g., "1080p") |
|
||||
| `resolution` | `Integer?` | Nullable resolution in pixels |
|
||||
| `bitrate` | `Integer?` | Nullable bitrate |
|
||||
| `headers` | `okhttp3.Headers?` | HTTP headers required for playback |
|
||||
| `preferred` | `boolean` | Whether this is the preferred quality |
|
||||
| `subtitleTracks` | `List<Track>` | Subtitle tracks |
|
||||
| `audioTracks` | `List<Track>` | Audio tracks |
|
||||
| `timestamps` | `List<TimeStamp>` | Chapter/intro markers |
|
||||
| `mpvArgs` | `List<Pair<String,String>>` | MPV player arguments |
|
||||
| `ffmpegStreamArgs` | `List<Pair<String,String>>` | FFmpeg stream arguments |
|
||||
| `ffmpegVideoArgs` | `List<Pair<String,String>>` | FFmpeg video arguments |
|
||||
| `internalData` | `String` | Opaque internal data from extractor |
|
||||
| `initialized` | `boolean` | Whether video metadata is resolved |
|
||||
|
||||
**Deprecation notice:** Old extensions may use `quality` (maps to `videoTitle`) and `url` (maps to internal `videoPageUrl`). New code should use `videoTitle` and `videoUrl`.
|
||||
|
||||
---
|
||||
|
||||
### `SManga`
|
||||
|
||||
**Package:** `eu.kanade.tachiyomi.source.model.SManga`
|
||||
|
||||
Identical structure to `SAnime` but without `fetch_type`, `season_number`, and `update_strategy` is `UpdateStrategy` instead of `AnimeUpdateStrategy`.
|
||||
|
||||
---
|
||||
|
||||
### `SChapter`
|
||||
|
||||
**Package:** `eu.kanade.tachiyomi.source.model.SChapter`
|
||||
|
||||
Identical structure to `SEpisode` but `chapter_number` instead of `episode_number`.
|
||||
|
||||
---
|
||||
|
||||
### `Page`
|
||||
|
||||
**Package:** `eu.kanade.tachiyomi.source.model.Page`
|
||||
|
||||
**Fields:**
|
||||
|
||||
| Field | Type | Notes |
|
||||
|-------|------|-------|
|
||||
| `index` | `int` | Zero-based page index |
|
||||
| `url` | `String` | Page URL (may be relative) |
|
||||
| `imageUrl` | `String?` | Resolved direct image URL |
|
||||
| `number` | `int` | Human-readable page number (`index + 1`) |
|
||||
|
||||
---
|
||||
|
||||
## 9. Error Handling
|
||||
|
||||
### `UnyoCoreException`
|
||||
|
||||
**Package:** `dev.k3vinb5.aniyomibridge.exceptions.UnyoCoreException`
|
||||
|
||||
Extends `RuntimeException`.
|
||||
|
||||
**Constructors:**
|
||||
- `UnyoCoreException(String message)`
|
||||
- `UnyoCoreException(String message, Throwable cause)`
|
||||
|
||||
**Behavior:** Every public method in `AniyomiBridge` wraps failures in `UnyoCoreException` with the full Java stack trace in the message string. When catching via JNI, you get both the user-friendly message and the developer stack trace.
|
||||
|
||||
**Recommended Flutter handling:**
|
||||
```dart
|
||||
// Pseudocode — depends on your JNI bindings
|
||||
try {
|
||||
final results = bridge.getAnimeSearchResults(query, page, packageName);
|
||||
} on JniException catch (e) {
|
||||
// e.message contains both the summary and the full Java stack trace
|
||||
log.error(e.message);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Breaking Changes from v1
|
||||
|
||||
If your Flutter code was written against the pre-refactor API (before multi-module split), these are the critical changes:
|
||||
|
||||
### 1. Extension keys changed from `extensionName` to `packageName`
|
||||
|
||||
**Before:**
|
||||
```java
|
||||
loadAnimeExtension(String extensionUrl, String basePath)
|
||||
// Key was extracted from URL: "animepahe", "torrentio", etc.
|
||||
|
||||
unloadAnimeExtension(String extensionName, String version, String basePath)
|
||||
// You passed extensionName + version, bridge constructed filename
|
||||
```
|
||||
|
||||
**After:**
|
||||
```java
|
||||
loadAnimeExtension(String extensionUrl, String basePath, String packageName)
|
||||
// You pass the full Android package name as key
|
||||
|
||||
unloadAnimeExtension(String packageName)
|
||||
// Just packageName. Bridge tracks the JAR path internally.
|
||||
```
|
||||
|
||||
**Migration:**
|
||||
- Stop extracting extension names from URLs
|
||||
- Use the Android package name (from the extension repo JSON) as the key everywhere
|
||||
- Update your Flutter model to pass `packageName` to all methods
|
||||
|
||||
### 2. `getAnimeSearchResults` / `getMangaSearchResults` signature change
|
||||
|
||||
**Before:**
|
||||
```java
|
||||
getAnimeSearchResults(String query, int page, String animeSource)
|
||||
// 'animeSource' was the extension name
|
||||
```
|
||||
|
||||
**After:**
|
||||
```java
|
||||
getAnimeSearchResults(String query, int page, String packageName)
|
||||
// 'packageName' is the full package name
|
||||
```
|
||||
|
||||
**Migration:** Pass `packageName` instead of extension name.
|
||||
|
||||
### 3. Content fetching methods signature change
|
||||
|
||||
**Before:**
|
||||
```java
|
||||
getEpisodeList(SAnime anime, String animeSource)
|
||||
getVideoList(SEpisode episode, String animeSource)
|
||||
```
|
||||
|
||||
**After:**
|
||||
```java
|
||||
getEpisodeList(SAnime anime, String packageName)
|
||||
getVideoList(SEpisode episode, String packageName)
|
||||
```
|
||||
|
||||
**Migration:** Same as above — replace `animeSource` string with `packageName`.
|
||||
|
||||
### 4. Removal of `correctedNames` map
|
||||
|
||||
**Before:** The bridge internally mapped names like `"zoro" → "hianime"` and `"aniwatch" → "aniwatchtv"`.
|
||||
|
||||
**After:** No internal name correction. You must use the exact `packageName`.
|
||||
|
||||
**Migration:** Remove any logic in Flutter that relied on these aliases. Use package names directly from the repo metadata.
|
||||
|
||||
### 5. Unload methods no longer take `version` or `basePath`
|
||||
|
||||
**Before:**
|
||||
```java
|
||||
unloadAnimeExtension(String extensionName, String version, String basePath)
|
||||
```
|
||||
|
||||
**After:**
|
||||
```java
|
||||
unloadAnimeExtension(String packageName)
|
||||
```
|
||||
|
||||
**Migration:** Simpler — just pass `packageName`.
|
||||
|
||||
### 6. Preferences API is new
|
||||
|
||||
**Before:** No way to read/write extension preferences from Flutter.
|
||||
|
||||
**After:** Full `getAnimePreferences`, `setAnimePreference`, `setAnimePreferences` API.
|
||||
|
||||
**Migration:** If your app previously couldn't handle configurable sources, now you can/should expose these preferences in your UI.
|
||||
|
||||
### 7. `MediaSourceUtils` now uses per-source filters
|
||||
|
||||
**Before:**
|
||||
```kotlin
|
||||
animeSource.getSearchAnime(page, query, filters = defaultAnimeFilterList)
|
||||
```
|
||||
|
||||
**After:**
|
||||
```kotlin
|
||||
animeSource.getSearchAnime(page, query, filters = animeSource.getFilterList())
|
||||
```
|
||||
|
||||
**Impact:** Search results may differ if an extension had custom filters. This is generally more correct but could change behavior for extensions that rely on non-default filter states.
|
||||
|
||||
---
|
||||
|
||||
## 11. JNI Implementation Notes
|
||||
|
||||
### Class Loading
|
||||
|
||||
The fat JAR (`aniyomibridge-core-1.0.0.jar`) contains everything including the Android SDK stubs, Kotlin stdlib, and all dependencies. Load it with a standard `URLClassLoader` or from the classpath.
|
||||
|
||||
**Important:** The `Main.class` (`dev.k3vinb5.aniyomibridge.Main`) is excluded from the fat JAR (consumed by external Flutter app). The class you instantiate is `dev.k3vinb5.aniyomibridge.AniyomiBridge`.
|
||||
|
||||
### Threading
|
||||
|
||||
All content-fetching methods use `kotlinx.coroutines.runBlocking` internally. They **will block the calling thread**. In Flutter, always call these methods through a background isolate or `compute()` / `Isolate.run()`.
|
||||
|
||||
The internal maps (`animeSources`, `mangaSources`, `classLoaders`) use `ConcurrentHashMap`, so `load*` and `unload*` are thread-safe. However, you should not load and unload the same extension concurrently.
|
||||
|
||||
### Object Lifetimes
|
||||
|
||||
`SAnime`, `SEpisode`, `SManga`, `SChapter` are `Serializable`. You can safely pass them across JNI boundaries and cache them in Dart if needed.
|
||||
|
||||
`Video` contains `okhttp3.Headers` which is **not serializable**. If you need to serialize video data (e.g., for Dart isolates or JSON), convert `Headers` to a `Map<String, List<String>>` or use the `SerializableVideo` Kotlin helper class.
|
||||
|
||||
### Memory
|
||||
|
||||
Each loaded extension gets its own `URLClassLoader`. When you call `unload*Extension`, the classloader is closed and the JAR file is deleted. This should release most memory, but some classes may remain in the JVM's metaspace until GC.
|
||||
|
||||
### Logging
|
||||
|
||||
The bridge uses JBoss Logging. Logs go to stdout/stderr by default. In a Flutter desktop app, you'll see them in the console. Consider redirecting `java.util.logging` or JBoss LogManager to your Dart logging system if you need structured logs.
|
||||
|
||||
---
|
||||
|
||||
## Appendix: Quick Reference — Method Signatures
|
||||
|
||||
```java
|
||||
// Initialization
|
||||
public static void init()
|
||||
|
||||
// Anime lifecycle
|
||||
public void loadAnimeExtension(String extensionUrl, String basePath, String packageName)
|
||||
public void unloadAnimeExtension(String packageName)
|
||||
|
||||
// Manga lifecycle
|
||||
public void loadMangaExtension(String extensionUrl, String basePath, String packageName)
|
||||
public void unloadMangaExtension(String packageName)
|
||||
|
||||
// Query loaded
|
||||
public List<String> getAnimeLoadedExtensions()
|
||||
public List<String> getMangaLoadedExtensions()
|
||||
|
||||
// Preferences (anime only, currently)
|
||||
public List<PreferenceItem> getAnimePreferences(String packageName)
|
||||
public void setAnimePreference(String packageName, String key, Object value)
|
||||
public void setAnimePreferences(String packageName, Map<String, Object> values)
|
||||
|
||||
// Anime content
|
||||
public List<SAnime> getAnimeSearchResults(String query, int page, String packageName)
|
||||
public List<SEpisode> getEpisodeList(SAnime anime, String packageName)
|
||||
public List<Video> getVideoList(SEpisode episode, String packageName)
|
||||
|
||||
// Manga content
|
||||
public List<SManga> getMangaSearchResults(String query, int page, String packageName)
|
||||
public List<SChapter> getChapterList(SManga manga, String packageName)
|
||||
public List<Page> getPageList(SChapter chapter, String packageName)
|
||||
```
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,16 +1,757 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:k3vinb5_aniyomi_bridge/aniyomi_bridge.dart';
|
||||
import 'package:k3vinb5_aniyomi_bridge/jmodels/jpreferenceitem.dart';
|
||||
import 'package:k3vinb5_aniyomi_bridge/jmodels/jsanime.dart';
|
||||
import 'package:k3vinb5_aniyomi_bridge/jmodels/jsepisode.dart';
|
||||
import 'package:k3vinb5_aniyomi_bridge/jmodels/jvideo.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
AniyomiBridge bridge = AniyomiBridge();
|
||||
await Future.delayed(Duration(milliseconds: 1000));
|
||||
await bridge.loadAnimeExtension("https://gitea.k3vinb5.dev/Backups/kohi-den-extensions/raw/branch/main/apk/aniyomi-all.animeonsen-v14.7.apk");
|
||||
List<JSAnime> animes = await bridge.getAnimeSearchResults("Shingeki", 1, "animeonsen");
|
||||
print(animes);
|
||||
animes.forEach((anime) => print(anime.getTitle().toDartString()));
|
||||
|
||||
runApp(const SizedBox());
|
||||
void main() {
|
||||
runApp(const BridgeTesterApp());
|
||||
}
|
||||
|
||||
class BridgeTesterApp extends StatelessWidget {
|
||||
const BridgeTesterApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'AniyomiBridge Tester',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: const TesterHomePage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Data models
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
class RepoExtension {
|
||||
final String name;
|
||||
final String pkg;
|
||||
final String apk;
|
||||
final String lang;
|
||||
final int code;
|
||||
final String version;
|
||||
final int nsfw;
|
||||
final List<SourceInfo> sources;
|
||||
|
||||
RepoExtension({
|
||||
required this.name,
|
||||
required this.pkg,
|
||||
required this.apk,
|
||||
required this.lang,
|
||||
required this.code,
|
||||
required this.version,
|
||||
required this.nsfw,
|
||||
required this.sources,
|
||||
});
|
||||
|
||||
factory RepoExtension.fromJson(Map<String, dynamic> json) {
|
||||
return RepoExtension(
|
||||
name: json['name'] ?? '',
|
||||
pkg: json['pkg'] ?? '',
|
||||
apk: json['apk'] ?? '',
|
||||
lang: json['lang'] ?? '',
|
||||
code: json['code'] ?? 0,
|
||||
version: json['version'] ?? '',
|
||||
nsfw: json['nsfw'] ?? 0,
|
||||
sources: (json['sources'] as List<dynamic>? ?? [])
|
||||
.map((s) => SourceInfo.fromJson(s))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SourceInfo {
|
||||
final String name;
|
||||
final String lang;
|
||||
final String id;
|
||||
final String baseUrl;
|
||||
|
||||
SourceInfo({required this.name, required this.lang, required this.id, required this.baseUrl});
|
||||
|
||||
factory SourceInfo.fromJson(Map<String, dynamic> json) {
|
||||
return SourceInfo(
|
||||
name: json['name'] ?? '',
|
||||
lang: json['lang'] ?? '',
|
||||
id: json['id']?.toString() ?? '',
|
||||
baseUrl: json['baseUrl'] ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LogEntry {
|
||||
final DateTime timestamp;
|
||||
final String message;
|
||||
final bool isError;
|
||||
|
||||
LogEntry({required this.timestamp, required this.message, this.isError = false});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Main page
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
class TesterHomePage extends StatefulWidget {
|
||||
const TesterHomePage({super.key});
|
||||
|
||||
@override
|
||||
State<TesterHomePage> createState() => _TesterHomePageState();
|
||||
}
|
||||
|
||||
class _TesterHomePageState extends State<TesterHomePage> {
|
||||
late final AniyomiBridge _bridge;
|
||||
final List<LogEntry> _logs = [];
|
||||
|
||||
// Navigation
|
||||
int _selectedPage = 0;
|
||||
|
||||
// Repo state
|
||||
final _repoUrlCtrl = TextEditingController(
|
||||
text: 'https://raw.githubusercontent.com/aniyomiorg/aniyomi-extensions/repo/index.min.json',
|
||||
);
|
||||
List<RepoExtension> _repoExtensions = [];
|
||||
List<RepoExtension> _filteredExtensions = [];
|
||||
bool _isFetchingRepo = false;
|
||||
String? _repoBaseUrl;
|
||||
|
||||
// Search / content state
|
||||
final _searchCtrl = TextEditingController();
|
||||
String? _activePackageName;
|
||||
String? _activeExtensionName;
|
||||
List<JSAnime> _animeResults = [];
|
||||
List<JSEpisode> _episodeResults = [];
|
||||
List<JVideo> _videoResults = [];
|
||||
List<JPreferenceItem> _preferences = [];
|
||||
List<String> _loadedPackages = [];
|
||||
final Set<String> _installingPackages = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_bridge = AniyomiBridge();
|
||||
Future.delayed(const Duration(seconds: 2), () {
|
||||
_log('Bridge initialized ${_bridge.isReady()}');
|
||||
_refreshLoaded();
|
||||
});
|
||||
}
|
||||
|
||||
void _log(String message, {bool isError = false}) {
|
||||
setState(() {
|
||||
_logs.insert(0, LogEntry(
|
||||
timestamp: DateTime.now(),
|
||||
message: message,
|
||||
isError: isError,
|
||||
));
|
||||
if (_logs.length > 200) _logs.removeLast();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _run(Future<void> Function() action, String label) async {
|
||||
setState(() {});
|
||||
try {
|
||||
await action();
|
||||
_log('$label: OK');
|
||||
} catch (e, st) {
|
||||
_log('$label: ERROR\n$e', isError: true);
|
||||
debugPrint(st.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _fetchRepo() async {
|
||||
final url = _repoUrlCtrl.text.trim();
|
||||
if (url.isEmpty) return;
|
||||
|
||||
setState(() => _isFetchingRepo = true);
|
||||
try {
|
||||
final uri = Uri.parse(url);
|
||||
_repoBaseUrl = '${uri.scheme}://${uri.host}${path.dirname(uri.path)}';
|
||||
|
||||
final response = await http.get(uri).timeout(const Duration(seconds: 15));
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('HTTP ${response.statusCode}');
|
||||
}
|
||||
|
||||
final List<dynamic> jsonList = jsonDecode(response.body);
|
||||
final extensions = jsonList.map((j) => RepoExtension.fromJson(j)).toList();
|
||||
|
||||
setState(() {
|
||||
_repoExtensions = extensions;
|
||||
_filteredExtensions = extensions;
|
||||
});
|
||||
_log('Fetched ${extensions.length} extensions from repo');
|
||||
} catch (e) {
|
||||
_log('Fetch repo failed: $e', isError: true);
|
||||
} finally {
|
||||
setState(() => _isFetchingRepo = false);
|
||||
}
|
||||
}
|
||||
|
||||
void _filterExtensions(String query) {
|
||||
final q = query.toLowerCase();
|
||||
setState(() {
|
||||
_filteredExtensions = _repoExtensions.where((ext) {
|
||||
return ext.name.toLowerCase().contains(q) ||
|
||||
ext.pkg.toLowerCase().contains(q) ||
|
||||
ext.lang.toLowerCase().contains(q);
|
||||
}).toList();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _installExtension(RepoExtension ext) async {
|
||||
if (_repoBaseUrl == null || _installingPackages.contains(ext.pkg)) return;
|
||||
|
||||
setState(() => _installingPackages.add(ext.pkg));
|
||||
final apkUrl = '$_repoBaseUrl/${ext.apk}';
|
||||
|
||||
try {
|
||||
await _bridge.loadAnimeExtension(apkUrl, ext.pkg);
|
||||
setState(() {
|
||||
_activePackageName = ext.pkg;
|
||||
_activeExtensionName = ext.name;
|
||||
});
|
||||
_log('Installed ${ext.name} (${ext.pkg})');
|
||||
await _refreshLoaded();
|
||||
} catch (e) {
|
||||
_log('Install ${ext.name} failed: $e', isError: true);
|
||||
} finally {
|
||||
setState(() => _installingPackages.remove(ext.pkg));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _unloadExtension(String packageName) async {
|
||||
await _run(() => _bridge.unloadAnimeExtension(packageName), 'Unload $packageName');
|
||||
if (_activePackageName == packageName) {
|
||||
setState(() {
|
||||
_activePackageName = null;
|
||||
_activeExtensionName = null;
|
||||
});
|
||||
}
|
||||
await _refreshLoaded();
|
||||
}
|
||||
|
||||
Future<void> _searchAnime() async {
|
||||
final query = _searchCtrl.text.trim();
|
||||
final pkg = _activePackageName;
|
||||
if (query.isEmpty || pkg == null || pkg.isEmpty) return;
|
||||
await _run(() async {
|
||||
final results = await _bridge.getAnimeSearchResults(query, 1, pkg);
|
||||
setState(() => _animeResults = results);
|
||||
}, 'Search anime');
|
||||
}
|
||||
|
||||
Future<void> _getEpisodes(JSAnime anime) async {
|
||||
final pkg = _activePackageName;
|
||||
if (pkg == null) return;
|
||||
await _run(() async {
|
||||
final episodes = await _bridge.getEpisodeList(anime, pkg);
|
||||
setState(() => _episodeResults = episodes);
|
||||
}, 'Get episodes');
|
||||
}
|
||||
|
||||
Future<void> _getVideos(JSEpisode episode) async {
|
||||
final pkg = _activePackageName;
|
||||
if (pkg == null) return;
|
||||
await _run(() async {
|
||||
final videos = await _bridge.getVideoList(episode, pkg);
|
||||
setState(() => _videoResults = videos);
|
||||
}, 'Get videos');
|
||||
}
|
||||
|
||||
Future<void> _getPreferences() async {
|
||||
final pkg = _activePackageName;
|
||||
if (pkg == null) return;
|
||||
await _run(() async {
|
||||
final prefs = await _bridge.getAnimePreferences(pkg);
|
||||
setState(() => _preferences = prefs);
|
||||
}, 'Get preferences');
|
||||
}
|
||||
|
||||
Future<void> _refreshLoaded() async {
|
||||
try {
|
||||
final loaded = await _bridge.getAnimeLoadedExtensions();
|
||||
setState(() => _loadedPackages = loaded);
|
||||
} catch (e) {
|
||||
_log('Refresh loaded: $e', isError: true);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Build
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('AniyomiBridge Tester'),
|
||||
actions: [
|
||||
if (_isFetchingRepo || _installingPackages.isNotEmpty)
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: NavigationBar(
|
||||
selectedIndex: _selectedPage,
|
||||
onDestinationSelected: (i) => setState(() => _selectedPage = i),
|
||||
destinations: const [
|
||||
NavigationDestination(icon: Icon(Icons.cloud_download), label: 'Repo'),
|
||||
NavigationDestination(icon: Icon(Icons.extension), label: 'Installed'),
|
||||
NavigationDestination(icon: Icon(Icons.search), label: 'Search'),
|
||||
NavigationDestination(icon: Icon(Icons.settings), label: 'Prefs'),
|
||||
NavigationDestination(icon: Icon(Icons.terminal), label: 'Logs'),
|
||||
],
|
||||
),
|
||||
body: IndexedStack(
|
||||
index: _selectedPage,
|
||||
children: [
|
||||
_buildRepoTab(),
|
||||
_buildInstalledTab(),
|
||||
_buildSearchTab(),
|
||||
_buildPrefsTab(),
|
||||
_buildLogsTab(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Repo tab
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
Widget _buildRepoTab() {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _repoUrlCtrl,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Repo JSON URL',
|
||||
hintText: 'https://.../index.min.json',
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.link),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton.icon(
|
||||
onPressed: _isFetchingRepo ? null : _fetchRepo,
|
||||
icon: const Icon(Icons.download),
|
||||
label: const Text('Fetch'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (_repoExtensions.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: TextField(
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Filter extensions...',
|
||||
prefixIcon: Icon(Icons.filter_list),
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
onChanged: _filterExtensions,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _filteredExtensions.isEmpty
|
||||
? const Center(child: Text('No extensions. Enter a repo URL and tap Fetch.'))
|
||||
: ListView.builder(
|
||||
itemCount: _filteredExtensions.length,
|
||||
itemBuilder: (context, i) {
|
||||
final ext = _filteredExtensions[i];
|
||||
final isLoaded = _loadedPackages.contains(ext.pkg);
|
||||
final isInstalling = _installingPackages.contains(ext.pkg);
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: isLoaded ? Colors.green : Colors.grey,
|
||||
child: Icon(
|
||||
isLoaded ? Icons.check : Icons.extension,
|
||||
color: Colors.white,
|
||||
size: 18,
|
||||
),
|
||||
),
|
||||
title: Text(ext.name),
|
||||
subtitle: Text('${ext.lang} | v${ext.version} | ${ext.pkg}'),
|
||||
trailing: isInstalling
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: isLoaded
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.delete, color: Colors.red),
|
||||
onPressed: () => _unloadExtension(ext.pkg),
|
||||
)
|
||||
: ElevatedButton(
|
||||
onPressed: () => _installExtension(ext),
|
||||
child: const Text('Install'),
|
||||
),
|
||||
onTap: isLoaded
|
||||
? () {
|
||||
setState(() {
|
||||
_activePackageName = ext.pkg;
|
||||
_activeExtensionName = ext.name;
|
||||
_selectedPage = 2; // Go to search
|
||||
});
|
||||
}
|
||||
: null,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Installed tab
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
Widget _buildInstalledTab() {
|
||||
return _loadedPackages.isEmpty
|
||||
? const Center(child: Text('No extensions loaded'))
|
||||
: ListView.builder(
|
||||
itemCount: _loadedPackages.length,
|
||||
itemBuilder: (context, i) {
|
||||
final pkg = _loadedPackages[i];
|
||||
final ext = _repoExtensions.firstWhere(
|
||||
(e) => e.pkg == pkg,
|
||||
orElse: () => RepoExtension(
|
||||
name: pkg.split('.').last,
|
||||
pkg: pkg,
|
||||
apk: '',
|
||||
lang: '',
|
||||
code: 0,
|
||||
version: '',
|
||||
nsfw: 0,
|
||||
sources: [],
|
||||
),
|
||||
);
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.check_circle, color: Colors.green),
|
||||
title: Text(ext.name),
|
||||
subtitle: Text(pkg),
|
||||
trailing: Wrap(
|
||||
spacing: 4,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_activePackageName = pkg;
|
||||
_activeExtensionName = ext.name;
|
||||
_selectedPage = 2;
|
||||
});
|
||||
},
|
||||
child: const Text('Search'),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete, color: Colors.red),
|
||||
onPressed: () => _unloadExtension(pkg),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Search tab
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
Widget _buildSearchTab() {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (_activeExtensionName != null)
|
||||
Text(
|
||||
'Active: $_activeExtensionName',
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _searchCtrl,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Search anime...',
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.search),
|
||||
),
|
||||
onSubmitted: (_) => _searchAnime(),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton(
|
||||
onPressed: _activePackageName == null ? null : _searchAnime,
|
||||
child: const Text('Search'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _animeResults.isEmpty
|
||||
? const Center(child: Text('No results'))
|
||||
: ListView.builder(
|
||||
itemCount: _animeResults.length,
|
||||
itemBuilder: (context, i) {
|
||||
final anime = _animeResults[i];
|
||||
final title = anime.getTitle().toDartString();
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.movie),
|
||||
title: Text(title),
|
||||
subtitle: Text(
|
||||
anime.getUrl().toDartString(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () => _showEpisodes(anime, title),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _showEpisodes(JSAnime anime, String title) {
|
||||
_getEpisodes(anime);
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (_) => DraggableScrollableSheet(
|
||||
initialChildSize: 0.6,
|
||||
minChildSize: 0.3,
|
||||
maxChildSize: 0.9,
|
||||
expand: false,
|
||||
builder: (context, scrollCtrl) {
|
||||
return Column(
|
||||
children: [
|
||||
AppBar(
|
||||
title: Text(title),
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
Expanded(
|
||||
child: _episodeResults.isEmpty
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: ListView.builder(
|
||||
controller: scrollCtrl,
|
||||
itemCount: _episodeResults.length,
|
||||
itemBuilder: (context, i) {
|
||||
final ep = _episodeResults[i];
|
||||
return ListTile(
|
||||
leading: CircleAvatar(
|
||||
child: Text('${ep.getEpisode_number()}'),
|
||||
),
|
||||
title: Text(ep.getName().toDartString()),
|
||||
subtitle: Text(
|
||||
ep.getUrl().toDartString(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
trailing: const Icon(Icons.play_arrow),
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
_getVideos(ep);
|
||||
_showVideos(ep.getName().toDartString());
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showVideos(String title) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) => AlertDialog(
|
||||
title: Text('Videos: $title'),
|
||||
content: SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: _videoResults.isEmpty
|
||||
? const Text('No videos found')
|
||||
: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: _videoResults.length,
|
||||
itemBuilder: (context, i) {
|
||||
final vid = _videoResults[i];
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.videocam),
|
||||
title: Text(vid.getVideoTitle().toDartString()),
|
||||
subtitle: Text(
|
||||
vid.getVideoUrl().toDartString(),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
trailing: vid.getPreferred()
|
||||
? const Icon(Icons.star, color: Colors.amber)
|
||||
: null,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Close'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Prefs tab
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
Widget _buildPrefsTab() {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
_activeExtensionName == null
|
||||
? 'No extension selected'
|
||||
: 'Extension: $_activeExtensionName',
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: _activePackageName == null ? null : _getPreferences,
|
||||
child: const Text('Load Prefs'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _preferences.isEmpty
|
||||
? const Center(child: Text('No preferences loaded'))
|
||||
: ListView.builder(
|
||||
itemCount: _preferences.length,
|
||||
itemBuilder: (context, i) {
|
||||
final pref = _preferences[i];
|
||||
final key = pref.key?.toDartString() ?? '';
|
||||
final title = pref.title?.toDartString() ?? key;
|
||||
final type = pref.type$1?.toDartString() ?? 'unknown';
|
||||
final value = pref.value;
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: ListTile(
|
||||
title: Text(title),
|
||||
subtitle: Text('[$type] $key'),
|
||||
trailing: Text(value?.toString() ?? 'null'),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Logs tab
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
Widget _buildLogsTab() {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Row(
|
||||
children: [
|
||||
TextButton.icon(
|
||||
onPressed: () => setState(() => _logs.clear()),
|
||||
icon: const Icon(Icons.clear_all),
|
||||
label: const Text('Clear'),
|
||||
),
|
||||
const Spacer(),
|
||||
Text('${_logs.length} entries'),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _logs.isEmpty
|
||||
? const Center(child: Text('No logs'))
|
||||
: ListView.builder(
|
||||
reverse: true,
|
||||
itemCount: _logs.length,
|
||||
itemBuilder: (context, i) {
|
||||
final log = _logs[i];
|
||||
return Container(
|
||||
color: log.isError ? Colors.red.shade50 : null,
|
||||
child: ListTile(
|
||||
dense: true,
|
||||
leading: Icon(
|
||||
log.isError ? Icons.error : Icons.info,
|
||||
color: log.isError ? Colors.red : Colors.blue,
|
||||
size: 18,
|
||||
),
|
||||
title: Text(
|
||||
log.message,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: log.isError ? Colors.red.shade900 : null,
|
||||
fontFamily: 'monospace',
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
'${log.timestamp.hour.toString().padLeft(2, '0')}:${log.timestamp.minute.toString().padLeft(2, '0')}:${log.timestamp.second.toString().padLeft(2, '0')}',
|
||||
style: const TextStyle(fontSize: 10),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ packages:
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
http:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
|
||||
@@ -235,7 +235,7 @@ packages:
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
|
||||
@@ -34,6 +34,8 @@ dependencies:
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.8
|
||||
http: ^1.2.0
|
||||
path: ^1.9.0
|
||||
k3vinb5_aniyomi_bridge:
|
||||
path: ../
|
||||
|
||||
|
||||
9
jnigen/preferenceitem.yaml
Normal file
9
jnigen/preferenceitem.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
output:
|
||||
dart:
|
||||
path: ../lib/jmodels/jpreferenceitem.dart
|
||||
structure: single_file
|
||||
|
||||
class_path:
|
||||
- '../assets/aniyomibridge-core.jar'
|
||||
classes:
|
||||
- 'dev.k3vinb5.aniyomibridge.PreferenceItem'
|
||||
@@ -17,6 +17,7 @@ import 'package:k3vinb5_aniyomi_bridge/jmodels/jsepisode.dart';
|
||||
import 'package:k3vinb5_aniyomi_bridge/jmodels/jsmanga.dart';
|
||||
import 'package:k3vinb5_aniyomi_bridge/jmodels/jschapter.dart';
|
||||
import 'package:k3vinb5_aniyomi_bridge/jmodels/jpage.dart';
|
||||
import 'package:k3vinb5_aniyomi_bridge/jmodels/jpreferenceitem.dart';
|
||||
|
||||
class AniyomiBridge {
|
||||
// static consts
|
||||
@@ -94,11 +95,11 @@ class AniyomiBridge {
|
||||
return _isReady;
|
||||
}
|
||||
|
||||
Future<List<JSAnime>> getAnimeSearchResults(String query, int page, String source) async {
|
||||
Future<List<JSAnime>> getAnimeSearchResults(String query, int page, String packageName) async {
|
||||
Map<String, dynamic> args = {
|
||||
'query': query,
|
||||
'page': page,
|
||||
'source': source,
|
||||
'source': packageName,
|
||||
};
|
||||
Future<List<JSAnime>>? cachedResponse = _getCachedResponse<Future<List<JSAnime>>>(JniIsolateMessageType.getAnimeSearchResults, args);
|
||||
if (cachedResponse != null) return cachedResponse;
|
||||
@@ -113,11 +114,11 @@ class AniyomiBridge {
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<List<JSManga>> getMangaSearchResults(String query, int page, String source) async {
|
||||
Future<List<JSManga>> getMangaSearchResults(String query, int page, String packageName) async {
|
||||
Map<String, dynamic> args = {
|
||||
'query': query,
|
||||
'page': page,
|
||||
'source': source,
|
||||
'source': packageName,
|
||||
};
|
||||
Future<List<JSManga>>? cachedResponse = _getCachedResponse<Future<List<JSManga>>>(JniIsolateMessageType.getMangaSearchResults, args);
|
||||
if (cachedResponse != null) return cachedResponse;
|
||||
@@ -132,10 +133,10 @@ class AniyomiBridge {
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<List<JSEpisode>> getEpisodeList(JSAnime sAnime, String source) async {
|
||||
Future<List<JSEpisode>> getEpisodeList(JSAnime sAnime, String packageName) async {
|
||||
Map<String, dynamic> args = {
|
||||
'sAnime': sAnime,
|
||||
'source': source,
|
||||
'source': packageName,
|
||||
};
|
||||
Future<List<JSEpisode>>? cachedResponse = _getCachedResponse<Future<List<JSEpisode>>>(JniIsolateMessageType.getEpisodeList, args);
|
||||
if (cachedResponse != null) return cachedResponse;
|
||||
@@ -150,10 +151,10 @@ class AniyomiBridge {
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<List<JSChapter>> getChapterList(JSManga sManga, String source) async {
|
||||
Future<List<JSChapter>> getChapterList(JSManga sManga, String packageName) async {
|
||||
Map<String, dynamic> args = {
|
||||
'sManga': sManga,
|
||||
'source': source,
|
||||
'source': packageName,
|
||||
};
|
||||
Future<List<JSChapter>>? cachedResponse = _getCachedResponse<Future<List<JSChapter>>>(JniIsolateMessageType.getChapterList, args);
|
||||
if (cachedResponse != null) return cachedResponse;
|
||||
@@ -168,10 +169,10 @@ class AniyomiBridge {
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<List<JVideo>> getVideoList(JSEpisode sEpisode, String source) async {
|
||||
Future<List<JVideo>> getVideoList(JSEpisode sEpisode, String packageName) async {
|
||||
Map<String, dynamic> args = {
|
||||
'sEpisode': sEpisode,
|
||||
'source': source,
|
||||
'source': packageName,
|
||||
};
|
||||
Future<List<JVideo>>? cachedResponse = _getCachedResponse<Future<List<JVideo>>>(JniIsolateMessageType.getVideoList, args);
|
||||
if (cachedResponse != null) return cachedResponse;
|
||||
@@ -186,10 +187,10 @@ class AniyomiBridge {
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<List<JPage>> getPageList(JSChapter sChapter, String source) async {
|
||||
Future<List<JPage>> getPageList(JSChapter sChapter, String packageName) async {
|
||||
Map<String, dynamic> args = {
|
||||
'sChapter': sChapter,
|
||||
'source': source,
|
||||
'source': packageName,
|
||||
};
|
||||
Future<List<JPage>>? cachedResponse = _getCachedResponse<Future<List<JPage>>>(JniIsolateMessageType.getPageList, args);
|
||||
if (cachedResponse != null) return cachedResponse;
|
||||
@@ -204,117 +205,143 @@ class AniyomiBridge {
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<void> loadAnimeExtension(String extensionUrl) async {
|
||||
Future<void> loadAnimeExtension(String extensionUrl, String packageName) async {
|
||||
return _sendJniIsolateMessage<void>(
|
||||
JniIsolateMessage(
|
||||
id: _random.nextInt(1 << 32),
|
||||
type: JniIsolateMessageType.loadAnimeExtension,
|
||||
args: {
|
||||
'extensionUrl': extensionUrl,
|
||||
'packageName': packageName,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> unloadAnimeExtension(String extensionName, String extensionVersion) async {
|
||||
Future<void> unloadAnimeExtension(String packageName) async {
|
||||
return _sendJniIsolateMessage<void>(
|
||||
JniIsolateMessage(
|
||||
id: _random.nextInt(1 << 32),
|
||||
type: JniIsolateMessageType.unloadAnimeExtension,
|
||||
args: {
|
||||
'extensionName': extensionName,
|
||||
'extensionVersion': extensionVersion,
|
||||
'packageName': packageName,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> loadMangaExtension(String extensionUrl) async {
|
||||
Future<void> loadMangaExtension(String extensionUrl, String packageName) async {
|
||||
return _sendJniIsolateMessage<void>(
|
||||
JniIsolateMessage(
|
||||
id: _random.nextInt(1 << 32),
|
||||
type: JniIsolateMessageType.loadMangaExtension,
|
||||
args: {
|
||||
'extensionUrl': extensionUrl,
|
||||
'packageName': packageName,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> unloadMangaExtension(String extensionName, String extensionVersion) async {
|
||||
Future<void> unloadMangaExtension(String packageName) async {
|
||||
return _sendJniIsolateMessage<void>(
|
||||
JniIsolateMessage(
|
||||
id: _random.nextInt(1 << 32),
|
||||
type: JniIsolateMessageType.unloadMangaExtension,
|
||||
args: {
|
||||
'extensionName': extensionName,
|
||||
'extensionVersion': extensionVersion,
|
||||
'packageName': packageName,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<JPreferenceItem>> getAnimePreferences(String packageName) async {
|
||||
return _sendJniIsolateMessage<List<JPreferenceItem>>(
|
||||
JniIsolateMessage(
|
||||
id: _random.nextInt(1 << 32),
|
||||
type: JniIsolateMessageType.getAnimePreferences,
|
||||
args: {'packageName': packageName},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> setAnimePreference(String packageName, String key, dynamic value) async {
|
||||
return _sendJniIsolateMessage<void>(
|
||||
JniIsolateMessage(
|
||||
id: _random.nextInt(1 << 32),
|
||||
type: JniIsolateMessageType.setAnimePreference,
|
||||
args: {'packageName': packageName, 'key': key, 'value': value},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> setAnimePreferences(String packageName, Map<String, dynamic> values) async {
|
||||
return _sendJniIsolateMessage<void>(
|
||||
JniIsolateMessage(
|
||||
id: _random.nextInt(1 << 32),
|
||||
type: JniIsolateMessageType.setAnimePreferences,
|
||||
args: {'packageName': packageName, 'values': values},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<String>> getAnimeLoadedExtensions() async {
|
||||
return _sendJniIsolateMessage<List<String>>(
|
||||
JniIsolateMessage(
|
||||
id: _random.nextInt(1 << 32),
|
||||
type: JniIsolateMessageType.getAnimeLoadedExtensions,
|
||||
args: {},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<String>> getMangaLoadedExtensions() async {
|
||||
return _sendJniIsolateMessage<List<String>>(
|
||||
JniIsolateMessage(
|
||||
id: _random.nextInt(1 << 32),
|
||||
type: JniIsolateMessageType.getMangaLoadedExtensions,
|
||||
args: {},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<Set<Extension>> getInstalledAnimeExtensions() async {
|
||||
Directory animeExtDir = Directory(
|
||||
path.join(_supportDirectoryPath, _aniyomiBridgeAnimeExtensionsDir),
|
||||
);
|
||||
Set<Extension> extensions = await animeExtDir
|
||||
.list()
|
||||
.where((entityFileSystem) => entityFileSystem is File)
|
||||
.where((file) => path.extension(file.path) == ".jar")
|
||||
.map((jarFile) => path.basename(jarFile.path))
|
||||
.map(
|
||||
(jarFileName) => Extension(
|
||||
name: jarFileName.split("-")[0],
|
||||
version: jarFileName.split("-")[1],
|
||||
),
|
||||
)
|
||||
.toSet();
|
||||
return extensions;
|
||||
if (!await animeExtDir.exists()) return {};
|
||||
return _scanExtensionsRecursively(animeExtDir);
|
||||
}
|
||||
|
||||
Future<Set<Extension>> getInstalledMangaExtensions() async {
|
||||
Directory mangaExtDir = Directory(
|
||||
path.join(_supportDirectoryPath, _aniyomiBridgeMangaExtensionsDir),
|
||||
);
|
||||
Set<Extension> extensions = await mangaExtDir
|
||||
.list()
|
||||
.where((entityFileSystem) => entityFileSystem is File)
|
||||
.where((file) => path.extension(file.path) == ".jar")
|
||||
.map((jarFile) => path.basename(jarFile.path))
|
||||
.map(
|
||||
(jarFileName) => Extension(
|
||||
name: jarFileName.split("-")[0],
|
||||
version: jarFileName.split("-")[1],
|
||||
),
|
||||
)
|
||||
.toSet();
|
||||
return extensions;
|
||||
if (!await mangaExtDir.exists()) return {};
|
||||
return _scanExtensionsRecursively(mangaExtDir);
|
||||
}
|
||||
|
||||
// List<String>? getLoadedAnimeExtensions() {
|
||||
// JList<JString?>? loadedExtensions = _jAniyomiBridge
|
||||
// .getAnimeLoadedExtensions();
|
||||
// if (loadedExtensions == null) {
|
||||
// return null;
|
||||
// }
|
||||
// return loadedExtensions
|
||||
// .cast<JString>()
|
||||
// .map((jStr) => jStr.toDartString())
|
||||
// .toList();
|
||||
// }
|
||||
//
|
||||
// List<String>? getLoadedMangaExtensions() {
|
||||
// JList<JString?>? loadedExtensions = _jAniyomiBridge
|
||||
// .getMangaLoadedExtensions();
|
||||
// if (loadedExtensions == null) {
|
||||
// return null;
|
||||
// }
|
||||
// return loadedExtensions
|
||||
// .cast<JString>()
|
||||
// .map((jStr) => jStr.toDartString())
|
||||
// .toList();
|
||||
// }
|
||||
Future<Set<Extension>> _scanExtensionsRecursively(Directory rootDir) async {
|
||||
final extensions = <Extension>{};
|
||||
await for (final entity in rootDir.list(recursive: true)) {
|
||||
if (entity is! File) continue;
|
||||
if (path.extension(entity.path) != '.jar') continue;
|
||||
|
||||
final jarFileName = path.basename(entity.path);
|
||||
|
||||
final nameWithoutExt = jarFileName.replaceAll('.jar', '');
|
||||
final firstDash = nameWithoutExt.indexOf('-');
|
||||
final name = firstDash >= 0 ? nameWithoutExt.substring(0, firstDash) : nameWithoutExt;
|
||||
final version = firstDash >= 0 ? nameWithoutExt.substring(firstDash + 1) : '';
|
||||
|
||||
extensions.add(Extension(
|
||||
name: name,
|
||||
version: version,
|
||||
packageName: '', // Still cannot determine from filename alone
|
||||
));
|
||||
}
|
||||
return extensions;
|
||||
}
|
||||
|
||||
Future<T> _sendJniIsolateMessage<T>(JniIsolateMessage message) {
|
||||
final completer = Completer<T>();
|
||||
|
||||
@@ -36,7 +36,7 @@ import 'dart:core' as core$_;
|
||||
import 'package:jni/_internal.dart' as jni$_;
|
||||
import 'package:jni/jni.dart' as jni$_;
|
||||
|
||||
/// from: `dev.k3vinb5.aniyomibridge.AniyomiBridge`
|
||||
/// from: `dev.k3vinb5.aniyomibridge.JAniyomiBridge`
|
||||
class JAniyomiBridge extends jni$_.JObject {
|
||||
@jni$_.internal
|
||||
@core$_.override
|
||||
@@ -48,21 +48,27 @@ class JAniyomiBridge extends jni$_.JObject {
|
||||
super.fromReference(reference);
|
||||
|
||||
static final _class = jni$_.JClass.forName(
|
||||
r'dev/k3vinb5/aniyomibridge/AniyomiBridge',
|
||||
r'dev/k3vinb5/aniyomibridge/JAniyomiBridge',
|
||||
);
|
||||
|
||||
/// The type which includes information such as the signature of this class.
|
||||
static const nullableType = $AniyomiBridge$NullableType();
|
||||
static const type = $AniyomiBridge$Type();
|
||||
static final _id_COROUTINE_UTILS = _class.staticFieldId(
|
||||
r'COROUTINE_UTILS',
|
||||
r'Ldev/k3vinb5/aniyomibridge/utils/CoroutineUtils;',
|
||||
static const nullableType = $JAniyomiBridge$NullableType();
|
||||
static const type = $JAniyomiBridge$Type();
|
||||
static final _id_classLoaders = _class.staticFieldId(
|
||||
r'classLoaders',
|
||||
r'Ljava/util/Map;',
|
||||
);
|
||||
|
||||
/// from: `static public final dev.k3vinb5.aniyomibridge.utils.CoroutineUtils COROUTINE_UTILS`
|
||||
/// from: `static public final java.util.Map<java.lang.String, java.lang.ClassLoader> classLoaders`
|
||||
/// The returned object must be released after use, by calling the [release] method.
|
||||
static jni$_.JObject? get COROUTINE_UTILS =>
|
||||
_id_COROUTINE_UTILS.get(_class, const jni$_.JObjectNullableType());
|
||||
static jni$_.JMap<jni$_.JString?, jni$_.JObject?>? get classLoaders =>
|
||||
_id_classLoaders.get(
|
||||
_class,
|
||||
const jni$_.JMapNullableType<jni$_.JString?, jni$_.JObject?>(
|
||||
jni$_.JStringNullableType(),
|
||||
jni$_.JObjectNullableType(),
|
||||
),
|
||||
);
|
||||
|
||||
static final _id_new$ = _class.constructorId(r'()V');
|
||||
|
||||
@@ -115,48 +121,10 @@ class JAniyomiBridge extends jni$_.JObject {
|
||||
|
||||
static final _id_loadAnimeExtension = _class.instanceMethodId(
|
||||
r'loadAnimeExtension',
|
||||
r'(Ljava/lang/String;Ljava/lang/String;)V',
|
||||
r'(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V',
|
||||
);
|
||||
|
||||
static final _loadAnimeExtension =
|
||||
jni$_.ProtectedJniExtensions.lookup<
|
||||
jni$_.NativeFunction<
|
||||
jni$_.JThrowablePtr Function(
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.JMethodIDPtr,
|
||||
jni$_.VarArgs<
|
||||
(jni$_.Pointer<jni$_.Void>, jni$_.Pointer<jni$_.Void>)
|
||||
>,
|
||||
)
|
||||
>
|
||||
>('globalEnv_CallVoidMethod')
|
||||
.asFunction<
|
||||
jni$_.JThrowablePtr Function(
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.JMethodIDPtr,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
)
|
||||
>();
|
||||
|
||||
/// from: `public void loadAnimeExtension(java.lang.String string, java.lang.String string1)`
|
||||
void loadAnimeExtension(jni$_.JString? string, jni$_.JString? string1) {
|
||||
final _$string = string?.reference ?? jni$_.jNullReference;
|
||||
final _$string1 = string1?.reference ?? jni$_.jNullReference;
|
||||
_loadAnimeExtension(
|
||||
reference.pointer,
|
||||
_id_loadAnimeExtension as jni$_.JMethodIDPtr,
|
||||
_$string.pointer,
|
||||
_$string1.pointer,
|
||||
).check();
|
||||
}
|
||||
|
||||
static final _id_unloadAnimeExtension = _class.instanceMethodId(
|
||||
r'unloadAnimeExtension',
|
||||
r'(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V',
|
||||
);
|
||||
|
||||
static final _unloadAnimeExtension =
|
||||
jni$_.ProtectedJniExtensions.lookup<
|
||||
jni$_.NativeFunction<
|
||||
jni$_.JThrowablePtr Function(
|
||||
@@ -182,8 +150,8 @@ class JAniyomiBridge extends jni$_.JObject {
|
||||
)
|
||||
>();
|
||||
|
||||
/// from: `public void unloadAnimeExtension(java.lang.String string, java.lang.String string1, java.lang.String string2)`
|
||||
void unloadAnimeExtension(
|
||||
/// from: `public void loadAnimeExtension(java.lang.String string, java.lang.String string1, java.lang.String string2)`
|
||||
void loadAnimeExtension(
|
||||
jni$_.JString? string,
|
||||
jni$_.JString? string1,
|
||||
jni$_.JString? string2,
|
||||
@@ -191,59 +159,54 @@ class JAniyomiBridge extends jni$_.JObject {
|
||||
final _$string = string?.reference ?? jni$_.jNullReference;
|
||||
final _$string1 = string1?.reference ?? jni$_.jNullReference;
|
||||
final _$string2 = string2?.reference ?? jni$_.jNullReference;
|
||||
_unloadAnimeExtension(
|
||||
_loadAnimeExtension(
|
||||
reference.pointer,
|
||||
_id_unloadAnimeExtension as jni$_.JMethodIDPtr,
|
||||
_id_loadAnimeExtension as jni$_.JMethodIDPtr,
|
||||
_$string.pointer,
|
||||
_$string1.pointer,
|
||||
_$string2.pointer,
|
||||
).check();
|
||||
}
|
||||
|
||||
static final _id_unloadAnimeExtension = _class.instanceMethodId(
|
||||
r'unloadAnimeExtension',
|
||||
r'(Ljava/lang/String;)V',
|
||||
);
|
||||
|
||||
static final _unloadAnimeExtension =
|
||||
jni$_.ProtectedJniExtensions.lookup<
|
||||
jni$_.NativeFunction<
|
||||
jni$_.JThrowablePtr Function(
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.JMethodIDPtr,
|
||||
jni$_.VarArgs<(jni$_.Pointer<jni$_.Void>,)>,
|
||||
)
|
||||
>
|
||||
>('globalEnv_CallVoidMethod')
|
||||
.asFunction<
|
||||
jni$_.JThrowablePtr Function(
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.JMethodIDPtr,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
)
|
||||
>();
|
||||
|
||||
/// from: `public void unloadAnimeExtension(java.lang.String string)`
|
||||
void unloadAnimeExtension(jni$_.JString? string) {
|
||||
final _$string = string?.reference ?? jni$_.jNullReference;
|
||||
_unloadAnimeExtension(
|
||||
reference.pointer,
|
||||
_id_unloadAnimeExtension as jni$_.JMethodIDPtr,
|
||||
_$string.pointer,
|
||||
).check();
|
||||
}
|
||||
|
||||
static final _id_loadMangaExtension = _class.instanceMethodId(
|
||||
r'loadMangaExtension',
|
||||
r'(Ljava/lang/String;Ljava/lang/String;)V',
|
||||
r'(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V',
|
||||
);
|
||||
|
||||
static final _loadMangaExtension =
|
||||
jni$_.ProtectedJniExtensions.lookup<
|
||||
jni$_.NativeFunction<
|
||||
jni$_.JThrowablePtr Function(
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.JMethodIDPtr,
|
||||
jni$_.VarArgs<
|
||||
(jni$_.Pointer<jni$_.Void>, jni$_.Pointer<jni$_.Void>)
|
||||
>,
|
||||
)
|
||||
>
|
||||
>('globalEnv_CallVoidMethod')
|
||||
.asFunction<
|
||||
jni$_.JThrowablePtr Function(
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.JMethodIDPtr,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
)
|
||||
>();
|
||||
|
||||
/// from: `public void loadMangaExtension(java.lang.String string, java.lang.String string1)`
|
||||
void loadMangaExtension(jni$_.JString? string, jni$_.JString? string1) {
|
||||
final _$string = string?.reference ?? jni$_.jNullReference;
|
||||
final _$string1 = string1?.reference ?? jni$_.jNullReference;
|
||||
_loadMangaExtension(
|
||||
reference.pointer,
|
||||
_id_loadMangaExtension as jni$_.JMethodIDPtr,
|
||||
_$string.pointer,
|
||||
_$string1.pointer,
|
||||
).check();
|
||||
}
|
||||
|
||||
static final _id_unloadMangaExtension = _class.instanceMethodId(
|
||||
r'unloadMangaExtension',
|
||||
r'(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V',
|
||||
);
|
||||
|
||||
static final _unloadMangaExtension =
|
||||
jni$_.ProtectedJniExtensions.lookup<
|
||||
jni$_.NativeFunction<
|
||||
jni$_.JThrowablePtr Function(
|
||||
@@ -269,8 +232,8 @@ class JAniyomiBridge extends jni$_.JObject {
|
||||
)
|
||||
>();
|
||||
|
||||
/// from: `public void unloadMangaExtension(java.lang.String string, java.lang.String string1, java.lang.String string2)`
|
||||
void unloadMangaExtension(
|
||||
/// from: `public void loadMangaExtension(java.lang.String string, java.lang.String string1, java.lang.String string2)`
|
||||
void loadMangaExtension(
|
||||
jni$_.JString? string,
|
||||
jni$_.JString? string1,
|
||||
jni$_.JString? string2,
|
||||
@@ -278,12 +241,45 @@ class JAniyomiBridge extends jni$_.JObject {
|
||||
final _$string = string?.reference ?? jni$_.jNullReference;
|
||||
final _$string1 = string1?.reference ?? jni$_.jNullReference;
|
||||
final _$string2 = string2?.reference ?? jni$_.jNullReference;
|
||||
_loadMangaExtension(
|
||||
reference.pointer,
|
||||
_id_loadMangaExtension as jni$_.JMethodIDPtr,
|
||||
_$string.pointer,
|
||||
_$string1.pointer,
|
||||
_$string2.pointer,
|
||||
).check();
|
||||
}
|
||||
|
||||
static final _id_unloadMangaExtension = _class.instanceMethodId(
|
||||
r'unloadMangaExtension',
|
||||
r'(Ljava/lang/String;)V',
|
||||
);
|
||||
|
||||
static final _unloadMangaExtension =
|
||||
jni$_.ProtectedJniExtensions.lookup<
|
||||
jni$_.NativeFunction<
|
||||
jni$_.JThrowablePtr Function(
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.JMethodIDPtr,
|
||||
jni$_.VarArgs<(jni$_.Pointer<jni$_.Void>,)>,
|
||||
)
|
||||
>
|
||||
>('globalEnv_CallVoidMethod')
|
||||
.asFunction<
|
||||
jni$_.JThrowablePtr Function(
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.JMethodIDPtr,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
)
|
||||
>();
|
||||
|
||||
/// from: `public void unloadMangaExtension(java.lang.String string)`
|
||||
void unloadMangaExtension(jni$_.JString? string) {
|
||||
final _$string = string?.reference ?? jni$_.jNullReference;
|
||||
_unloadMangaExtension(
|
||||
reference.pointer,
|
||||
_id_unloadMangaExtension as jni$_.JMethodIDPtr,
|
||||
_$string.pointer,
|
||||
_$string1.pointer,
|
||||
_$string2.pointer,
|
||||
).check();
|
||||
}
|
||||
|
||||
@@ -355,6 +351,204 @@ class JAniyomiBridge extends jni$_.JObject {
|
||||
);
|
||||
}
|
||||
|
||||
static final _id_getInstalledAnimeExtensions = _class.instanceMethodId(
|
||||
r'getInstalledAnimeExtensions',
|
||||
r'()Ljava/util/Map;',
|
||||
);
|
||||
|
||||
static final _getInstalledAnimeExtensions =
|
||||
jni$_.ProtectedJniExtensions.lookup<
|
||||
jni$_.NativeFunction<
|
||||
jni$_.JniResult Function(
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.JMethodIDPtr,
|
||||
)
|
||||
>
|
||||
>('globalEnv_CallObjectMethod')
|
||||
.asFunction<
|
||||
jni$_.JniResult Function(
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.JMethodIDPtr,
|
||||
)
|
||||
>();
|
||||
|
||||
/// from: `public java.util.Map<java.lang.String, java.lang.String> getInstalledAnimeExtensions()`
|
||||
/// The returned object must be released after use, by calling the [release] method.
|
||||
jni$_.JMap<jni$_.JString?, jni$_.JString?>? getInstalledAnimeExtensions() {
|
||||
return _getInstalledAnimeExtensions(
|
||||
reference.pointer,
|
||||
_id_getInstalledAnimeExtensions as jni$_.JMethodIDPtr,
|
||||
).object<jni$_.JMap<jni$_.JString?, jni$_.JString?>?>(
|
||||
const jni$_.JMapNullableType<jni$_.JString?, jni$_.JString?>(
|
||||
jni$_.JStringNullableType(),
|
||||
jni$_.JStringNullableType(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static final _id_getInstalledMangaExtensions = _class.instanceMethodId(
|
||||
r'getInstalledMangaExtensions',
|
||||
r'()Ljava/util/Map;',
|
||||
);
|
||||
|
||||
static final _getInstalledMangaExtensions =
|
||||
jni$_.ProtectedJniExtensions.lookup<
|
||||
jni$_.NativeFunction<
|
||||
jni$_.JniResult Function(
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.JMethodIDPtr,
|
||||
)
|
||||
>
|
||||
>('globalEnv_CallObjectMethod')
|
||||
.asFunction<
|
||||
jni$_.JniResult Function(
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.JMethodIDPtr,
|
||||
)
|
||||
>();
|
||||
|
||||
/// from: `public java.util.Map<java.lang.String, java.lang.String> getInstalledMangaExtensions()`
|
||||
/// The returned object must be released after use, by calling the [release] method.
|
||||
jni$_.JMap<jni$_.JString?, jni$_.JString?>? getInstalledMangaExtensions() {
|
||||
return _getInstalledMangaExtensions(
|
||||
reference.pointer,
|
||||
_id_getInstalledMangaExtensions as jni$_.JMethodIDPtr,
|
||||
).object<jni$_.JMap<jni$_.JString?, jni$_.JString?>?>(
|
||||
const jni$_.JMapNullableType<jni$_.JString?, jni$_.JString?>(
|
||||
jni$_.JStringNullableType(),
|
||||
jni$_.JStringNullableType(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static final _id_getAnimePreferences = _class.instanceMethodId(
|
||||
r'getAnimePreferences',
|
||||
r'(Ljava/lang/String;)Ljava/util/List;',
|
||||
);
|
||||
|
||||
static final _getAnimePreferences =
|
||||
jni$_.ProtectedJniExtensions.lookup<
|
||||
jni$_.NativeFunction<
|
||||
jni$_.JniResult Function(
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.JMethodIDPtr,
|
||||
jni$_.VarArgs<(jni$_.Pointer<jni$_.Void>,)>,
|
||||
)
|
||||
>
|
||||
>('globalEnv_CallObjectMethod')
|
||||
.asFunction<
|
||||
jni$_.JniResult Function(
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.JMethodIDPtr,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
)
|
||||
>();
|
||||
|
||||
/// from: `public java.util.List<dev.k3vinb5.aniyomibridge.PreferenceItem> getAnimePreferences(java.lang.String string)`
|
||||
/// The returned object must be released after use, by calling the [release] method.
|
||||
jni$_.JList<jni$_.JObject?>? getAnimePreferences(jni$_.JString? string) {
|
||||
final _$string = string?.reference ?? jni$_.jNullReference;
|
||||
return _getAnimePreferences(
|
||||
reference.pointer,
|
||||
_id_getAnimePreferences as jni$_.JMethodIDPtr,
|
||||
_$string.pointer,
|
||||
).object<jni$_.JList<jni$_.JObject?>?>(
|
||||
const jni$_.JListNullableType<jni$_.JObject?>(
|
||||
jni$_.JObjectNullableType(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static final _id_setAnimePreference = _class.instanceMethodId(
|
||||
r'setAnimePreference',
|
||||
r'(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V',
|
||||
);
|
||||
|
||||
static final _setAnimePreference =
|
||||
jni$_.ProtectedJniExtensions.lookup<
|
||||
jni$_.NativeFunction<
|
||||
jni$_.JThrowablePtr Function(
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.JMethodIDPtr,
|
||||
jni$_.VarArgs<
|
||||
(
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
)
|
||||
>,
|
||||
)
|
||||
>
|
||||
>('globalEnv_CallVoidMethod')
|
||||
.asFunction<
|
||||
jni$_.JThrowablePtr Function(
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.JMethodIDPtr,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
)
|
||||
>();
|
||||
|
||||
/// from: `public void setAnimePreference(java.lang.String string, java.lang.String string1, java.lang.Object object)`
|
||||
void setAnimePreference(
|
||||
jni$_.JString? string,
|
||||
jni$_.JString? string1,
|
||||
jni$_.JObject? object,
|
||||
) {
|
||||
final _$string = string?.reference ?? jni$_.jNullReference;
|
||||
final _$string1 = string1?.reference ?? jni$_.jNullReference;
|
||||
final _$object = object?.reference ?? jni$_.jNullReference;
|
||||
_setAnimePreference(
|
||||
reference.pointer,
|
||||
_id_setAnimePreference as jni$_.JMethodIDPtr,
|
||||
_$string.pointer,
|
||||
_$string1.pointer,
|
||||
_$object.pointer,
|
||||
).check();
|
||||
}
|
||||
|
||||
static final _id_setAnimePreferences = _class.instanceMethodId(
|
||||
r'setAnimePreferences',
|
||||
r'(Ljava/lang/String;Ljava/util/Map;)V',
|
||||
);
|
||||
|
||||
static final _setAnimePreferences =
|
||||
jni$_.ProtectedJniExtensions.lookup<
|
||||
jni$_.NativeFunction<
|
||||
jni$_.JThrowablePtr Function(
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.JMethodIDPtr,
|
||||
jni$_.VarArgs<
|
||||
(jni$_.Pointer<jni$_.Void>, jni$_.Pointer<jni$_.Void>)
|
||||
>,
|
||||
)
|
||||
>
|
||||
>('globalEnv_CallVoidMethod')
|
||||
.asFunction<
|
||||
jni$_.JThrowablePtr Function(
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.JMethodIDPtr,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
)
|
||||
>();
|
||||
|
||||
/// from: `public void setAnimePreferences(java.lang.String string, java.util.Map<java.lang.String, java.lang.Object> map)`
|
||||
void setAnimePreferences(
|
||||
jni$_.JString? string,
|
||||
jni$_.JMap<jni$_.JString?, jni$_.JObject?>? map,
|
||||
) {
|
||||
final _$string = string?.reference ?? jni$_.jNullReference;
|
||||
final _$map = map?.reference ?? jni$_.jNullReference;
|
||||
_setAnimePreferences(
|
||||
reference.pointer,
|
||||
_id_setAnimePreferences as jni$_.JMethodIDPtr,
|
||||
_$string.pointer,
|
||||
_$map.pointer,
|
||||
).check();
|
||||
}
|
||||
|
||||
static final _id_getAnimeSearchResults = _class.instanceMethodId(
|
||||
r'getAnimeSearchResults',
|
||||
r'(Ljava/lang/String;ILjava/lang/String;)Ljava/util/List;',
|
||||
@@ -646,13 +840,13 @@ class JAniyomiBridge extends jni$_.JObject {
|
||||
}
|
||||
}
|
||||
|
||||
final class $AniyomiBridge$NullableType extends jni$_.JObjType<JAniyomiBridge?> {
|
||||
final class $JAniyomiBridge$NullableType extends jni$_.JObjType<JAniyomiBridge?> {
|
||||
@jni$_.internal
|
||||
const $AniyomiBridge$NullableType();
|
||||
const $JAniyomiBridge$NullableType();
|
||||
|
||||
@jni$_.internal
|
||||
@core$_.override
|
||||
String get signature => r'Ldev/k3vinb5/aniyomibridge/AniyomiBridge;';
|
||||
String get signature => r'Ldev/k3vinb5/aniyomibridge/JAniyomiBridge;';
|
||||
|
||||
@jni$_.internal
|
||||
@core$_.override
|
||||
@@ -671,22 +865,22 @@ final class $AniyomiBridge$NullableType extends jni$_.JObjType<JAniyomiBridge?>
|
||||
final superCount = 1;
|
||||
|
||||
@core$_.override
|
||||
int get hashCode => ($AniyomiBridge$NullableType).hashCode;
|
||||
int get hashCode => ($JAniyomiBridge$NullableType).hashCode;
|
||||
|
||||
@core$_.override
|
||||
bool operator ==(Object other) {
|
||||
return other.runtimeType == ($AniyomiBridge$NullableType) &&
|
||||
other is $AniyomiBridge$NullableType;
|
||||
return other.runtimeType == ($JAniyomiBridge$NullableType) &&
|
||||
other is $JAniyomiBridge$NullableType;
|
||||
}
|
||||
}
|
||||
|
||||
final class $AniyomiBridge$Type extends jni$_.JObjType<JAniyomiBridge> {
|
||||
final class $JAniyomiBridge$Type extends jni$_.JObjType<JAniyomiBridge> {
|
||||
@jni$_.internal
|
||||
const $AniyomiBridge$Type();
|
||||
const $JAniyomiBridge$Type();
|
||||
|
||||
@jni$_.internal
|
||||
@core$_.override
|
||||
String get signature => r'Ldev/k3vinb5/aniyomibridge/AniyomiBridge;';
|
||||
String get signature => r'Ldev/k3vinb5/aniyomibridge/JAniyomiBridge;';
|
||||
|
||||
@jni$_.internal
|
||||
@core$_.override
|
||||
@@ -699,18 +893,18 @@ final class $AniyomiBridge$Type extends jni$_.JObjType<JAniyomiBridge> {
|
||||
@jni$_.internal
|
||||
@core$_.override
|
||||
jni$_.JObjType<JAniyomiBridge?> get nullableType =>
|
||||
const $AniyomiBridge$NullableType();
|
||||
const $JAniyomiBridge$NullableType();
|
||||
|
||||
@jni$_.internal
|
||||
@core$_.override
|
||||
final superCount = 1;
|
||||
|
||||
@core$_.override
|
||||
int get hashCode => ($AniyomiBridge$Type).hashCode;
|
||||
int get hashCode => ($JAniyomiBridge$Type).hashCode;
|
||||
|
||||
@core$_.override
|
||||
bool operator ==(Object other) {
|
||||
return other.runtimeType == ($AniyomiBridge$Type) &&
|
||||
other is $AniyomiBridge$Type;
|
||||
return other.runtimeType == ($JAniyomiBridge$Type) &&
|
||||
other is $JAniyomiBridge$Type;
|
||||
}
|
||||
}
|
||||
|
||||
309
lib/jmodels/jpreferenceitem.dart
Normal file
309
lib/jmodels/jpreferenceitem.dart
Normal file
@@ -0,0 +1,309 @@
|
||||
// AUTO GENERATED BY JNIGEN 0.14.2. 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' show Object, String, bool, double, int;
|
||||
import 'dart:core' as core$_;
|
||||
|
||||
import 'package:jni/_internal.dart' as jni$_;
|
||||
import 'package:jni/jni.dart' as jni$_;
|
||||
|
||||
/// from: `dev.k3vinb5.aniyomibridge.JPreferenceItem`
|
||||
class JPreferenceItem extends jni$_.JObject {
|
||||
@jni$_.internal
|
||||
@core$_.override
|
||||
final jni$_.JObjType<JPreferenceItem> $type;
|
||||
|
||||
@jni$_.internal
|
||||
JPreferenceItem.fromReference(jni$_.JReference reference)
|
||||
: $type = type,
|
||||
super.fromReference(reference);
|
||||
|
||||
static final _class = jni$_.JClass.forName(
|
||||
r'dev/k3vinb5/aniyomibridge/JPreferenceItem',
|
||||
);
|
||||
|
||||
/// The type which includes information such as the signature of this class.
|
||||
static const nullableType = $JPreferenceItem$NullableType();
|
||||
static const type = $JPreferenceItem$Type();
|
||||
static final _id_key = _class.instanceFieldId(r'key', r'Ljava/lang/String;');
|
||||
|
||||
/// from: `public java.lang.String key`
|
||||
/// The returned object must be released after use, by calling the [release] method.
|
||||
jni$_.JString? get key =>
|
||||
_id_key.get(this, const jni$_.JStringNullableType());
|
||||
|
||||
/// from: `public java.lang.String key`
|
||||
/// The returned object must be released after use, by calling the [release] method.
|
||||
set key(jni$_.JString? value) =>
|
||||
_id_key.set(this, const jni$_.JStringNullableType(), value);
|
||||
|
||||
static final _id_title = _class.instanceFieldId(
|
||||
r'title',
|
||||
r'Ljava/lang/String;',
|
||||
);
|
||||
|
||||
/// from: `public java.lang.String title`
|
||||
/// The returned object must be released after use, by calling the [release] method.
|
||||
jni$_.JString? get title =>
|
||||
_id_title.get(this, const jni$_.JStringNullableType());
|
||||
|
||||
/// from: `public java.lang.String title`
|
||||
/// The returned object must be released after use, by calling the [release] method.
|
||||
set title(jni$_.JString? value) =>
|
||||
_id_title.set(this, const jni$_.JStringNullableType(), value);
|
||||
|
||||
static final _id_type$1 = _class.instanceFieldId(
|
||||
r'type',
|
||||
r'Ljava/lang/String;',
|
||||
);
|
||||
|
||||
/// from: `public java.lang.String type`
|
||||
/// The returned object must be released after use, by calling the [release] method.
|
||||
jni$_.JString? get type$1 =>
|
||||
_id_type$1.get(this, const jni$_.JStringNullableType());
|
||||
|
||||
/// from: `public java.lang.String type`
|
||||
/// The returned object must be released after use, by calling the [release] method.
|
||||
set type$1(jni$_.JString? value) =>
|
||||
_id_type$1.set(this, const jni$_.JStringNullableType(), value);
|
||||
|
||||
static final _id_value = _class.instanceFieldId(
|
||||
r'value',
|
||||
r'Ljava/lang/Object;',
|
||||
);
|
||||
|
||||
/// from: `public java.lang.Object value`
|
||||
/// The returned object must be released after use, by calling the [release] method.
|
||||
jni$_.JObject? get value =>
|
||||
_id_value.get(this, const jni$_.JObjectNullableType());
|
||||
|
||||
/// from: `public java.lang.Object value`
|
||||
/// The returned object must be released after use, by calling the [release] method.
|
||||
set value(jni$_.JObject? value) =>
|
||||
_id_value.set(this, const jni$_.JObjectNullableType(), value);
|
||||
|
||||
static final _id_entries = _class.instanceFieldId(
|
||||
r'entries',
|
||||
r'Ljava/util/List;',
|
||||
);
|
||||
|
||||
/// from: `public java.util.List<java.lang.String> entries`
|
||||
/// The returned object must be released after use, by calling the [release] method.
|
||||
jni$_.JList<jni$_.JString?>? get entries => _id_entries.get(
|
||||
this,
|
||||
const jni$_.JListNullableType<jni$_.JString?>(jni$_.JStringNullableType()),
|
||||
);
|
||||
|
||||
/// from: `public java.util.List<java.lang.String> entries`
|
||||
/// The returned object must be released after use, by calling the [release] method.
|
||||
set entries(jni$_.JList<jni$_.JString?>? value) => _id_entries.set(
|
||||
this,
|
||||
const jni$_.JListNullableType<jni$_.JString?>(jni$_.JStringNullableType()),
|
||||
value,
|
||||
);
|
||||
|
||||
static final _id_entryValues = _class.instanceFieldId(
|
||||
r'entryValues',
|
||||
r'Ljava/util/List;',
|
||||
);
|
||||
|
||||
/// from: `public java.util.List<java.lang.String> entryValues`
|
||||
/// The returned object must be released after use, by calling the [release] method.
|
||||
jni$_.JList<jni$_.JString?>? get entryValues => _id_entryValues.get(
|
||||
this,
|
||||
const jni$_.JListNullableType<jni$_.JString?>(jni$_.JStringNullableType()),
|
||||
);
|
||||
|
||||
/// from: `public java.util.List<java.lang.String> entryValues`
|
||||
/// The returned object must be released after use, by calling the [release] method.
|
||||
set entryValues(jni$_.JList<jni$_.JString?>? value) => _id_entryValues.set(
|
||||
this,
|
||||
const jni$_.JListNullableType<jni$_.JString?>(jni$_.JStringNullableType()),
|
||||
value,
|
||||
);
|
||||
|
||||
static final _id_summary = _class.instanceFieldId(
|
||||
r'summary',
|
||||
r'Ljava/lang/String;',
|
||||
);
|
||||
|
||||
/// from: `public java.lang.String summary`
|
||||
/// The returned object must be released after use, by calling the [release] method.
|
||||
jni$_.JString? get summary =>
|
||||
_id_summary.get(this, const jni$_.JStringNullableType());
|
||||
|
||||
/// from: `public java.lang.String summary`
|
||||
/// The returned object must be released after use, by calling the [release] method.
|
||||
set summary(jni$_.JString? value) =>
|
||||
_id_summary.set(this, const jni$_.JStringNullableType(), value);
|
||||
|
||||
static final _id_new$ = _class.constructorId(
|
||||
r'(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;Ljava/util/List;Ljava/util/List;Ljava/lang/String;)V',
|
||||
);
|
||||
|
||||
static final _new$ =
|
||||
jni$_.ProtectedJniExtensions.lookup<
|
||||
jni$_.NativeFunction<
|
||||
jni$_.JniResult Function(
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.JMethodIDPtr,
|
||||
jni$_.VarArgs<
|
||||
(
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
)
|
||||
>,
|
||||
)
|
||||
>
|
||||
>('globalEnv_NewObject')
|
||||
.asFunction<
|
||||
jni$_.JniResult Function(
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.JMethodIDPtr,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
jni$_.Pointer<jni$_.Void>,
|
||||
)
|
||||
>();
|
||||
|
||||
/// from: `public void <init>(java.lang.String string, java.lang.String string1, java.lang.String string2, java.lang.Object object, java.util.List<java.lang.String> list, java.util.List<java.lang.String> list1, java.lang.String string3)`
|
||||
/// The returned object must be released after use, by calling the [release] method.
|
||||
factory JPreferenceItem(
|
||||
jni$_.JString? string,
|
||||
jni$_.JString? string1,
|
||||
jni$_.JString? string2,
|
||||
jni$_.JObject? object,
|
||||
jni$_.JList<jni$_.JString?>? list,
|
||||
jni$_.JList<jni$_.JString?>? list1,
|
||||
jni$_.JString? string3,
|
||||
) {
|
||||
final _$string = string?.reference ?? jni$_.jNullReference;
|
||||
final _$string1 = string1?.reference ?? jni$_.jNullReference;
|
||||
final _$string2 = string2?.reference ?? jni$_.jNullReference;
|
||||
final _$object = object?.reference ?? jni$_.jNullReference;
|
||||
final _$list = list?.reference ?? jni$_.jNullReference;
|
||||
final _$list1 = list1?.reference ?? jni$_.jNullReference;
|
||||
final _$string3 = string3?.reference ?? jni$_.jNullReference;
|
||||
return JPreferenceItem.fromReference(
|
||||
_new$(
|
||||
_class.reference.pointer,
|
||||
_id_new$ as jni$_.JMethodIDPtr,
|
||||
_$string.pointer,
|
||||
_$string1.pointer,
|
||||
_$string2.pointer,
|
||||
_$object.pointer,
|
||||
_$list.pointer,
|
||||
_$list1.pointer,
|
||||
_$string3.pointer,
|
||||
).reference,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final class $JPreferenceItem$NullableType
|
||||
extends jni$_.JObjType<JPreferenceItem?> {
|
||||
@jni$_.internal
|
||||
const $JPreferenceItem$NullableType();
|
||||
|
||||
@jni$_.internal
|
||||
@core$_.override
|
||||
String get signature => r'Ldev/k3vinb5/aniyomibridge/JPreferenceItem;';
|
||||
|
||||
@jni$_.internal
|
||||
@core$_.override
|
||||
JPreferenceItem? fromReference(jni$_.JReference reference) =>
|
||||
reference.isNull ? null : JPreferenceItem.fromReference(reference);
|
||||
@jni$_.internal
|
||||
@core$_.override
|
||||
jni$_.JObjType get superType => const jni$_.JObjectNullableType();
|
||||
|
||||
@jni$_.internal
|
||||
@core$_.override
|
||||
jni$_.JObjType<JPreferenceItem?> get nullableType => this;
|
||||
|
||||
@jni$_.internal
|
||||
@core$_.override
|
||||
final superCount = 1;
|
||||
|
||||
@core$_.override
|
||||
int get hashCode => ($JPreferenceItem$NullableType).hashCode;
|
||||
|
||||
@core$_.override
|
||||
bool operator ==(Object other) {
|
||||
return other.runtimeType == ($JPreferenceItem$NullableType) &&
|
||||
other is $JPreferenceItem$NullableType;
|
||||
}
|
||||
}
|
||||
|
||||
final class $JPreferenceItem$Type extends jni$_.JObjType<JPreferenceItem> {
|
||||
@jni$_.internal
|
||||
const $JPreferenceItem$Type();
|
||||
|
||||
@jni$_.internal
|
||||
@core$_.override
|
||||
String get signature => r'Ldev/k3vinb5/aniyomibridge/JPreferenceItem;';
|
||||
|
||||
@jni$_.internal
|
||||
@core$_.override
|
||||
JPreferenceItem fromReference(jni$_.JReference reference) =>
|
||||
JPreferenceItem.fromReference(reference);
|
||||
@jni$_.internal
|
||||
@core$_.override
|
||||
jni$_.JObjType get superType => const jni$_.JObjectNullableType();
|
||||
|
||||
@jni$_.internal
|
||||
@core$_.override
|
||||
jni$_.JObjType<JPreferenceItem?> get nullableType =>
|
||||
const $JPreferenceItem$NullableType();
|
||||
|
||||
@jni$_.internal
|
||||
@core$_.override
|
||||
final superCount = 1;
|
||||
|
||||
@core$_.override
|
||||
int get hashCode => ($JPreferenceItem$Type).hashCode;
|
||||
|
||||
@core$_.override
|
||||
bool operator ==(Object other) {
|
||||
return other.runtimeType == ($JPreferenceItem$Type) &&
|
||||
other is $JPreferenceItem$Type;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import 'package:path/path.dart' as path;
|
||||
|
||||
import 'jmodels/janiyomibridge.dart';
|
||||
import 'jmodels/jpage.dart';
|
||||
import 'jmodels/jpreferenceitem.dart';
|
||||
import 'jmodels/jsanime.dart';
|
||||
import 'jmodels/jschapter.dart';
|
||||
import 'jmodels/jsepisode.dart';
|
||||
@@ -21,7 +22,6 @@ class JniIsolate {
|
||||
static late Isolate _jvmIsolate;
|
||||
static late SendPort _jvmIsolateSendPort;
|
||||
// Instance variables
|
||||
late final Directory _supportDirectory;
|
||||
late final String _supportDirectoryPath;
|
||||
|
||||
Future<(bool, SendPort)> initJniIsolate(
|
||||
@@ -31,7 +31,6 @@ class JniIsolate {
|
||||
String dyLibDir,
|
||||
List<String> classPath,
|
||||
) async {
|
||||
_supportDirectory = supportDirectory;
|
||||
_supportDirectoryPath = supportDirectory.path;
|
||||
_jvmIsolate = await Isolate.spawn(
|
||||
_jniWorkerEntryPoint, {
|
||||
@@ -138,6 +137,7 @@ class JniIsolate {
|
||||
_loadAnimeExtension(
|
||||
jAniyomiBridge,
|
||||
message.args['extensionUrl'] as String,
|
||||
message.args['packageName'] as String,
|
||||
);
|
||||
mainIsolateSendPort.send(
|
||||
JniIsolateResponse(id: message.id),
|
||||
@@ -146,6 +146,7 @@ class JniIsolate {
|
||||
_loadMangaExtension(
|
||||
jAniyomiBridge,
|
||||
message.args['extensionUrl'] as String,
|
||||
message.args['packageName'] as String,
|
||||
);
|
||||
mainIsolateSendPort.send(
|
||||
JniIsolateResponse(id: message.id),
|
||||
@@ -153,8 +154,7 @@ class JniIsolate {
|
||||
case JniIsolateMessageType.unloadAnimeExtension:
|
||||
_unloadAnimeExtension(
|
||||
jAniyomiBridge,
|
||||
message.args['extensionName'] as String,
|
||||
message.args['extensionVersion'] as String,
|
||||
message.args['packageName'] as String,
|
||||
);
|
||||
mainIsolateSendPort.send(
|
||||
JniIsolateResponse(id: message.id),
|
||||
@@ -162,12 +162,48 @@ class JniIsolate {
|
||||
case JniIsolateMessageType.unloadMangaExtension:
|
||||
_unloadMangaExtension(
|
||||
jAniyomiBridge,
|
||||
message.args['extensionName'] as String,
|
||||
message.args['extensionVersion'] as String,
|
||||
message.args['packageName'] as String,
|
||||
);
|
||||
mainIsolateSendPort.send(
|
||||
JniIsolateResponse(id: message.id),
|
||||
);
|
||||
case JniIsolateMessageType.getAnimePreferences:
|
||||
List<JPreferenceItem> results = _getAnimePreferences(
|
||||
jAniyomiBridge,
|
||||
message.args['packageName'] as String,
|
||||
);
|
||||
mainIsolateSendPort.send(
|
||||
JniIsolateResponse(id: message.id, results: results),
|
||||
);
|
||||
case JniIsolateMessageType.setAnimePreference:
|
||||
_setAnimePreference(
|
||||
jAniyomiBridge,
|
||||
message.args['packageName'] as String,
|
||||
message.args['key'] as String,
|
||||
message.args['value'],
|
||||
);
|
||||
mainIsolateSendPort.send(
|
||||
JniIsolateResponse(id: message.id),
|
||||
);
|
||||
case JniIsolateMessageType.setAnimePreferences:
|
||||
_setAnimePreferences(
|
||||
jAniyomiBridge,
|
||||
message.args['packageName'] as String,
|
||||
message.args['values'] as Map<String, dynamic>,
|
||||
);
|
||||
mainIsolateSendPort.send(
|
||||
JniIsolateResponse(id: message.id),
|
||||
);
|
||||
case JniIsolateMessageType.getAnimeLoadedExtensions:
|
||||
List<String> results = _getAnimeLoadedExtensions(jAniyomiBridge);
|
||||
mainIsolateSendPort.send(
|
||||
JniIsolateResponse(id: message.id, results: results),
|
||||
);
|
||||
case JniIsolateMessageType.getMangaLoadedExtensions:
|
||||
List<String> results = _getMangaLoadedExtensions(jAniyomiBridge);
|
||||
mainIsolateSendPort.send(
|
||||
JniIsolateResponse(id: message.id, results: results),
|
||||
);
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
mainIsolateSendPort.send(
|
||||
@@ -190,7 +226,7 @@ class JniIsolate {
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
void dispose() {
|
||||
_jvmIsolate.kill(priority: Isolate.immediate);
|
||||
}
|
||||
|
||||
@@ -242,14 +278,14 @@ class JniIsolate {
|
||||
}
|
||||
|
||||
List<JSChapter> _getChapterList(JAniyomiBridge jAniyomiBridge, JSManga sManga, String source) {
|
||||
JList<JObject?>? episodeList = jAniyomiBridge.getEpisodeList(
|
||||
JList<JObject?>? chapterList = jAniyomiBridge.getChapterList(
|
||||
sManga,
|
||||
JString.fromString(source),
|
||||
);
|
||||
if (episodeList == null) {
|
||||
if (chapterList == null) {
|
||||
return [];
|
||||
}
|
||||
return episodeList
|
||||
return chapterList
|
||||
.cast<JObject?>()
|
||||
.where(_jObjIsNotNull)
|
||||
.map((jObj) => jObj!.as<JSChapter>(JSChapter.type))
|
||||
@@ -272,58 +308,125 @@ class JniIsolate {
|
||||
}
|
||||
|
||||
List<JPage> _getPageList(JAniyomiBridge jAniyomiBridge, JSChapter sChapter, String source) {
|
||||
JList<JObject?>? videoList = jAniyomiBridge.getVideoList(
|
||||
JList<JObject?>? pageList = jAniyomiBridge.getPageList(
|
||||
sChapter,
|
||||
JString.fromString(source),
|
||||
);
|
||||
if (videoList == null) {
|
||||
if (pageList == null) {
|
||||
return [];
|
||||
}
|
||||
return videoList
|
||||
return pageList
|
||||
.cast<JObject?>()
|
||||
.where(_jObjIsNotNull)
|
||||
.map((jObj) => jObj!.as<JPage>(JPage.type))
|
||||
.toList();
|
||||
}
|
||||
|
||||
void _loadAnimeExtension(JAniyomiBridge jAniyomiBridge, String extensionUrl) {
|
||||
void _loadAnimeExtension(JAniyomiBridge jAniyomiBridge, String extensionUrl, String packageName) {
|
||||
final repoName = _resolveRepoNameFromUrl(extensionUrl);
|
||||
jAniyomiBridge.loadAnimeExtension(
|
||||
JString.fromString(extensionUrl),
|
||||
JString.fromString(
|
||||
path.join(_supportDirectoryPath, _aniyomiBridgeAnimeExtensionsDir),
|
||||
path.join(_supportDirectoryPath, _aniyomiBridgeAnimeExtensionsDir, repoName),
|
||||
),
|
||||
JString.fromString(packageName),
|
||||
);
|
||||
}
|
||||
|
||||
void _unloadAnimeExtension(JAniyomiBridge jAniyomiBridge, String extensionName, String extensionVersion) {
|
||||
void _unloadAnimeExtension(JAniyomiBridge jAniyomiBridge, String packageName) {
|
||||
jAniyomiBridge.unloadAnimeExtension(
|
||||
JString.fromString(extensionName),
|
||||
JString.fromString(extensionVersion),
|
||||
JString.fromString(
|
||||
path.join(_supportDirectoryPath, _aniyomiBridgeAnimeExtensionsDir),
|
||||
),
|
||||
JString.fromString(packageName),
|
||||
);
|
||||
}
|
||||
|
||||
void _loadMangaExtension(JAniyomiBridge jAniyomiBridge, String extensionUrl) {
|
||||
void _loadMangaExtension(JAniyomiBridge jAniyomiBridge, String extensionUrl, String packageName) {
|
||||
final repoName = _resolveRepoNameFromUrl(extensionUrl);
|
||||
jAniyomiBridge.loadMangaExtension(
|
||||
JString.fromString(extensionUrl),
|
||||
JString.fromString(
|
||||
path.join(_supportDirectoryPath, _aniyomiBridgeMangaExtensionsDir),
|
||||
path.join(_supportDirectoryPath, _aniyomiBridgeMangaExtensionsDir, repoName),
|
||||
),
|
||||
JString.fromString(packageName),
|
||||
);
|
||||
}
|
||||
|
||||
void _unloadMangaExtension(JAniyomiBridge jAniyomiBridge, String extensionName, String extensionVersion) {
|
||||
void _unloadMangaExtension(JAniyomiBridge jAniyomiBridge, String packageName) {
|
||||
jAniyomiBridge.unloadMangaExtension(
|
||||
JString.fromString(extensionName),
|
||||
JString.fromString(extensionVersion),
|
||||
JString.fromString(
|
||||
path.join(_supportDirectoryPath, _aniyomiBridgeMangaExtensionsDir),
|
||||
),
|
||||
JString.fromString(packageName),
|
||||
);
|
||||
}
|
||||
|
||||
/// Resolves a repo name from an extension URL to use as a subdirectory.
|
||||
/// Extracts the hostname and sanitizes it for filesystem use.
|
||||
String _resolveRepoNameFromUrl(String extensionUrl) {
|
||||
try {
|
||||
final uri = Uri.parse(extensionUrl);
|
||||
final host = uri.host;
|
||||
if (host.isNotEmpty) {
|
||||
// Sanitize: replace non-alphanumeric with underscore
|
||||
return host.replaceAll(RegExp(r'[^a-zA-Z0-9._-]'), '_');
|
||||
}
|
||||
} catch (_) {
|
||||
// Fall through to default
|
||||
}
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
List<JPreferenceItem> _getAnimePreferences(JAniyomiBridge jAniyomiBridge, String packageName) {
|
||||
JList<JObject?>? prefs = jAniyomiBridge.getAnimePreferences(JString.fromString(packageName));
|
||||
if (prefs == null) return [];
|
||||
return prefs
|
||||
.cast<JObject?>()
|
||||
.where(_jObjIsNotNull)
|
||||
.map((jObj) => jObj!.as<JPreferenceItem>(JPreferenceItem.type))
|
||||
.toList();
|
||||
}
|
||||
|
||||
void _setAnimePreference(JAniyomiBridge jAniyomiBridge, String packageName, String key, dynamic value) {
|
||||
JObject jValue;
|
||||
if (value is bool) {
|
||||
jValue = JBoolean(value);
|
||||
} else if (value is String) {
|
||||
jValue = JString.fromString(value);
|
||||
} else if (value is Set || value is List) {
|
||||
// Java side splits String by ";" for MultiSelectListPreference
|
||||
jValue = JString.fromString(value.join(';'));
|
||||
} else {
|
||||
throw UnsupportedError('Unsupported preference value type: ${value.runtimeType}');
|
||||
}
|
||||
jAniyomiBridge.setAnimePreference(
|
||||
JString.fromString(packageName),
|
||||
JString.fromString(key),
|
||||
jValue,
|
||||
);
|
||||
}
|
||||
|
||||
void _setAnimePreferences(JAniyomiBridge jAniyomiBridge, String packageName, Map<String, dynamic> values) {
|
||||
for (final entry in values.entries) {
|
||||
_setAnimePreference(jAniyomiBridge, packageName, entry.key, entry.value);
|
||||
}
|
||||
}
|
||||
|
||||
List<String> _getAnimeLoadedExtensions(JAniyomiBridge jAniyomiBridge) {
|
||||
JList<JObject?>? loaded = jAniyomiBridge.getAnimeLoadedExtensions();
|
||||
if (loaded == null) return [];
|
||||
return loaded
|
||||
.cast<JObject?>()
|
||||
.where(_jObjIsNotNull)
|
||||
.map((jObj) => (jObj!.as<JString>(JString.type)).toDartString())
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<String> _getMangaLoadedExtensions(JAniyomiBridge jAniyomiBridge) {
|
||||
JList<JObject?>? loaded = jAniyomiBridge.getMangaLoadedExtensions();
|
||||
if (loaded == null) return [];
|
||||
return loaded
|
||||
.cast<JObject?>()
|
||||
.where(_jObjIsNotNull)
|
||||
.map((jObj) => (jObj!.as<JString>(JString.type)).toDartString())
|
||||
.toList();
|
||||
}
|
||||
|
||||
bool Function(JObject? jObj) get _jObjIsNotNull =>
|
||||
(jObj) => jObj != null;
|
||||
}
|
||||
|
||||
@@ -32,4 +32,9 @@ enum JniIsolateMessageType {
|
||||
loadMangaExtension,
|
||||
unloadAnimeExtension,
|
||||
unloadMangaExtension,
|
||||
getAnimePreferences,
|
||||
setAnimePreference,
|
||||
setAnimePreferences,
|
||||
getAnimeLoadedExtensions,
|
||||
getMangaLoadedExtensions,
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
class Extension {
|
||||
final String name;
|
||||
final String version;
|
||||
final String packageName;
|
||||
|
||||
Extension({required this.name, required this.version});
|
||||
Extension({required this.name, required this.version, required this.packageName});
|
||||
}
|
||||
Reference in New Issue
Block a user