From dc3916281897f82d44cc4b5f62705e0bd2fa4d0e Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Sat, 20 Jul 2019 18:24:09 +0900 Subject: [PATCH] poc: sticky headers --- lib/common/outlined_text.dart | 36 +++++++++++ lib/image_fetcher.dart | 4 +- lib/main.dart | 72 ++++++---------------- lib/{ => model}/mime_types.dart | 0 lib/thumbnail.dart | 2 +- lib/thumbnail_collection.dart | 104 ++++++++++++++++++++++++++++++++ pubspec.lock | 17 +++++- pubspec.yaml | 3 + 8 files changed, 181 insertions(+), 57 deletions(-) create mode 100644 lib/common/outlined_text.dart rename lib/{ => model}/mime_types.dart (100%) create mode 100644 lib/thumbnail_collection.dart diff --git a/lib/common/outlined_text.dart b/lib/common/outlined_text.dart new file mode 100644 index 000000000..7bd9e3eb3 --- /dev/null +++ b/lib/common/outlined_text.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; + +class OutlinedText extends StatelessWidget { + final String data; + final TextStyle style; + final double outlineWidth; + final Color outlineColor; + + OutlinedText( + this.data, { + this.style, + @required this.outlineWidth, + @required this.outlineColor, + }); + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Text( + data, + style: style.copyWith( + foreground: Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = outlineWidth + ..color = outlineColor, + ), + ), + Text( + data, + style: style, + ), + ], + ); + } +} diff --git a/lib/image_fetcher.dart b/lib/image_fetcher.dart index 484f61a9d..f6aac55c5 100644 --- a/lib/image_fetcher.dart +++ b/lib/image_fetcher.dart @@ -6,10 +6,10 @@ import 'package:flutter/services.dart'; class ImageFetcher { static const platform = const MethodChannel('deckers.thibault.aves/mediastore'); - static Future getImageEntries() async { + static Future> getImageEntries() async { try { final result = await platform.invokeMethod('getImageEntries'); - return result as List; + return (result as List).cast(); } on PlatformException catch (e) { debugPrint('failed with exception=${e.message}'); } diff --git a/lib/main.dart b/lib/main.dart index c7aeb8413..c61ca0a9b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,5 @@ import 'package:aves/image_fetcher.dart'; -import 'package:aves/thumbnail.dart'; -import 'package:draggable_scrollbar/draggable_scrollbar.dart'; +import 'package:aves/thumbnail_collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -12,84 +11,51 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - title: 'Flutter Demo', + title: 'Aves', theme: ThemeData( brightness: Brightness.dark, primarySwatch: Colors.blue, ), - home: MyHomePage(title: 'Flutter Demo Home Page'), + home: HomePage(title: 'Home'), ); } } -class MyHomePage extends StatefulWidget { - MyHomePage({Key key, this.title}) : super(key: key); +class HomePage extends StatefulWidget { + HomePage({Key key, this.title}) : super(key: key); final String title; @override - _MyHomePageState createState() => _MyHomePageState(); + _HomePageState createState() => _HomePageState(); } -class _MyHomePageState extends State { - Future imageLoader; - ScrollController scrollController = ScrollController(); +class _HomePageState extends State { + List imageEntryList; @override void initState() { super.initState(); imageCache.maximumSizeBytes = 100 * 1024 * 1024; + getImageEntries(); + } + + getImageEntries() async { + imageEntryList = await ImageFetcher.getImageEntries(); + setState(() {}); } @override Widget build(BuildContext context) { - if (imageLoader == null) { - imageLoader = ImageFetcher.getImageEntries(); - } - var columnCount = 4; - var extent = MediaQuery.of(context).size.width / columnCount; return Scaffold( appBar: AppBar( title: Text(widget.title), ), - body: FutureBuilder( - future: imageLoader, - builder: (futureContext, AsyncSnapshot snapshot) { - switch (snapshot.connectionState) { - case ConnectionState.none: - return Text('None'); - case ConnectionState.active: - case ConnectionState.waiting: - return Text('Awaiting result...'); - case ConnectionState.done: - if (snapshot.hasError) return Text('Error: ${snapshot.error}'); - var imageEntryList = snapshot.data; - return Container( - padding: EdgeInsets.symmetric(vertical: 0.0), - child: DraggableScrollbar.arrows( - labelTextBuilder: (double offset) => Text( - "${offset ~/ 1}", - style: TextStyle(color: Colors.blueGrey), - ), - controller: scrollController, - child: GridView.builder( - controller: scrollController, - gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: extent, - ), - itemBuilder: (gridContext, index) { - return Thumbnail( - entry: imageEntryList[index] as Map, - extent: extent, - ); - }, - itemCount: imageEntryList.length, - ), - ), - ); - } - return null; - }), + body: imageEntryList == null + ? Center( + child: CircularProgressIndicator(), + ) + : ThumbnailCollection(imageEntryList), ); } } diff --git a/lib/mime_types.dart b/lib/model/mime_types.dart similarity index 100% rename from lib/mime_types.dart rename to lib/model/mime_types.dart diff --git a/lib/thumbnail.dart b/lib/thumbnail.dart index f3810faa9..be0bd4d91 100644 --- a/lib/thumbnail.dart +++ b/lib/thumbnail.dart @@ -3,7 +3,7 @@ import 'dart:typed_data'; import 'package:aves/image_fetcher.dart'; import 'package:aves/image_fullscreen_page.dart'; -import 'package:aves/mime_types.dart'; +import 'package:aves/model/mime_types.dart'; import 'package:flutter/material.dart'; import 'package:transparent_image/transparent_image.dart'; diff --git a/lib/thumbnail_collection.dart b/lib/thumbnail_collection.dart new file mode 100644 index 000000000..ca7c5262e --- /dev/null +++ b/lib/thumbnail_collection.dart @@ -0,0 +1,104 @@ +import 'package:aves/common/outlined_text.dart'; +import 'package:aves/thumbnail.dart'; +import "package:collection/collection.dart"; +import 'package:draggable_scrollbar/draggable_scrollbar.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_sticky_header/flutter_sticky_header.dart'; +import 'package:intl/intl.dart'; + +class ThumbnailCollection extends StatelessWidget { + final Map> sections; + final ScrollController scrollController = ScrollController(); + + ThumbnailCollection(List entries) : sections = groupBy(entries, getDayTaken); + + static DateTime getBestDate(Map entry) { + var dateTakenMillis = entry['sourceDateTakenMillis'] as int; + if (dateTakenMillis != null && dateTakenMillis > 0) return DateTime.fromMillisecondsSinceEpoch(dateTakenMillis); + + var dateModifiedSecs = entry['dateModifiedSecs'] as int; + if (dateModifiedSecs != null && dateModifiedSecs > 0) return DateTime.fromMillisecondsSinceEpoch(dateModifiedSecs * 1000); + + return null; + } + + static DateFormat sectionDateFormat = DateFormat('yyyy/MM/dd'); + + static String getDayTaken(Map entry) { + var date = getBestDate(entry); + return date == null ? 'Unknown' : sectionDateFormat.format(date); + } + + @override + Widget build(BuildContext context) { + var columnCount = 4; + var extent = MediaQuery.of(context).size.width / columnCount; + + return CustomScrollView( + slivers: sections.keys + .map((sectionKey) => SliverStickyHeader( + header: SectionHeader(sectionKey), + sliver: SliverGrid( + delegate: SliverChildBuilderDelegate( + (context, index) { + var entries = sections[sectionKey]; + if (index >= entries.length) return null; + return Thumbnail( + entry: entries[index], + extent: extent, + ); + }, + childCount: sections[sectionKey].length, + ), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: columnCount, + ), + ), + )) + .toList(), + ); + +// return DraggableScrollbar.arrows( +// labelTextBuilder: (double offset) => Text( +// "${offset ~/ 1}", +// style: TextStyle(color: Colors.blueGrey), +// ), +// controller: scrollController, +// child: GridView.builder( +// controller: scrollController, +// gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( +// maxCrossAxisExtent: extent, +// ), +// itemBuilder: (gridContext, index) { +// return Thumbnail( +// entry: imageEntryList[index], +// extent: extent, +// ); +// }, +// itemCount: imageEntryList.length, +// ), +// ); + } +} + +class SectionHeader extends StatelessWidget { + final String primaryText; + + SectionHeader(this.primaryText); + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.all(16), + child: OutlinedText( + primaryText, + style: TextStyle( + color: Colors.white, + fontSize: 20, + ), + outlineColor: Colors.black87, + outlineWidth: 2, + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index a4e617555..c838be640 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -23,7 +23,7 @@ packages: source: hosted version: "1.1.2" collection: - dependency: transitive + dependency: "direct main" description: name: collection url: "https://pub.dartlang.org" @@ -41,11 +41,25 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_sticky_header: + dependency: "direct main" + description: + name: flutter_sticky_header + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.0" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + intl: + dependency: "direct main" + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.15.8" matcher: dependency: transitive description: @@ -151,3 +165,4 @@ packages: version: "2.0.8" sdks: dart: ">=2.2.2 <3.0.0" + flutter: ">=1.5.9-pre.94 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 487590f28..63ee668eb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,7 +19,10 @@ environment: dependencies: flutter: sdk: flutter + collection: draggable_scrollbar: + flutter_sticky_header: + intl: transparent_image: dev_dependencies: