diff --git a/lib/utils/color_utils.dart b/lib/utils/color_utils.dart index ed4ca50bc..8a22b11fe 100644 --- a/lib/utils/color_utils.dart +++ b/lib/utils/color_utils.dart @@ -1,7 +1,14 @@ import 'package:flutter/material.dart'; +final Map _stringColors = {}; + Color stringToColor(String string, {double saturation = .8, double lightness = .6}) { - final hash = string.codeUnits.fold(0, (prev, el) => prev = el + ((prev << 5) - prev)); - final hue = (hash % 360).toDouble(); - return HSLColor.fromAHSL(1.0, hue, saturation, lightness).toColor(); + var color = _stringColors[string]; + if (color == null) { + final hash = string.codeUnits.fold(0, (prev, el) => prev = el + ((prev << 5) - prev)); + final hue = (hash % 360).toDouble(); + color = HSLColor.fromAHSL(1.0, hue, saturation, lightness).toColor(); + _stringColors[string] = color; + } + return color; } diff --git a/lib/widgets/fullscreen/info/location_section.dart b/lib/widgets/fullscreen/info/location_section.dart index 76791f172..98176b5b7 100644 --- a/lib/widgets/fullscreen/info/location_section.dart +++ b/lib/widgets/fullscreen/info/location_section.dart @@ -6,6 +6,7 @@ import 'package:aves/utils/android_app_service.dart'; import 'package:aves/utils/geo_utils.dart'; import 'package:aves/widgets/common/aves_filter_chip.dart'; import 'package:aves/widgets/fullscreen/info/info_page.dart'; +import 'package:aves/widgets/fullscreen/info/map_initializer.dart'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:outline_material_icons/outline_material_icons.dart'; @@ -168,31 +169,34 @@ class ImageMapState extends State with AutomaticKeepAliveClientMixin { return Row( children: [ Expanded( - child: SizedBox( - height: 200, - child: ClipRRect( - borderRadius: const BorderRadius.all( - Radius.circular(16), - ), - child: GoogleMap( - initialCameraPosition: CameraPosition( - target: widget.latLng, - zoom: widget.initialZoom, + child: ClipRRect( + borderRadius: const BorderRadius.all( + Radius.circular(16), + ), + child: Container( + color: Colors.white70, + height: 200, + child: GoogleMapInitializer( + builder: (context) => GoogleMap( + initialCameraPosition: CameraPosition( + target: widget.latLng, + zoom: widget.initialZoom, + ), + onMapCreated: (controller) => setState(() => _controller = controller), + rotateGesturesEnabled: false, + scrollGesturesEnabled: false, + zoomGesturesEnabled: false, + tiltGesturesEnabled: false, + myLocationEnabled: false, + myLocationButtonEnabled: false, + markers: { + Marker( + markerId: MarkerId(widget.markerId), + icon: BitmapDescriptor.defaultMarkerWithHue(accentHue), + position: widget.latLng, + ) + }, ), - onMapCreated: (controller) => setState(() => _controller = controller), - rotateGesturesEnabled: false, - scrollGesturesEnabled: false, - zoomGesturesEnabled: false, - tiltGesturesEnabled: false, - myLocationEnabled: false, - myLocationButtonEnabled: false, - markers: { - Marker( - markerId: MarkerId(widget.markerId), - icon: BitmapDescriptor.defaultMarkerWithHue(accentHue), - position: widget.latLng, - ) - }, ), ), ), diff --git a/lib/widgets/fullscreen/info/map_initializer.dart b/lib/widgets/fullscreen/info/map_initializer.dart new file mode 100644 index 000000000..afbda561b --- /dev/null +++ b/lib/widgets/fullscreen/info/map_initializer.dart @@ -0,0 +1,65 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:meta/meta.dart'; + +// workaround to Google Maps initialization blocking the dart thread +// cf https://github.com/flutter/flutter/issues/28493 +// it loads first Google Maps in an isolate, and then build the desired map + +class GoogleMapInitializer extends StatefulWidget { + final WidgetBuilder builder, errorBuilder, placeholderBuilder; + + const GoogleMapInitializer({ + @required this.builder, + this.errorBuilder, + this.placeholderBuilder, + }); + + @override + _GoogleMapInitializerState createState() => _GoogleMapInitializerState(); +} + +class _GoogleMapInitializerState extends State { + Future initializer; + + @override + void initState() { + super.initState(); + initializer = compute(_preload, null); + } + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: initializer, + builder: (context, snapshot) { + if (snapshot.hasError) { + return widget.errorBuilder?.call(context) ?? const Icon(Icons.error_outline); + } else if (snapshot.connectionState == ConnectionState.done) { + return widget.builder(context); + } else { + return widget.placeholderBuilder?.call(context) ?? const SizedBox.shrink(); + } + }); + } +} + +Future _preload(_) async { + final mapCreatedCompleter = Completer(); + GoogleMap( + compassEnabled: false, + mapToolbarEnabled: false, + rotateGesturesEnabled: false, + scrollGesturesEnabled: false, + zoomGesturesEnabled: false, + tiltGesturesEnabled: false, + buildingsEnabled: false, + initialCameraPosition: const CameraPosition(target: LatLng(0, 0), zoom: 20), + onMapCreated: (controller) => mapCreatedCompleter.complete(), + ); + return mapCreatedCompleter.future; +} diff --git a/lib/widgets/fullscreen/info/metadata_section.dart b/lib/widgets/fullscreen/info/metadata_section.dart index 60b5635e3..a4d0f9768 100644 --- a/lib/widgets/fullscreen/info/metadata_section.dart +++ b/lib/widgets/fullscreen/info/metadata_section.dart @@ -6,6 +6,7 @@ import 'package:aves/model/metadata_service.dart'; import 'package:aves/utils/color_utils.dart'; import 'package:aves/widgets/common/fx/highlight_decoration.dart'; import 'package:aves/widgets/fullscreen/info/info_page.dart'; +import 'package:expansion_tile_card/expansion_tile_card.dart'; import 'package:flutter/material.dart'; import 'package:outline_material_icons/outline_material_icons.dart'; @@ -63,37 +64,36 @@ class _MetadataSectionSliverState extends State with Auto Widget build(BuildContext context) { super.build(context); - final directoriesWithoutTitle = _metadata.where((dir) => dir.name.isEmpty); - final directoriesWithTitle = _metadata.where((dir) => dir.name.isNotEmpty); + if (_metadata.isEmpty) return const SliverToBoxAdapter(child: SizedBox.shrink()); + + final directoriesWithoutTitle = _metadata.where((dir) => dir.name.isEmpty).toList(); + final directoriesWithTitle = _metadata.where((dir) => dir.name.isNotEmpty).toList(); + final untitledDirectoryCount = directoriesWithoutTitle.length; return SliverList( - delegate: SliverChildListDelegate( - [ - if (_metadata.isNotEmpty) const SectionRow(OMIcons.info), - ...directoriesWithoutTitle.map((dir) => InfoRowGroup(dir.tags)), - Theme( - data: Theme.of(context).copyWith(cardColor: Colors.grey[900]), - child: ExpansionPanelList.radio( - key: ValueKey(widget.entry.uri), - expandedHeaderPadding: EdgeInsets.zero, - children: directoriesWithTitle.map((dir) { - return ExpansionPanelRadio( - value: dir.name, - canTapOnHeader: true, - headerBuilder: (BuildContext context, bool isExpanded) { - return ListTile( - title: _DirectoryTitle(dir.name), - ); - }, - body: Container( - alignment: Alignment.topLeft, - padding: const EdgeInsets.all(8), - child: InfoRowGroup(dir.tags), - ), - ); - }).toList(), - ), - ) - ], + delegate: SliverChildBuilderDelegate( + (context, index) { + if (index == 0) { + return const SectionRow(OMIcons.info); + } + if (index < untitledDirectoryCount + 1) { + final dir = directoriesWithoutTitle[index - 1]; + return InfoRowGroup(dir.tags); + } + final dir = directoriesWithTitle[index - 1 - untitledDirectoryCount]; + return ExpansionTileCard( + title: _DirectoryTitle(dir.name), + children: [ + const Divider(thickness: 1.0, height: 1.0), + Container( + alignment: Alignment.topLeft, + padding: const EdgeInsets.all(8), + child: InfoRowGroup(dir.tags), + ), + ], + baseColor: Colors.grey[900], + ); + }, + childCount: 1 + _metadata.length, ), ); } @@ -143,6 +143,7 @@ class _DirectoryTitle extends StatelessWidget { child: Text( name, style: const TextStyle( + color: Colors.white, shadows: [ Shadow( color: Colors.black, @@ -153,6 +154,9 @@ class _DirectoryTitle extends StatelessWidget { fontSize: 18, fontFamily: 'Concourse Caps', ), + softWrap: false, + overflow: TextOverflow.fade, + maxLines: 1, ), ), ); diff --git a/pubspec.lock b/pubspec.lock index 251cd285c..e67264f03 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -101,6 +101,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.1" + expansion_tile_card: + dependency: "direct main" + description: + name: expansion_tile_card + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" flushbar: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 02d3518ef..204324c85 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,6 +23,7 @@ dependencies: git: url: git://github.com/deckerst/flutter-draggable-scrollbar.git event_bus: + expansion_tile_card: flushbar: # flushbar-1.9.1 cannot be built with Flutter 1.15.17 git: