winche_storage 2.0.0 copy "winche_storage: ^2.0.0" to clipboard
winche_storage: ^2.0.0 copied to clipboard

Dart SDK for the WincheStorage backend - a reference-based API with resumable, multipart-aware upload and download tasks for any storage backend.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:winche_storage/winche_storage.dart';
import 'package:file_picker/file_picker.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final storage = WincheStorage(
    WincheStorageConfig(
      uri: Uri.parse('http://localhost:5209/files'),
      directoryResolver: () async {
        final dir = await getApplicationDocumentsDirectory();
        return p.join(dir.path, 'winche_files');
      },
      // Pin files for offline use, with remote-first reads + cache fallback.
      enableOfflineCache: true,
      // Durable transfer queue: uploads/downloads resume after an app restart
      // and self-retry with backoff. Pending transfers resume on construction.
      enableAutoResume: true,
    ),
  );

  runApp(_Application(storage: storage));
}

class _Application extends StatefulWidget {
  final WincheStorage storage;

  const _Application({required this.storage});

  @override
  State<_Application> createState() => _ApplicationState();
}

class _ApplicationState extends State<_Application> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: _HomePage(storage: widget.storage));
  }
}

class _HomePage extends StatefulWidget {
  final WincheStorage storage;

  const _HomePage({required this.storage});

  @override
  State<_HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<_HomePage> {
  WincheStorage get storage => widget.storage;
  ChildReference get root => storage.child("userFiles/user-123");

  UploadTask? currentUploadTask;
  DownloadTask? currentDownloadTask;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Winche Storage Example')),
      body: Column(
        children: [
          if (currentUploadTask != null)
            Container(
              padding: const EdgeInsets.all(8),
              decoration: BoxDecoration(
                color: Colors.blue,
                borderRadius: BorderRadius.circular(8),
              ),
              child: StreamBuilder(
                stream: currentUploadTask!.stateStream,
                builder: (context, snapshot) {
                  if (snapshot.connectionState == ConnectionState.waiting) {
                    return Row(
                      children: [
                        const Text('Upload starting...'),
                        Spacer(),
                        const CircularProgressIndicator(),
                      ],
                    );
                  }

                  final state = snapshot.data;
                  final uploadStatus = state?.status;
                  final progress = state?.progress ?? 0.0;

                  return Row(
                    children: [
                      Column(
                        mainAxisAlignment: MainAxisAlignment.start,
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          Text('Upload Status: $uploadStatus'),
                          Text(
                            'Progress: ${(progress * 100).toStringAsFixed(2)}%',
                          ),
                        ],
                      ),
                      Spacer(),
                      if (uploadStatus == UploadTaskStatus.running)
                        IconButton(
                          onPressed: () {
                            currentUploadTask!.pause();
                          },
                          icon: const Icon(Icons.pause),
                        )
                      else if (uploadStatus == UploadTaskStatus.paused)
                        IconButton(
                          onPressed: () {
                            currentUploadTask!.resume();
                          },
                          icon: const Icon(Icons.play_arrow),
                        ),
                      IconButton(
                        onPressed: () {
                          currentUploadTask!.cancel();
                        },
                        icon: const Icon(Icons.close),
                      ),
                    ],
                  );
                },
              ),
            ),
          if (currentDownloadTask != null)
            Container(
              padding: const EdgeInsets.all(8),
              decoration: BoxDecoration(
                color: Colors.green,
                borderRadius: BorderRadius.circular(8),
              ),
              child: StreamBuilder(
                stream: currentDownloadTask!.stateStream,
                builder: (context, snapshot) {
                  if (snapshot.connectionState == ConnectionState.waiting) {
                    return Row(
                      children: [
                        const Text('Download starting...'),
                        Spacer(),
                        const CircularProgressIndicator(),
                      ],
                    );
                  }

                  final state = snapshot.data;
                  final downloadStatus = state?.status;
                  final progress = state?.progress ?? 0.0;

                  return Row(
                    children: [
                      Column(
                        mainAxisAlignment: MainAxisAlignment.start,
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          Text('Download Status: $downloadStatus'),
                          Text(
                            'Progress: ${(progress * 100).toStringAsFixed(2)}%',
                          ),
                        ],
                      ),
                      Spacer(),
                      if (downloadStatus == DownloadTaskStatus.running)
                        IconButton(
                          onPressed: () {
                            currentDownloadTask!.pause();
                          },
                          icon: const Icon(Icons.pause),
                        )
                      else if (downloadStatus == DownloadTaskStatus.paused)
                        IconButton(
                          onPressed: () {
                            currentDownloadTask!.resume();
                          },
                          icon: const Icon(Icons.play_arrow),
                        ),
                      IconButton(
                        onPressed: () {
                          currentDownloadTask!.cancel();
                        },
                        icon: const Icon(Icons.close),
                      ),
                    ],
                  );
                },
              ),
            ),
          Expanded(
            child: FutureBuilder(
              future: storage.child("userFiles/user-123").list(),
              builder: (context, snapshot) {
                if (snapshot.connectionState == ConnectionState.waiting) {
                  return const Center(child: CircularProgressIndicator());
                }
                if (snapshot.hasError) {
                  return Center(
                    child: Text('Error loading files: ${snapshot.error}'),
                  );
                }
                final files = snapshot.data ?? [];
                if (files.isEmpty) {
                  return const Center(child: Text('No files found'));
                }
                return ListView.separated(
                  itemCount: files.length,
                  separatorBuilder: (context, index) => const Divider(),
                  itemBuilder: (context, index) {
                    final file = files[index];
                    final data = file.data!;
                    return ListTile(
                      // list() reflects offline state: `isCached` is true once the
                      // file's content is downloaded locally (`localPath`).
                      leading: data.isCached
                          ? const Icon(Icons.offline_pin, color: Colors.green)
                          : const Icon(Icons.cloud_outlined),
                      title: Text(file.reference.path),
                      subtitle: Text(
                        "Size: ${data.sizeBytes} bytes, MimeType: ${data.mimeType}, "
                        "Offline: ${data.isCached}${data.localPath != null ? ' (${data.localPath})' : ''}",
                      ),
                      trailing: Row(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          // Pin for offline use: downloads into the managed
                          // id-keyed cache and keeps it available offline.
                          IconButton(
                            tooltip: 'Make available offline',
                            onPressed: () async {
                              // Wait for the download to finish, then refresh the
                              // list so the offline badge (isCached) updates.
                              await file.reference.makeAvailableOffline();
                              if (!context.mounted) return;
                              setState(() {});
                              ScaffoldMessenger.of(context).showSnackBar(
                                SnackBar(
                                  content:
                                      Text('Pinned offline: ${file.reference.path}'),
                                ),
                              );
                            },
                            icon: const Icon(Icons.cloud_download_outlined),
                          ),
                          IconButton(
                            onPressed: () async {
                              // download() now takes an explicit destination.
                              final dir =
                                  await getApplicationDocumentsDirectory();
                              final saveTo = p.join(
                                dir.path,
                                'winche_downloads',
                                file.reference.name,
                              );
                              setState(() {
                                currentDownloadTask =
                                    file.reference.download(saveTo);
                              });
                              await currentDownloadTask!.whenDone;
                              setState(() {
                                currentDownloadTask = null;
                              });
                              if (!context.mounted) return;
                              ScaffoldMessenger.of(context).showSnackBar(
                                SnackBar(
                                  content: Text(
                                    'Download complete: ${file.reference.path}',
                                  ),
                                ),
                              );
                            },
                            icon: const Icon(Icons.download),
                          ),
                          IconButton(
                            onPressed: () async {
                              await file.reference.delete();
                              setState(() {});
                            },
                            icon: const Icon(Icons.delete),
                          ),
                        ],
                      ),
                    );
                  },
                );
              },
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          final result = await FilePicker.pickFiles(
            type: FileType.any,
            allowMultiple: false,
            withData: true,
          );
          final path = result?.files.first.path;

          if (!context.mounted) return;

          if (path == null) {
            ScaffoldMessenger.of(
              context,
            ).showSnackBar(const SnackBar(content: Text('No file selected')));
            return;
          }

          final file = root.child(
            "test-${DateTime.now().millisecondsSinceEpoch}",
          );
          setState(() {
            currentUploadTask = file.uploadBytes(
              result!.files.first.bytes!,
              "application/octet-stream",
              metadata: {"description": "Test file upload"},
            );
          });

          final record = await currentUploadTask!.whenDone;
          setState(() {
            currentUploadTask = null;
          });
          if (!context.mounted) return;
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(
              content: Text('Upload complete: ${record?.reference.path}'),
            ),
          );
        },
        child: const Icon(Icons.upload),
      ),
    );
  }
}
0
likes
85
points
17
downloads

Documentation

API reference

Publisher

verified publisherwinchetechnologies.co.uk

Weekly Downloads

Dart SDK for the WincheStorage backend - a reference-based API with resumable, multipart-aware upload and download tasks for any storage backend.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

dio, mime, path, sembast, sembast_web

More

Packages that depend on winche_storage