GelRuleApp
GelRuleApp 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, 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 infolib/core/database: Isar database and entity mappingslib/core/cache: metadata cache helperslib/core/errors: app exceptions and failureslib/core/utils:Result<T>and logginglib/backend/models: domain modelslib/backend/providers: provider interface, concrete providers, factory, managerlib/backend/repositories: local persistence accesslib/backend/services: app-facing service layerlib/backend/di: Riverpod providerslib/app: app shell, router, theme, responsive helperslib/shared/widgets: reusable UI pieceslib/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.
FeedControllercallsFeedService,SearchService, andProviderManager.PostDetailsScreenuses cached post metadata first, thenProviderManager.getPost.PostCardand details actions callFavoriteServiceandCollectionService.- Provider screens call
ProviderManagerandProviderCheckService. - Settings call
SettingsServiceandCacheService.
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:
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:
/index.php?page=dapi&s=post&q=index&json=1
Danbooru uses:
/posts.json
All provider responses are normalized to Post.
Adding A Provider
- Add a mapper in
lib/backend/mappers. - Implement
ContentProviderinlib/backend/providers. - Register the
apiTypeinProviderFactory. - Save a
ContentProviderConfigwithProviderManager.addCustomProvider.
For custom providers, MVP supports these apiType values:
gelboorurule34danbooru
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:
- Offline providers are skipped based on saved health.
- A failed provider marks itself offline.
- Other providers continue returning posts.
- Results are combined into a single list.
Health Checks
Use ProviderCheckService:
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:
ProviderScope(
child: MyApp(),
);
Load a feed:
final feed = ref.read(feedServiceProvider);
final result = await feed.refresh(
tags: ['landscape'],
rating: 'safe',
);
Infinite scroll:
final next = await ref.read(feedServiceProvider).loadNextPage(
tags: ['landscape'],
);
Search history:
final search = ref.read(searchServiceProvider);
final tags = search.parseTags('cat cute');
await search.saveSearch('cat cute', 120);
final recent = await search.recentSearches();
Favorites:
await ref.read(favoriteServiceProvider).addFavorite(post);
final saved = await ref
.read(favoriteServiceProvider)
.isFavorite(post.id, post.providerId);
Collections:
final collection = await ref
.read(collectionServiceProvider)
.createCollection('Inspiration', 'Reference posts');
await ref
.read(collectionServiceProvider)
.addPostToCollection(collection.data.id, post);
Settings export/import:
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:
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:
dart run build_runner build --delete-conflicting-outputs
Run tests:
flutter test
Run the app:
flutter run -d windows
flutter run -d linux
flutter run -d android
Build release artifacts:
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
- Add a route in
lib/app/router.dart. - Add feature state/controller/screen under
lib/features/<name>/presentation. - Use backend providers from
backend_providers.dart; do not recreate Dio, Isar, or provider logic in UI code. - Add loading, error, empty, and success states.