poc: sticky headers
This commit is contained in:
parent
f43e861d05
commit
dc39162818
8 changed files with 181 additions and 57 deletions
36
lib/common/outlined_text.dart
Normal file
36
lib/common/outlined_text.dart
Normal file
|
@ -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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,10 +6,10 @@ import 'package:flutter/services.dart';
|
||||||
class ImageFetcher {
|
class ImageFetcher {
|
||||||
static const platform = const MethodChannel('deckers.thibault.aves/mediastore');
|
static const platform = const MethodChannel('deckers.thibault.aves/mediastore');
|
||||||
|
|
||||||
static Future<List> getImageEntries() async {
|
static Future<List<Map>> getImageEntries() async {
|
||||||
try {
|
try {
|
||||||
final result = await platform.invokeMethod('getImageEntries');
|
final result = await platform.invokeMethod('getImageEntries');
|
||||||
return result as List;
|
return (result as List).cast<Map>();
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
debugPrint('failed with exception=${e.message}');
|
debugPrint('failed with exception=${e.message}');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import 'package:aves/image_fetcher.dart';
|
import 'package:aves/image_fetcher.dart';
|
||||||
import 'package:aves/thumbnail.dart';
|
import 'package:aves/thumbnail_collection.dart';
|
||||||
import 'package:draggable_scrollbar/draggable_scrollbar.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
@ -12,84 +11,51 @@ class MyApp extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'Flutter Demo',
|
title: 'Aves',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
primarySwatch: Colors.blue,
|
primarySwatch: Colors.blue,
|
||||||
),
|
),
|
||||||
home: MyHomePage(title: 'Flutter Demo Home Page'),
|
home: HomePage(title: 'Home'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyHomePage extends StatefulWidget {
|
class HomePage extends StatefulWidget {
|
||||||
MyHomePage({Key key, this.title}) : super(key: key);
|
HomePage({Key key, this.title}) : super(key: key);
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_MyHomePageState createState() => _MyHomePageState();
|
_HomePageState createState() => _HomePageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MyHomePageState extends State<MyHomePage> {
|
class _HomePageState extends State<HomePage> {
|
||||||
Future<List> imageLoader;
|
List<Map> imageEntryList;
|
||||||
ScrollController scrollController = ScrollController();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
imageCache.maximumSizeBytes = 100 * 1024 * 1024;
|
imageCache.maximumSizeBytes = 100 * 1024 * 1024;
|
||||||
|
getImageEntries();
|
||||||
|
}
|
||||||
|
|
||||||
|
getImageEntries() async {
|
||||||
|
imageEntryList = await ImageFetcher.getImageEntries();
|
||||||
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (imageLoader == null) {
|
|
||||||
imageLoader = ImageFetcher.getImageEntries();
|
|
||||||
}
|
|
||||||
var columnCount = 4;
|
|
||||||
var extent = MediaQuery.of(context).size.width / columnCount;
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(widget.title),
|
title: Text(widget.title),
|
||||||
),
|
),
|
||||||
body: FutureBuilder(
|
body: imageEntryList == null
|
||||||
future: imageLoader,
|
? Center(
|
||||||
builder: (futureContext, AsyncSnapshot<List> snapshot) {
|
child: CircularProgressIndicator(),
|
||||||
switch (snapshot.connectionState) {
|
)
|
||||||
case ConnectionState.none:
|
: ThumbnailCollection(imageEntryList),
|
||||||
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;
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:aves/image_fetcher.dart';
|
import 'package:aves/image_fetcher.dart';
|
||||||
import 'package:aves/image_fullscreen_page.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:flutter/material.dart';
|
||||||
import 'package:transparent_image/transparent_image.dart';
|
import 'package:transparent_image/transparent_image.dart';
|
||||||
|
|
||||||
|
|
104
lib/thumbnail_collection.dart
Normal file
104
lib/thumbnail_collection.dart
Normal file
|
@ -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<String, List<Map>> sections;
|
||||||
|
final ScrollController scrollController = ScrollController();
|
||||||
|
|
||||||
|
ThumbnailCollection(List<Map> 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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
17
pubspec.lock
17
pubspec.lock
|
@ -23,7 +23,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.1.2"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: collection
|
name: collection
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
|
@ -41,11 +41,25 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
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:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
intl:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: intl
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.15.8"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -151,3 +165,4 @@ packages:
|
||||||
version: "2.0.8"
|
version: "2.0.8"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.2.2 <3.0.0"
|
dart: ">=2.2.2 <3.0.0"
|
||||||
|
flutter: ">=1.5.9-pre.94 <2.0.0"
|
||||||
|
|
|
@ -19,7 +19,10 @@ environment:
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
collection:
|
||||||
draggable_scrollbar:
|
draggable_scrollbar:
|
||||||
|
flutter_sticky_header:
|
||||||
|
intl:
|
||||||
transparent_image:
|
transparent_image:
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|
Loading…
Reference in a new issue