Expose object metadata from `Bucket.list`
diff --git a/pkgs/gcloud/CHANGELOG.md b/pkgs/gcloud/CHANGELOG.md
index 81ae6ff..bb6008c 100644
--- a/pkgs/gcloud/CHANGELOG.md
+++ b/pkgs/gcloud/CHANGELOG.md
@@ -1,5 +1,16 @@
 ## 0.8.16-wip
 
+ - **Breaking** `BucketEntry` is now `sealed` anyone implementing or subclassing
+   will experience breakage.
+ - Feature `BucketEntry` objects returns from `Bucket.list` are now instances
+   of:
+    * `BucketDirectoryEntry`, or,
+    * `BucketObjectEntry`, which implements `ObjectInfo` exposing metadata.
+
+   This means that anyone using `Bucket.list` to find objects, does not need
+   to use `Bucket.info` to fetch metadata for an object.
+ - Minimum Dart SDK constraint bumped to `^3.0.0`.
+
 ## 0.8.15
 
 - Update the pubspec repository field to reflect the repo move.
diff --git a/pkgs/gcloud/lib/src/storage_impl.dart b/pkgs/gcloud/lib/src/storage_impl.dart
index afaf8a7..1bd593d 100644
--- a/pkgs/gcloud/lib/src/storage_impl.dart
+++ b/pkgs/gcloud/lib/src/storage_impl.dart
@@ -349,9 +349,9 @@
       storage_api.Objects response)
       : items = [
           for (final item in response.prefixes ?? const <String>[])
-            BucketEntry._directory(item),
+            BucketDirectoryEntry._(item),
           for (final item in response.items ?? const <storage_api.Object>[])
-            BucketEntry._object(item.name!)
+            _BucketObjectEntry(item)
         ],
         _nextPageToken = response.nextPageToken;
 
@@ -430,6 +430,16 @@
   ObjectMetadata get metadata => _metadata;
 }
 
+class _BucketObjectEntry extends _ObjectInfoImpl implements BucketObjectEntry {
+  _BucketObjectEntry(storage_api.Object object) : super(object);
+
+  @override
+  bool get isDirectory => false;
+
+  @override
+  bool get isObject => true;
+}
+
 class _ObjectMetadata implements ObjectMetadata {
   final storage_api.Object _object;
   Acl? _cachedAcl;
diff --git a/pkgs/gcloud/lib/storage.dart b/pkgs/gcloud/lib/storage.dart
index 21e7705..a7ba329 100644
--- a/pkgs/gcloud/lib/storage.dart
+++ b/pkgs/gcloud/lib/storage.dart
@@ -668,23 +668,38 @@
 /// Listing operate like a directory listing, despite the object
 /// namespace being flat.
 ///
+/// Instances of [BucketEntry] are either instances of [BucketDirectoryEntry]
+/// or [BucketObjectEntry]. Casting to [BucketObjectEntry] will give access to
+/// object metadata.
+///
 /// See [Bucket.list] for information on how the hierarchical structure
 /// is determined.
-class BucketEntry {
+sealed class BucketEntry {
   /// Whether this is information on an object.
-  final bool isObject;
+  bool get isObject;
 
   /// Name of object or directory.
-  final String name;
-
-  BucketEntry._object(this.name) : isObject = true;
-
-  BucketEntry._directory(this.name) : isObject = false;
+  String get name;
 
   /// Whether this is a prefix.
   bool get isDirectory => !isObject;
 }
 
+/// Entry in [Bucket.list] representing a directory given the `delimiter`
+/// passed.
+class BucketDirectoryEntry extends BucketEntry {
+  @override
+  final String name;
+
+  BucketDirectoryEntry._(this.name);
+
+  @override
+  bool get isObject => false;
+}
+
+/// Entry in [Bucket.list] representing an object.
+abstract class BucketObjectEntry implements BucketEntry, ObjectInfo {}
+
 /// Access to operations on a specific cloud storage bucket.
 abstract class Bucket {
   /// Name of this bucket.
diff --git a/pkgs/gcloud/pubspec.yaml b/pkgs/gcloud/pubspec.yaml
index bc45012..775419e 100644
--- a/pkgs/gcloud/pubspec.yaml
+++ b/pkgs/gcloud/pubspec.yaml
@@ -9,7 +9,7 @@
  - gcp
 
 environment:
-  sdk: '>=2.19.0 <4.0.0'
+  sdk: '^3.0.0'
 
 dependencies:
   _discoveryapis_commons: ^1.0.0