wallpapers_kit 0.1.0
wallpapers_kit: ^0.1.0 copied to clipboard
A drop-in wallpaper gallery toolkit for Flutter: grid, category, full-screen viewer, favorites, set-as-wallpaper and download — all styled from your app's own ThemeData so it automatically follows you [...]
wallpapers_kit #
A drop-in wallpaper gallery toolkit for Flutter: grid, category strip, full-screen
viewer, favorites, set-as-wallpaper, and download — styled from your app's own
ThemeData, so it follows your light/dark theme automatically.
wallpapers_kit is UI + actions only. It has no bundled wallpapers, no network client,
and shows no ads — you supply the List<Wallpaper> (from your own API, a hardcoded
list, whatever) and, optionally, your own ad-gating logic.
Features #
WallpaperGridView— a lazily-paginated grid with an optional header slot.WallpaperCategoryStrip— a horizontal category row you can drop into that header.WallpaperViewer— full-screen swipeable viewer with info / favorite / download / set-as-wallpaper.WallpaperFavoritesView— a favorites grid backed by a pluggableWallpaperFavoritesStore.- Set wallpaper to home screen, lock screen, or both (via
wallpaper_manager_plus). - Download to the device gallery (via
gal), with correct scoped-storage permission handling on Android andNSPhotoLibraryAddUsageDescriptionon iOS. - Theme-adaptive by default — every color is read from
Theme.of(context)unless you override it with aWallpapersKitStyle. - Ad-free, hook-based —
onBeforeSetWallpaper/onBeforeDownloadcallbacks let your app gate actions behind a rewarded ad, a dialog, or a paywall. wallpapers_kit itself never shows ads.
Getting started #
dependencies:
wallpapers_kit: ^0.1.0
Android #
wallpaper_manager_plus's own manifest declares WRITE_EXTERNAL_STORAGE and
READ_EXTERNAL_STORAGE without a maxSdkVersion cap, and Android's manifest
merger auto-includes that in your app regardless of what you declare — an uncapped
READ_EXTERNAL_STORAGE is exactly the kind of unjustified broad media permission
Play Console's review flags (wallpapers_kit never reads the gallery, only writes to
it). Override both with tools:node="replace" in
android/app/src/main/AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29"
tools:node="replace" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32"
tools:node="replace" />
<!-- ... -->
</manifest>
Without tools:node="replace", the cap you declare doesn't win — the plugin's
uncapped version merges straight through into the built APK. Verified against this
package's own example/ build (build/app/intermediates/merged_manifest/.../ AndroidManifest.xml shows both permissions correctly capped only with this fix in
place). No READ_MEDIA_IMAGES is needed or declared — wallpapers_kit only ever writes
new files, it never reads or browses existing photos.
(See example/android/app/src/main/AndroidManifest.xml for the full, commented version.)
iOS #
Two things:
-
Add a usage description for saving photos to
ios/Runner/Info.plist:<key>NSPhotoLibraryAddUsageDescription</key> <string>Used to save downloaded wallpapers to your photo library.</string> -
permission_handlerships every permission type disabled by default on iOS — theInfo.pliststring above isn't enough on its own. After your firstpod install, add this toios/Podfile(inside the existingpost_install do |installer|block'sinstaller.pods_project.targets.each do |target|loop) so the "add-only photos" permission actually compiles in:target.build_configurations.each do |config| config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)', 'PERMISSION_PHOTOS_ADD_ONLY=1'] endSkip this and
Permission.photosAddOnly.request()silently reports denied on iOS regardless of whatInfo.plistsays. See thepermission_handlerREADME for the full per-permission macro table if your app requests other permission types too.
Usage #
import 'package:wallpapers_kit/wallpapers_kit.dart';
final wallpapers = [
const Wallpaper(url: 'https://example.com/1.jpg', category: 'Nature'),
const Wallpaper(url: 'https://example.com/2.jpg', category: 'Nature'),
];
Scaffold(
appBar: AppBar(title: const Text('Wallpapers')),
body: WallpaperGridView(
wallpapers: wallpapers,
favoritesStore: SharedPreferencesFavoritesStore(),
),
);
That's a working grid → tap-to-fullscreen-viewer → set/download flow, once wallpapers
is populated. See the next section for how to actually populate it from your API.
Because every widget pulls its colors from Theme.of(context), wrapping your
MaterialApp with theme: / darkTheme: / themeMode: is all it takes for
wallpapers_kit to match — no extra wiring.
Loading wallpapers from your own API #
wallpapers_kit has no HTTP client of its own — fetch JSON however you like (http,
dio, ...) and turn it into a List<Wallpaper>. A category name mapped to a plain
list of image URLs — the same shape hacking_walls_app's API already returns — is
enough:
{
"wallpapers": {
"Nature": ["https://cdn.example.com/n1.jpg", "https://cdn.example.com/n2.jpg"],
"Abstract": ["https://cdn.example.com/a1.jpg"]
}
}
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:wallpapers_kit/wallpapers_kit.dart';
Future<Map<String, List<Wallpaper>>> fetchCatalog() async {
final response = await http.get(Uri.parse('https://your-api.example.com/config'));
final raw =
(jsonDecode(response.body)['wallpapers'] as Map).cast<String, dynamic>();
return raw.map((category, urls) => MapEntry(
category,
(urls as List)
.map((url) => Wallpaper(url: url as String, category: category))
.toList(),
));
}
A few things worth knowing:
- Only
urlis required.idandthumbnailUrlboth default to it, so a plain URL string per wallpaper is enough — nothing to add to your existing API. - Extra fields in the JSON are ignored. If your config also has other top-level
keys (store IDs, ad settings, ...),
fetchCatalogabove only readswallpapers, so they're safely skipped. - Static file or live API — same code. Whether that JSON comes from a real endpoint or a static file you host somewhere (Firebase Hosting, S3, a GitHub raw URL), it's the same HTTP GET.
Feed the result into a FutureBuilder and hand the flattened list to
WallpaperGridView once it resolves:
FutureBuilder<Map<String, List<Wallpaper>>>(
future: fetchCatalog(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const Center(child: CircularProgressIndicator());
final all = snapshot.data!.values.expand((w) => w).toList();
return WallpaperGridView(wallpapers: all);
},
);
Want the grid to survive being offline? Cache the raw JSON yourself (e.g. in
SharedPreferences) and fall back to it when the request fails — the same pattern
hacking_walls_app's HackRemoteConfigService uses. That's app-specific (your
endpoint, your cache key), which is why it isn't part of wallpapers_kit — the package
starts one step later, once you already have a List<Wallpaper>.
Gating actions behind your own ads #
WallpaperGridView(
wallpapers: wallpapers,
favoritesStore: favoritesStore,
onBeforeDownload: () async {
// show your rewarded ad here; return true to proceed, false to cancel
return await MyAdManager.showRewarded();
},
onBeforeSetWallpaper: () async => MyAdManager.showRewarded(),
);
Categories #
WallpaperGridView(
wallpapers: allWallpapers,
header: WallpaperCategoryStrip(
categories: [
WallpaperCategory(title: 'Nature', thumbnailUrl: natureWallpapers.first.gridUrl),
WallpaperCategory(title: 'Abstract', thumbnailUrl: abstractWallpapers.first.gridUrl),
],
onCategoryTap: (category) => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => Scaffold(
appBar: AppBar(title: Text(category.title)),
body: WallpaperGridView(wallpapers: byCategory[category.title]!),
),
),
),
),
);
Overriding the theme #
Every widget accepts a style: WallpapersKitStyle(...). Leave a field null to keep
following the app theme; set it to pin a specific color regardless of theme (e.g. a
full-screen viewer that always stays black):
WallpaperViewer(
wallpapers: wallpapers,
initialIndex: 0,
style: const WallpapersKitStyle(
viewerBackgroundColor: Colors.black,
favoriteActiveColor: Colors.redAccent,
),
);
Full example #
See example/lib/main.dart for a complete app with a
light/dark theme toggle, categories, favorites, and an ad-gate stub showing exactly
where to plug in your own ad SDK.
Additional information #
Favorites persistence is pluggable — implement WallpaperFavoritesStore yourself if
you'd rather sync favorites to your own backend instead of using the bundled
SharedPreferences-backed default.
Contributions and issues welcome.