281 lines
7.1 KiB
Markdown
281 lines
7.1 KiB
Markdown
# RuleGel
|
|
|
|
RuleGel is a single Flutter monolith for browsing booru/gelbooru/rule34-like providers with a Pinterest-style feed. It does not start or require a separate backend server. Provider access, caching, favorites, collections, search history, settings, downloads, read-only comments where APIs allow them, and health checks all run locally inside the Flutter app.
|
|
|
|
## Stack
|
|
|
|
- Dart and Flutter
|
|
- Riverpod for dependency injection
|
|
- Dio for provider HTTP APIs
|
|
- Isar for local storage
|
|
- connectivity_plus for network state
|
|
- build_runner with Isar/freezed/json_serializable dependencies available for generated code workflows
|
|
- go_router for navigation
|
|
- cached_network_image and flutter_staggered_grid_view for the Pinterest-style feed
|
|
- media_kit for video posts
|
|
|
|
## Structure
|
|
|
|
- `lib/core/http`: Dio setup, retry/backoff, network info
|
|
- `lib/core/database`: Isar database and entity mappings
|
|
- `lib/core/cache`: metadata cache helpers
|
|
- `lib/core/errors`: app exceptions and failures
|
|
- `lib/core/utils`: `Result<T>` and logging
|
|
- `lib/backend/models`: domain models
|
|
- `lib/backend/providers`: provider interface, concrete providers, factory, manager
|
|
- `lib/backend/repositories`: local persistence access
|
|
- `lib/backend/services`: app-facing service layer
|
|
- `lib/backend/di`: Riverpod providers
|
|
- `lib/app`: app shell, router, theme, responsive helpers
|
|
- `lib/shared/widgets`: reusable UI pieces
|
|
- `lib/features`: feed, post details, search, favorites, collections, providers, settings
|
|
|
|
## Frontend Routes
|
|
|
|
- `/`: masonry feed
|
|
- `/search`: recent searches and autocomplete
|
|
- `/post/:providerId/:postId`: post details
|
|
- `/favorites`: favorite posts
|
|
- `/collections`: boards
|
|
- `/collections/:collectionId`: posts inside a collection
|
|
- `/providers`: provider management
|
|
- `/providers/new`: provider form for create/edit
|
|
- `/providers/check`: provider health checks
|
|
- `/settings`: app settings
|
|
|
|
## Frontend And Backend Integration
|
|
|
|
The UI calls backend services through Riverpod providers from `lib/backend/di/backend_providers.dart`.
|
|
|
|
- `FeedController` calls `FeedService`, `SearchService`, and `ProviderManager`.
|
|
- `PostDetailsScreen` uses cached post metadata first, then `ProviderManager.getPost`.
|
|
- `PostCard` and details actions call `FavoriteService` and `CollectionService`.
|
|
- Provider screens call `ProviderManager` and `ProviderCheckService`.
|
|
- Settings call `SettingsService` and `CacheService`.
|
|
|
|
Business logic stays in backend/core services. UI controllers only coordinate state and user actions.
|
|
|
|
## Providers
|
|
|
|
The MVP ships with seed configs for:
|
|
|
|
- Gelbooru: `https://gelbooru.com`
|
|
- Rule34: `https://api.rule34.xxx`
|
|
- Safebooru: `https://safebooru.org`
|
|
|
|
Providers implement:
|
|
|
|
```dart
|
|
abstract class ContentProvider {
|
|
String get id;
|
|
String get name;
|
|
String get baseUrl;
|
|
|
|
Future<List<Post>> searchPosts({
|
|
required List<String> tags,
|
|
required int page,
|
|
int limit = 50,
|
|
String? rating,
|
|
});
|
|
|
|
Future<Post?> getPost(String id);
|
|
Future<ProviderHealth> checkHealth();
|
|
}
|
|
```
|
|
|
|
Gelbooru and Rule34 use the Gelbooru-compatible dapi endpoint:
|
|
|
|
```text
|
|
/index.php?page=dapi&s=post&q=index&json=1
|
|
```
|
|
|
|
Danbooru uses:
|
|
|
|
```text
|
|
/posts.json
|
|
```
|
|
|
|
All provider responses are normalized to `Post`.
|
|
|
|
## Adding A Provider
|
|
|
|
1. Add a mapper in `lib/backend/mappers`.
|
|
2. Implement `ContentProvider` in `lib/backend/providers`.
|
|
3. Register the `apiType` in `ProviderFactory`.
|
|
4. Save a `ContentProviderConfig` with `ProviderManager.addCustomProvider`.
|
|
|
|
For custom providers, MVP supports these `apiType` values:
|
|
|
|
- `gelbooru`
|
|
- `rule34`
|
|
- `danbooru`
|
|
|
|
Unsupported types return `ProviderUnavailableException` instead of crashing the app.
|
|
|
|
## ProviderManager
|
|
|
|
`ProviderManager` loads enabled provider configs from Isar, sorts them by `priority`, creates provider instances through `ProviderFactory`, checks health, and performs multi-provider search.
|
|
|
|
Search behavior:
|
|
|
|
- Enabled providers are tried even if an older health check marked them offline.
|
|
- A failed provider marks itself offline for diagnostics.
|
|
- Other providers continue returning posts.
|
|
- Results are combined into a single list.
|
|
|
|
## Health Checks
|
|
|
|
Use `ProviderCheckService`:
|
|
|
|
```dart
|
|
final health = await ref
|
|
.read(providerCheckServiceProvider)
|
|
.checkOne('gelbooru');
|
|
|
|
final all = await ref
|
|
.read(providerCheckServiceProvider)
|
|
.checkAll();
|
|
```
|
|
|
|
`checkAll()` runs in parallel with a concurrency limit of 3 and saves `ProviderHealth` in Isar.
|
|
|
|
## Using Services From Flutter UI
|
|
|
|
Initialize your app with Riverpod:
|
|
|
|
```dart
|
|
ProviderScope(
|
|
child: MyApp(),
|
|
);
|
|
```
|
|
|
|
Load a feed:
|
|
|
|
```dart
|
|
final feed = ref.read(feedServiceProvider);
|
|
final result = await feed.refresh(
|
|
tags: ['landscape'],
|
|
rating: 'safe',
|
|
);
|
|
```
|
|
|
|
Infinite scroll:
|
|
|
|
```dart
|
|
final next = await ref.read(feedServiceProvider).loadNextPage(
|
|
tags: ['landscape'],
|
|
);
|
|
```
|
|
|
|
Search history:
|
|
|
|
```dart
|
|
final search = ref.read(searchServiceProvider);
|
|
final tags = search.parseTags('cat cute');
|
|
await search.saveSearch('cat cute', 120);
|
|
final recent = await search.recentSearches();
|
|
```
|
|
|
|
Favorites:
|
|
|
|
```dart
|
|
await ref.read(favoriteServiceProvider).addFavorite(post);
|
|
final saved = await ref
|
|
.read(favoriteServiceProvider)
|
|
.isFavorite(post.id, post.providerId);
|
|
```
|
|
|
|
Collections:
|
|
|
|
```dart
|
|
final collection = await ref
|
|
.read(collectionServiceProvider)
|
|
.createCollection('Inspiration', 'Reference posts');
|
|
|
|
await ref
|
|
.read(collectionServiceProvider)
|
|
.addPostToCollection(collection.data.id, post);
|
|
```
|
|
|
|
Settings export/import:
|
|
|
|
```dart
|
|
final settings = ref.read(settingsServiceProvider);
|
|
final json = await settings.exportSettingsToJson();
|
|
await settings.importSettingsFromJson(json.data);
|
|
```
|
|
|
|
## Local Storage
|
|
|
|
Isar stores:
|
|
|
|
- provider configs
|
|
- provider health
|
|
- cached post metadata
|
|
- favorites
|
|
- collections
|
|
- collection-post links
|
|
- search history
|
|
- app settings
|
|
|
|
Post cache stores metadata only. It does not download or persist original media files.
|
|
|
|
## Safety Notes
|
|
|
|
- The app requests API metadata only.
|
|
- It does not bypass site limits or authentication.
|
|
- Dio uses a clear User-Agent.
|
|
- Retry is limited and uses exponential backoff.
|
|
- Provider failures are isolated so one offline provider does not break the feed.
|
|
|
|
## Development
|
|
|
|
Fetch dependencies:
|
|
|
|
```bash
|
|
flutter pub get
|
|
```
|
|
|
|
On Windows, Flutter plugins require Developer Mode for symlink support. If `flutter pub get` or desktop builds show a symlink warning, enable Developer Mode in Windows settings.
|
|
|
|
Generate Isar code:
|
|
|
|
```bash
|
|
dart run build_runner build --delete-conflicting-outputs
|
|
```
|
|
|
|
Run tests:
|
|
|
|
```bash
|
|
flutter test
|
|
```
|
|
|
|
Run the app:
|
|
|
|
```bash
|
|
flutter run -d windows
|
|
flutter run -d linux
|
|
flutter run -d android
|
|
```
|
|
|
|
Build release artifacts:
|
|
|
|
```bash
|
|
flutter build windows
|
|
flutter build linux
|
|
flutter build apk
|
|
```
|
|
|
|
Notes:
|
|
|
|
- Windows builds require Windows Developer Mode when plugins are used.
|
|
- Linux builds must be run on a Linux host.
|
|
- Android builds require Android SDK and `ANDROID_HOME`.
|
|
|
|
## Adding A Screen
|
|
|
|
1. Add a route in `lib/app/router.dart`.
|
|
2. Add feature state/controller/screen under `lib/features/<name>/presentation`.
|
|
3. Use backend providers from `backend_providers.dart`; do not recreate Dio, Isar, or provider logic in UI code.
|
|
4. Add loading, error, empty, and success states.
|