wallpapers_kit 0.1.0 copy "wallpapers_kit: ^0.1.0" to clipboard
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 pluggable WallpaperFavoritesStore.
  • 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 and NSPhotoLibraryAddUsageDescription on iOS.
  • Theme-adaptive by default — every color is read from Theme.of(context) unless you override it with a WallpapersKitStyle.
  • Ad-free, hook-basedonBeforeSetWallpaper / onBeforeDownload callbacks 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:

  1. 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>
    
  2. permission_handler ships every permission type disabled by default on iOS — the Info.plist string above isn't enough on its own. After your first pod install, add this to ios/Podfile (inside the existing post_install do |installer| block's installer.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']
    end
    

    Skip this and Permission.photosAddOnly.request() silently reports denied on iOS regardless of what Info.plist says. See the permission_handler README 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 url is required. id and thumbnailUrl both 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, ...), fetchCatalog above only reads wallpapers, 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.

0
likes
0
points
9
downloads

Publisher

unverified uploader

Weekly Downloads

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 your light/dark theme.

Repository (GitHub)
View/report issues

Topics

#wallpaper #theming #gallery #image

License

unknown (license)

Dependencies

cached_network_image, device_info_plus, flutter, flutter_cache_manager, gal, permission_handler, shared_preferences, wallpaper_manager_plus

More

Packages that depend on wallpapers_kit