diff --git a/lib/utils/constants.dart b/lib/utils/constants.dart index 29bea99f4..a06442ca0 100644 --- a/lib/utils/constants.dart +++ b/lib/utils/constants.dart @@ -27,4 +27,189 @@ class Constants { static const svgBackground = Colors.white; static const svgColorFilter = ColorFilter.mode(svgBackground, BlendMode.dstOver); + + static const List packages = [ + Dependency( + name: 'Flutter', + license: 'BSD 3-Clause', + licenseUrl: 'https://github.com/flutter/flutter/blob/master/LICENSE', + sourceUrl: 'https://github.com/flutter/flutter', + ), + Dependency( + name: 'Charts', + license: 'Apache 2.0', + licenseUrl: 'https://github.com/google/charts/blob/master/LICENSE', + sourceUrl: 'https://github.com/google/charts', + ), + Dependency( + name: 'Collection', + license: 'BSD 3-Clause', + licenseUrl: 'https://github.com/dart-lang/collection/blob/master/LICENSE', + sourceUrl: 'https://github.com/dart-lang/collection', + ), + Dependency( + name: 'Draggable Scrollbar', + license: 'MIT', + licenseUrl: 'https://github.com/fluttercommunity/flutter-draggable-scrollbar/blob/master/LICENSE', + sourceUrl: 'https://github.com/fluttercommunity/flutter-draggable-scrollbar', + ), + Dependency( + name: 'Event Bus', + license: 'MIT', + licenseUrl: 'https://github.com/marcojakob/dart-event-bus/blob/master/LICENSE', + sourceUrl: 'https://github.com/marcojakob/dart-event-bus', + ), + Dependency( + name: 'Expansion Tile Card', + license: 'BSD 3-Clause', + licenseUrl: 'https://github.com/Skylled/expansion_tile_card/blob/master/LICENSE', + sourceUrl: 'https://github.com/Skylled/expansion_tile_card', + ), + Dependency( + name: 'Flushbar', + license: 'Apache 2.0', + licenseUrl: 'https://github.com/AndreHaueisen/flushbar/blob/master/LICENSE', + sourceUrl: 'https://github.com/AndreHaueisen/flushbar', + ), + Dependency( + name: 'Flutter ijkplayer', + license: 'MIT', + licenseUrl: 'https://github.com/CaiJingLong/flutter_ijkplayer/blob/master/LICENSE', + sourceUrl: 'https://github.com/CaiJingLong/flutter_ijkplayer', + ), + Dependency( + name: 'Flutter Native Timezone', + license: 'Apache 2.0', + licenseUrl: 'https://github.com/pinkfish/flutter_native_timezone/blob/master/LICENSE', + sourceUrl: 'https://github.com/pinkfish/flutter_native_timezone', + ), + Dependency( + name: 'Flutter SVG', + license: 'MIT', + licenseUrl: 'https://github.com/dnfield/flutter_svg/blob/master/LICENSE', + sourceUrl: 'https://github.com/dnfield/flutter_svg', + ), + Dependency( + name: 'Geocoder', + license: 'MIT', + licenseUrl: 'https://github.com/aloisdeniel/flutter_geocoder/blob/master/LICENSE', + sourceUrl: 'https://github.com/aloisdeniel/flutter_geocoder', + ), + Dependency( + name: 'Google Maps for Flutter', + license: 'BSD 3-Clause', + licenseUrl: 'https://github.com/flutter/plugins/blob/master/packages/google_maps_flutter/google_maps_flutter/LICENSE', + sourceUrl: 'https://github.com/flutter/plugins/blob/master/packages/google_maps_flutter/google_maps_flutter', + ), + Dependency( + name: 'Intl', + license: 'BSD 3-Clause', + licenseUrl: 'https://github.com/dart-lang/intl/blob/master/LICENSE', + sourceUrl: 'https://github.com/dart-lang/intl', + ), + Dependency( + name: 'Outline Material Icons', + license: 'Apache 2.0', + licenseUrl: 'https://github.com/lucaslcode/outline_material_icons/blob/master/LICENSE', + sourceUrl: 'https://github.com/lucaslcode/outline_material_icons', + ), + Dependency( + name: 'Palette Generator', + license: 'BSD 3-Clause', + licenseUrl: 'https://github.com/flutter/packages/blob/master/packages/palette_generator/LICENSE', + sourceUrl: 'https://github.com/flutter/packages/tree/master/packages/palette_generator', + ), + Dependency( + name: 'PDF for Dart and Flutter', + license: 'Apache 2.0', + licenseUrl: 'https://github.com/DavBfr/dart_pdf/blob/master/LICENSE', + sourceUrl: 'https://github.com/DavBfr/dart_pdf', + ), + Dependency( + name: 'Pedantic', + license: 'BSD 3-Clause', + licenseUrl: 'https://github.com/dart-lang/pedantic/blob/master/LICENSE', + sourceUrl: 'https://github.com/dart-lang/pedantic', + ), + Dependency( + name: 'Percent Indicator', + license: 'BSD 2-Clause', + licenseUrl: 'https://github.com/diegoveloper/flutter_percent_indicator/blob/master/LICENSE', + sourceUrl: 'https://github.com/diegoveloper/flutter_percent_indicator/', + ), + Dependency( + name: 'Permission Handler', + license: 'MIT', + licenseUrl: 'https://github.com/Baseflow/flutter-permission-handler/blob/develop/permission_handler/LICENSE', + sourceUrl: 'https://github.com/Baseflow/flutter-permission-handler', + ), + Dependency( + name: 'Photo View', + license: 'MIT', + licenseUrl: 'https://github.com/renancaraujo/photo_view/blob/master/LICENSE', + sourceUrl: 'https://github.com/renancaraujo/photo_view', + ), + Dependency( + name: 'Printing', + license: 'Apache 2.0', + licenseUrl: 'https://github.com/DavBfr/dart_pdf/blob/master/LICENSE', + sourceUrl: 'https://github.com/DavBfr/dart_pdf', + ), + Dependency( + name: 'Provider', + license: 'MIT', + licenseUrl: 'https://github.com/rrousselGit/provider/blob/master/LICENSE', + sourceUrl: 'https://github.com/rrousselGit/provider', + ), + Dependency( + name: 'Screen', + license: 'MIT', + licenseUrl: 'https://github.com/clovisnicolas/flutter_screen/blob/master/LICENSE', + sourceUrl: 'https://github.com/clovisnicolas/flutter_screen', + ), + Dependency( + name: 'Shared Preferences', + license: 'BSD 3-Clause', + licenseUrl: 'https://github.com/flutter/plugins/blob/master/packages/shared_preferences/shared_preferences/LICENSE', + sourceUrl: 'https://github.com/flutter/plugins/tree/master/packages/shared_preferences/shared_preferences', + ), + Dependency( + name: 'sqflite', + license: 'MIT', + licenseUrl: 'https://github.com/tekartik/sqflite/blob/master/sqflite/LICENSE', + sourceUrl: 'https://github.com/tekartik/sqflite', + ), + Dependency( + name: 'Streams Channel', + license: 'Apache 2.0', + licenseUrl: 'https://github.com/loup-v/streams_channel/blob/master/LICENSE', + sourceUrl: 'https://github.com/loup-v/streams_channel', + ), + Dependency( + name: 'Tuple', + license: 'BSD 2-Clause', + licenseUrl: 'https://github.com/dart-lang/tuple/blob/master/LICENSE', + sourceUrl: 'https://github.com/dart-lang/tuple', + ), + Dependency( + name: 'UUID', + license: 'MIT', + licenseUrl: 'https://github.com/Daegalus/dart-uuid/blob/master/LICENSE', + sourceUrl: 'https://github.com/Daegalus/dart-uuid', + ), + ]; +} + +class Dependency { + final String name; + final String license; + final String sourceUrl; + final String licenseUrl; + + const Dependency({ + @required this.name, + @required this.license, + @required this.licenseUrl, + @required this.sourceUrl, + }); } diff --git a/lib/widgets/about/about_page.dart b/lib/widgets/about/about_page.dart new file mode 100644 index 000000000..44746d147 --- /dev/null +++ b/lib/widgets/about/about_page.dart @@ -0,0 +1,45 @@ +import 'package:aves/widgets/about/licenses.dart'; +import 'package:flutter/material.dart'; + +class AboutPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('About'), + ), + body: SafeArea( + child: CustomScrollView( + slivers: [ + SliverPadding( + padding: const EdgeInsets.only(top: 16), + sliver: SliverList( + delegate: SliverChildListDelegate( + [ + Center( + child: RichText( + text: TextSpan( + children: [ + const TextSpan(text: 'Made with ❤️ and '), + WidgetSpan( + child: FlutterLogo( + size: Theme.of(context).textTheme.bodyText2.fontSize * 1.25, + ), + ), + ], + ), + ), + ), + const SizedBox(height: 8), + const Divider(), + ], + ), + ), + ), + Licenses(), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/about/licenses.dart b/lib/widgets/about/licenses.dart new file mode 100644 index 000000000..a8b1482d1 --- /dev/null +++ b/lib/widgets/about/licenses.dart @@ -0,0 +1,169 @@ +import 'package:aves/utils/constants.dart'; +import 'package:aves/widgets/common/icons.dart'; +import 'package:aves/widgets/common/menu_row.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class Licenses extends StatefulWidget { + @override + _LicensesState createState() => _LicensesState(); +} + +class _LicensesState extends State { + LicenseSort _sort = LicenseSort.name; + List _packages; + + @override + void initState() { + super.initState(); + _packages = List.of(Constants.packages); + _sortPackages(); + } + + void _sortPackages() { + _packages.sort((a, b) { + switch (_sort) { + case LicenseSort.license: + final c = compareAsciiUpperCase(a.license, b.license); + return c != 0 ? c : compareAsciiUpperCase(a.name, b.name); + case LicenseSort.name: + default: + return compareAsciiUpperCase(a.name, b.name); + } + }); + } + + @override + Widget build(BuildContext context) { + return SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 8), + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + if (index-- == 0) { + return _buildHeader(); + } + return LicenseRow(_packages[index]); + }, + childCount: _packages.length + 1, + ), + ), + ); + } + + Widget _buildHeader() { + return Column( + children: [ + Padding( + padding: const EdgeInsetsDirectional.only(start: 8), + child: Row( + children: [ + Text( + 'Open-source licenses', + style: Theme.of(context).textTheme.headline6.copyWith(fontFamily: 'Concourse Caps'), + ), + const Spacer(), + PopupMenuButton( + itemBuilder: (context) => [ + PopupMenuItem( + value: LicenseSort.name, + child: MenuRow(text: 'Sort by name', checked: _sort == LicenseSort.name), + ), + PopupMenuItem( + value: LicenseSort.license, + child: MenuRow(text: 'Sort by license', checked: _sort == LicenseSort.license), + ), + ], + onSelected: (newSort) { + _sort = newSort; + _sortPackages(); + setState(() {}); + }, + tooltip: 'Sort', + icon: Icon(AIcons.sort), + ), + ], + ), + ), + const SizedBox(height: 8), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 8), + child: Text('The following sets forth attribution notices for third party software that may be contained in this application.'), + ), + ], + ); + } +} + +class LicenseRow extends StatelessWidget { + final Dependency package; + + const LicenseRow(this.package); + + static const borderRadius = BorderRadius.all(Radius.circular(8)); + + @override + Widget build(BuildContext context) { + final textTheme = Theme.of(context).textTheme; + final bodyTextStyle = textTheme.bodyText2; + final subColor = bodyTextStyle.color.withOpacity(.6); + + return Padding( + padding: const EdgeInsets.only(top: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InkWell( + borderRadius: borderRadius, + onTap: () => launch(package.sourceUrl), + child: Padding( + padding: const EdgeInsets.all(8), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + package.name, + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(width: 8), + Icon( + AIcons.openInNew, + size: bodyTextStyle.fontSize, + ) + ], + ), + ), + ), + Padding( + padding: const EdgeInsetsDirectional.only(start: 16), + child: InkWell( + borderRadius: borderRadius, + onTap: () => launch(package.licenseUrl), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + package.license, + style: TextStyle(color: subColor), + ), + const SizedBox(width: 8), + Icon( + AIcons.openInNew, + size: bodyTextStyle.fontSize, + color: subColor, + ) + ], + ), + ), + ), + ), + ], + ), + ); + } +} + +enum LicenseSort { license, name } diff --git a/lib/widgets/app_drawer.dart b/lib/widgets/app_drawer.dart index 4347da6df..a5d5d8915 100644 --- a/lib/widgets/app_drawer.dart +++ b/lib/widgets/app_drawer.dart @@ -11,7 +11,7 @@ import 'package:aves/model/filters/tag.dart'; import 'package:aves/model/mime_types.dart'; import 'package:aves/model/settings.dart'; import 'package:aves/utils/android_file_utils.dart'; -import 'package:aves/utils/color_utils.dart'; +import 'package:aves/widgets/about/about_page.dart'; import 'package:aves/widgets/album/collection_page.dart'; import 'package:aves/widgets/common/aves_logo.dart'; import 'package:aves/widgets/common/icons.dart'; @@ -87,6 +87,15 @@ class _AppDrawerState extends State { title: 'Favourites', filter: FavouriteFilter(), ); + final aboutEntry = SafeArea( + top: false, + bottom: false, + child: ListTile( + leading: const Icon(AIcons.info), + title: const Text('About'), + onTap: () => _goToAbout(context), + ), + ); final drawerItems = [ header, @@ -97,6 +106,7 @@ class _AppDrawerState extends State { _buildRegularAlbumSection(), _buildCountrySection(), _buildTagSection(), + aboutEntry, if (kDebugMode) ...[ const Divider(), SafeArea( @@ -150,35 +160,6 @@ class _AppDrawerState extends State { ); } - Widget _buildLocationEntry(LocationLevel level, String location) { - String title; - String flag; - if (level == LocationLevel.country) { - final split = location.split(';'); - String countryCode; - if (split.isNotEmpty) title = split[0]; - if (split.length > 1) countryCode = split[1]; - flag = LocationFilter.countryCodeToFlag(countryCode); - } else { - title = location; - } - return _FilteredCollectionNavTile( - source: source, - leading: flag != null - ? Text( - flag, - style: TextStyle(fontSize: IconTheme.of(context).size), - ) - : Icon( - AIcons.location, - color: stringToColor(title), - ), - title: title, - dense: true, - filter: LocationFilter(level, location), - ); - } - Widget _buildSpecialAlbumSection() { return StreamBuilder( stream: source.eventBus.on(), @@ -320,6 +301,16 @@ class _AppDrawerState extends State { ); } + void _goToAbout(BuildContext context) { + Navigator.pop(context); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AboutPage(), + ), + ); + } + void _goToDebug(BuildContext context) { Navigator.pop(context); Navigator.push( diff --git a/lib/widgets/common/icons.dart b/lib/widgets/common/icons.dart index c78f7dbb5..11d7d4465 100644 --- a/lib/widgets/common/icons.dart +++ b/lib/widgets/common/icons.dart @@ -40,6 +40,7 @@ class AIcons { static const IconData search = OMIcons.search; static const IconData select = OMIcons.selectAll; static const IconData share = OMIcons.share; + static const IconData sort = OMIcons.sort; static const IconData stats = OMIcons.pieChart; static const IconData zoomIn = OMIcons.add; static const IconData zoomOut = OMIcons.remove; diff --git a/pubspec.lock b/pubspec.lock index ee2ffb3db..b38e0049a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -303,6 +303,13 @@ packages: relative: true source: path version: "0.9.2" + platform_detect: + dependency: transitive + description: + name: platform_detect + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0" plugin_platform_interface: dependency: transitive description: @@ -324,6 +331,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.1.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.4" qr: dependency: transitive description: @@ -469,6 +483,34 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.6" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + url: "https://pub.dartlang.org" + source: hosted + version: "5.4.10" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1+7" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.7" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.1+6" utf: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 0f78836ef..91f3fc769 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,19 +47,19 @@ dependencies: path: ../expansion_tile_card # git: # url: git://github.com/deckerst/expansion_tile_card.git + flushbar: flutter_ijkplayer: path: ../flutter_ijkplayer -# git: -# url: git://github.com/deckerst/flutter_ijkplayer.git - flushbar: + # git: + # url: git://github.com/deckerst/flutter_ijkplayer.git flutter_native_timezone: flutter_svg: geocoder: google_maps_flutter: intl: outline_material_icons: - pdf: palette_generator: + pdf: pedantic: percent_indicator: permission_handler: @@ -76,6 +76,7 @@ dependencies: sqflite: streams_channel: tuple: + url_launcher: uuid: dev_dependencies: