Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui_storage): add StorageGridView widget #11206

Merged
merged 3 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/firebase_ui_storage/example/lib/src/apps.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:firebase_ui_storage_example/main.dart';
import 'package:firebase_ui_storage_example/src/storage_image_app.dart';
import 'package:flutter/material.dart';

import 'grid_view_app.dart';
import 'progress_bar_app.dart';
import 'upload_button_app.dart';
import 'list_view_app.dart';
Expand All @@ -19,6 +20,7 @@ const apps = <App>[
ProgressBarApp(),
StorageImageApp(),
StorageListViewApp(),
StorageGridViewApp(),
];

class AppList extends StatelessWidget {
Expand Down
44 changes: 44 additions & 0 deletions packages/firebase_ui_storage/example/lib/src/grid_view_app.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2023, the Chromium project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:convert';

import 'package:firebase_storage/firebase_storage.dart';
import 'package:firebase_ui_storage/firebase_ui_storage.dart';
import 'package:flutter/material.dart';

import 'apps.dart';

class StorageGridViewApp extends StatelessWidget implements App {
const StorageGridViewApp({super.key});

@override
String get name => 'StorageGridView';

@override
Widget build(BuildContext context) {
return StorageGridView(
ref: FirebaseStorage.instance.ref('list'),
itemBuilder: (context, ref) {
return Card(
child: Center(
child: FutureBuilder(
future: ref.getData(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
if (snapshot.hasData) {
return Text(utf8.decode(snapshot.data!));
}

return const CircularProgressIndicator();
},
),
),
);
},
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:convert';

import 'package:firebase_storage/firebase_storage.dart';
import 'package:firebase_ui_storage/firebase_ui_storage.dart';
import 'package:flutter/material.dart';
Expand All @@ -27,7 +29,7 @@ class StorageListViewApp extends StatelessWidget implements App {
return Text(snapshot.error.toString());
}
if (snapshot.hasData) {
return Text(snapshot.data.toString());
return Text(utf8.decode(snapshot.data!));
}

return const Text('Loading...');
Expand Down
1 change: 1 addition & 0 deletions packages/firebase_ui_storage/lib/firebase_ui_storage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ export 'src/widgets/progress_indicator.dart'
export 'src/widgets/image.dart' show StorageImage, LoadingStateVariant;
export 'src/paginated_loading_controller.dart';
export 'src/widgets/list_view.dart';
export 'src/widgets/grid_view.dart';
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';

import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/scheduler.dart';

/// A base class for loading states.
sealed class PaginatedLoadingState {
Expand Down Expand Up @@ -70,26 +73,33 @@ class PaginatedLoadingController extends ChangeNotifier {
? const InitialPageLoading()
: PageLoading(items: _items!);

notifyListeners();

return ref.list(_listOptions).then((value) {
_cursor = value;
(_items ??= []).addAll(value.items);

_state = PageLoadComplete(
pageItems: value.items,
items: _items!,
);
final completer = Completer();

SchedulerBinding.instance.addPostFrameCallback((_) async {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I always used that as a workaround. Do we have example of addPostCallback being used in official libs?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure why is this a workaround, to me it's a valid usage :)
flutter itself uses it internally
Screenshot 2023-07-13 at 14 21 58

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, not sure why I always thought it was a "HACK"

notifyListeners();
}).catchError((e) {
_state = PageLoadError(
error: e,
items: _items,
);

notifyListeners();
try {
final value = await ref.list(_listOptions);

_cursor = value;
(_items ??= []).addAll(value.items);

_state = PageLoadComplete(
pageItems: value.items,
items: _items!,
);
} catch (err) {
_state = PageLoadError(
error: err,
items: _items,
);
} finally {
completer.complete();
notifyListeners();
}
});

return completer.future;
}

bool shouldLoadNextPage(int itemIndex) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2023, the Chromium project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:firebase_ui_shared/firebase_ui_shared.dart';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

license header

import 'package:flutter/widgets.dart';

class DefaultLoadingIndicator extends StatelessWidget {
const DefaultLoadingIndicator({super.key});

@override
Widget build(BuildContext context) {
return const Center(
child: LoadingIndicator(
size: 32,
borderWidth: 2,
),
);
}
}
103 changes: 103 additions & 0 deletions packages/firebase_ui_storage/lib/src/widgets/grid_view.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright 2023, the Chromium project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:firebase_storage/firebase_storage.dart';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

license header

import 'package:firebase_ui_storage/firebase_ui_storage.dart';
import 'package:flutter/widgets.dart';

import 'default_loading_indicator.dart';

Widget _defaultLoadingBuilder(BuildContext context) {
return const DefaultLoadingIndicator();
}

class StorageGridView extends StatefulWidget {
/// The [Reference] to list items from.
/// If not provided, a [loadingController] must be created and passed.
final Reference? ref;

final PaginatedLoadingController? loadingController;

/// The number of items to load per page.
/// Defaults to 50.
final int pageSize;

/// A builder that is called for the first page load.
final Widget Function(BuildContext context) loadingBuilder;

/// A builder that is called when an error occurs during page loading.
final Widget Function(
BuildContext context,
Object? error,
PaginatedLoadingController controller,
)? errorBuilder;

/// A builder that is called for each item in the list.
final Widget Function(BuildContext context, Reference ref) itemBuilder;

/// See [SliverGridDelegate].
final SliverGridDelegate gridDelegate;

const StorageGridView({
super.key,
this.ref,
this.loadingController,
this.pageSize = 50,
this.loadingBuilder = _defaultLoadingBuilder,
this.errorBuilder,
this.gridDelegate = const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
),
required this.itemBuilder,
}) : assert(
ref != null || loadingController != null,
'ref or loadingController must be provided',
);

@override
State<StorageGridView> createState() => _StorageGridViewState();
}

class _StorageGridViewState extends State<StorageGridView> {
late PaginatedLoadingController ctrl = widget.loadingController ??
PaginatedLoadingController(
ref: widget.ref!,
pageSize: widget.pageSize,
);

Widget gridBuilder(BuildContext context, List<Reference> items) {
return GridView.builder(
gridDelegate: widget.gridDelegate,
itemCount: items.length,
itemBuilder: (context, index) {
if (ctrl.shouldLoadNextPage(index)) {
ctrl.load();
}

return widget.itemBuilder(context, items[index]);
},
);
}

@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: ctrl,
builder: (context, _) {
return switch (ctrl.state) {
InitialPageLoading() => widget.loadingBuilder(context),
PageLoadError(
error: final error,
items: final items,
) =>
widget.errorBuilder != null
? widget.errorBuilder!(context, error, ctrl)
: gridBuilder(context, items ?? []),
PageLoading(items: final items) => gridBuilder(context, items),
PageLoadComplete(items: final items) => gridBuilder(context, items),
};
},
);
}
}
9 changes: 2 additions & 7 deletions packages/firebase_ui_storage/lib/src/widgets/list_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,14 @@
// BSD-style license that can be found in the LICENSE file.

import 'package:firebase_storage/firebase_storage.dart';
import 'package:firebase_ui_shared/firebase_ui_shared.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/widgets.dart';

import '../paginated_loading_controller.dart';
import 'default_loading_indicator.dart';

Widget _defaultLoadingBuilder(BuildContext context) {
return const Center(
child: LoadingIndicator(
size: 32,
borderWidth: 2,
),
);
return const DefaultLoadingIndicator();
}

/// A [ListView.builder] that automatically handles paginated loading from
Expand Down