From 04755efe57c0f67b76c8b0d4d4e89486b1b82382 Mon Sep 17 00:00:00 2001 From: Emanuel Braz Date: Fri, 12 Apr 2024 13:07:12 -0300 Subject: [PATCH] feat: add mobile layout --- lib/core/utils/layout_util.dart | 5 + lib/presentation/home/home_page.dart | 155 ++++-------------- .../home/home_view/desktop_view.dart | 122 ++++++++++++++ .../home/home_view/home_page_factory.dart | 15 ++ .../home/home_view/mobile_view.dart | 65 ++++++++ pubspec.lock | 68 +++++++- pubspec.yaml | 1 + 7 files changed, 302 insertions(+), 129 deletions(-) create mode 100644 lib/core/utils/layout_util.dart create mode 100644 lib/presentation/home/home_view/desktop_view.dart create mode 100644 lib/presentation/home/home_view/home_page_factory.dart create mode 100644 lib/presentation/home/home_view/mobile_view.dart diff --git a/lib/core/utils/layout_util.dart b/lib/core/utils/layout_util.dart new file mode 100644 index 0000000..8d500b2 --- /dev/null +++ b/lib/core/utils/layout_util.dart @@ -0,0 +1,5 @@ +import 'package:flutter/material.dart'; + +class LayoutUtil { + static bool isMobileLayout(BuildContext context) => MediaQuery.of(context).size.width < 600; +} diff --git a/lib/presentation/home/home_page.dart b/lib/presentation/home/home_page.dart index da8437d..b455741 100644 --- a/lib/presentation/home/home_page.dart +++ b/lib/presentation/home/home_page.dart @@ -1,9 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter_sp_social/data/social_qr_code.dart'; -import 'package:flutter_sp_social/presentation/home/event_store.dart'; -import 'package:pretty_qr_code/pretty_qr_code.dart'; +import 'package:flutter_sp_social/core/utils/layout_util.dart'; -const _defaultQrIcon = AssetImage('images/dash.png'); +import 'event_store.dart'; +import 'home_view/home_page_factory.dart'; class HomePage extends StatefulWidget { const HomePage({super.key}); @@ -15,14 +14,10 @@ class HomePage extends StatefulWidget { class _HomePageState extends State { final _store = EventStore(); - late final double _cardMaxWidth; - @override void initState() { super.initState(); - final params = Uri.base.queryParameters; - _cardMaxWidth = double.tryParse(params['width'] ?? '') ?? 300.0; WidgetsBinding.instance.addPostFrameCallback((_) { _store.loadEventData(); }); @@ -38,132 +33,38 @@ class _HomePageState extends State { } return Scaffold( - appBar: AppBar( - title: Text( - _store.value!.eventName, - style: Theme.of(context).textTheme.headlineSmall!.copyWith( - color: Theme.of(context).colorScheme.background, - ), - textAlign: TextAlign.center, - maxLines: 2, - overflow: TextOverflow.ellipsis, - semanticsLabel: _store.value!.eventName, - ), - actions: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: ElevatedButton( - onPressed: () { - Navigator.pushNamed(context, 'sorteio'); - }, - child: const Text( - 'Sorteio', - ), - ), - ), - ], - centerTitle: true, - backgroundColor: _store.value!.color != null - ? Color(int.parse(_store.value!.color!, radix: 16)) - : Theme.of(context).colorScheme.primary, - ), - backgroundColor: Theme.of(context).colorScheme.background, - body: Container( - alignment: Alignment.center, - padding: const EdgeInsets.all(16), - child: SingleChildScrollView( - padding: const EdgeInsets.only(bottom: 16), - child: Wrap( - spacing: 16, - runSpacing: 16, - children: _store.value!.socialQrCodes - .map((e) => _QRCode(socialQrCode: e, width: _cardMaxWidth)) - .toList()), - ), - ), - ); - }); - } -} - -class _QRCode extends StatelessWidget { - final SocialQrCode socialQrCode; - final double width; - - const _QRCode({required this.socialQrCode, required this.width}); - - @override - Widget build(BuildContext context) { - return MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: () { - showDialog( - context: context, - barrierColor: Colors.black.withOpacity(0.98), - builder: (_) => AlertDialog( - backgroundColor: Theme.of(context).colorScheme.surface, - content: SizedBox( - width: MediaQuery.sizeOf(context).height * 0.7, - child: AbsorbPointer(absorbing: true, child: this), - ), - )); - }, - child: Container( - constraints: BoxConstraints(maxWidth: width), - padding: const EdgeInsets.only(top: 16, left: 16, right: 16), - decoration: BoxDecoration( - color: Colors.black, - borderRadius: BorderRadius.circular(16), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.circular(8), - ), - child: PrettyQrView( - qrImage: QrImage( - QrCode.fromData( - data: socialQrCode.qrCode, - errorCorrectLevel: QrErrorCorrectLevel.H, - ), - ), - decoration: PrettyQrDecoration( - shape: PrettyQrSmoothSymbol( - color: socialQrCode.color != null - ? Color(int.parse(socialQrCode.color!, radix: 16)) - : Theme.of(context).colorScheme.primary, - roundFactor: 0, - ), - image: PrettyQrDecorationImage( - image: socialQrCode.icon != null - ? NetworkImage(socialQrCode.icon!) as ImageProvider - : _defaultQrIcon, - position: PrettyQrDecorationImagePosition.embedded, - ), - )), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - socialQrCode.title, + appBar: AppBar( + title: Text( + _store.value!.eventName, style: Theme.of(context).textTheme.headlineSmall!.copyWith( color: Theme.of(context).colorScheme.background, ), textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis, - semanticsLabel: socialQrCode.title, + semanticsLabel: _store.value!.eventName, ), + actions: [ + if (!LayoutUtil.isMobileLayout(context)) + Padding( + padding: const EdgeInsets.all(8.0), + child: ElevatedButton( + onPressed: () { + Navigator.pushNamed(context, 'sorteio'); + }, + child: const Text( + 'Sorteio', + ), + ), + ), + ], + centerTitle: true, + backgroundColor: _store.value!.color != null + ? Color(int.parse(_store.value!.color!, radix: 16)) + : Theme.of(context).colorScheme.primary, ), - ], - ), - ), - ), - ); + backgroundColor: Theme.of(context).colorScheme.background, + body: HomePageViewFactory.build(context, _store)); + }); } } diff --git a/lib/presentation/home/home_view/desktop_view.dart b/lib/presentation/home/home_view/desktop_view.dart new file mode 100644 index 0000000..43edfd5 --- /dev/null +++ b/lib/presentation/home/home_view/desktop_view.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_sp_social/data/social_qr_code.dart'; +import 'package:flutter_sp_social/presentation/home/event_store.dart'; +import 'package:pretty_qr_code/pretty_qr_code.dart'; + +class DesktopView extends StatefulWidget { + final EventStore store; + + const DesktopView({super.key, required this.store}); + + @override + State createState() => _DesktopViewState(); +} + +class _DesktopViewState extends State { + late final double _cardMaxWidth; + + @override + void initState() { + super.initState(); + final params = Uri.base.queryParameters; + _cardMaxWidth = double.tryParse(params['width'] ?? '') ?? 300.0; + } + + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment.center, + padding: const EdgeInsets.all(16), + child: SingleChildScrollView( + padding: const EdgeInsets.only(bottom: 16), + child: Wrap( + spacing: 16, + runSpacing: 16, + children: + widget.store.value!.socialQrCodes.map((e) => _QRCode(socialQrCode: e, width: _cardMaxWidth)).toList()), + ), + ); + } +} + +class _QRCode extends StatelessWidget { + final SocialQrCode socialQrCode; + final double width; + + const _QRCode({required this.socialQrCode, required this.width}); + + @override + Widget build(BuildContext context) { + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () { + showDialog( + context: context, + barrierColor: Colors.black.withOpacity(0.98), + builder: (_) => AlertDialog( + backgroundColor: Theme.of(context).colorScheme.surface, + content: SizedBox( + width: MediaQuery.sizeOf(context).height * 0.7, + child: AbsorbPointer(absorbing: true, child: this), + ), + )); + }, + child: Container( + constraints: BoxConstraints(maxWidth: width), + padding: const EdgeInsets.only(top: 16, left: 16, right: 16), + decoration: BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.circular(16), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.circular(8), + ), + child: PrettyQrView( + qrImage: QrImage( + QrCode.fromData( + data: socialQrCode.qrCode, + errorCorrectLevel: QrErrorCorrectLevel.H, + ), + ), + decoration: PrettyQrDecoration( + shape: PrettyQrSmoothSymbol( + color: socialQrCode.color != null + ? Color(int.parse(socialQrCode.color!, radix: 16)) + : Theme.of(context).colorScheme.primary, + roundFactor: 0, + ), + image: PrettyQrDecorationImage( + image: socialQrCode.icon != null + ? NetworkImage(socialQrCode.icon!) as ImageProvider + : const AssetImage('images/dash.png'), + position: PrettyQrDecorationImagePosition.embedded, + ), + )), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + socialQrCode.title, + style: Theme.of(context).textTheme.headlineSmall!.copyWith( + color: Theme.of(context).colorScheme.background, + ), + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, + semanticsLabel: socialQrCode.title, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/presentation/home/home_view/home_page_factory.dart b/lib/presentation/home/home_view/home_page_factory.dart new file mode 100644 index 0000000..4bd8c62 --- /dev/null +++ b/lib/presentation/home/home_view/home_page_factory.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_sp_social/core/utils/layout_util.dart'; +import 'package:flutter_sp_social/presentation/home/event_store.dart'; +import 'package:flutter_sp_social/presentation/home/home_view/desktop_view.dart'; +import 'package:flutter_sp_social/presentation/home/home_view/mobile_view.dart'; + +class HomePageViewFactory { + static Widget build(BuildContext context, EventStore store) { + if (LayoutUtil.isMobileLayout(context)) { + return MobileView(store: store); + } else { + return DesktopView(store: store); + } + } +} diff --git a/lib/presentation/home/home_view/mobile_view.dart b/lib/presentation/home/home_view/mobile_view.dart new file mode 100644 index 0000000..3ed7596 --- /dev/null +++ b/lib/presentation/home/home_view/mobile_view.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +import '../../../data/social_qr_code.dart'; +import '../event_store.dart'; + +class MobileView extends StatelessWidget { + final EventStore store; + + const MobileView({super.key, required this.store}); + + @override + Widget build(BuildContext context) { + return ListView.separated( + padding: const EdgeInsets.all(16), + itemCount: store.value!.socialQrCodes.length, + separatorBuilder: (context, index) => const SizedBox(height: 16), + itemBuilder: (context, index) => ListTileWidget(socialQrCode: store.value!.socialQrCodes[index]), + ); + } +} + +class ListTileWidget extends StatelessWidget { + final SocialQrCode socialQrCode; + const ListTileWidget({super.key, required this.socialQrCode}); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final textTheme = theme.textTheme; + + Widget icon = socialQrCode.icon != null + ? Image.network(socialQrCode.icon!, fit: BoxFit.fitWidth, width: 48, height: 48) + : Image.asset('images/dash.png', width: 48, height: 48, fit: BoxFit.fitWidth); + + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () => launchUrlString(socialQrCode.qrCode), + child: Container( + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all(color: theme.colorScheme.primary), + ), + padding: const EdgeInsets.all(16), + child: Row( + children: [ + icon, + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(socialQrCode.title, style: textTheme.headlineSmall), + Text(socialQrCode.qrCode, style: textTheme.bodySmall), + ], + ), + ), + ], + )), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 93f2280..fb7c488 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -317,6 +317,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e" + url: "https://pub.dev" + source: hosted + version: "6.2.6" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745 + url: "https://pub.dev" + source: hosted + version: "6.3.0" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5" + url: "https://pub.dev" + source: hosted + version: "6.2.5" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 + url: "https://pub.dev" + source: hosted + version: "3.1.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + url: "https://pub.dev" + source: hosted + version: "3.1.0" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b + url: "https://pub.dev" + source: hosted + version: "2.2.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 + url: "https://pub.dev" + source: hosted + version: "3.1.1" vector_math: dependency: transitive description: @@ -350,5 +414,5 @@ packages: source: hosted version: "1.0.3" sdks: - dart: ">=3.2.0-194.0.dev <4.0.0" - flutter: ">=3.7.0" + dart: ">=3.2.3 <4.0.0" + flutter: ">=3.16.6" diff --git a/pubspec.yaml b/pubspec.yaml index 75bcf0a..61cba98 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,6 +15,7 @@ dependencies: crypto: ^3.0.3 google_fonts: any file_picker: ^6.1.1 + url_launcher: ^6.2.6 dev_dependencies: flutter_test: