Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Order UI #94

Merged
merged 26 commits into from
Oct 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/lib/example/example_passes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ class _ExamplePassesState extends State<ExamplePasses> {
child: InkWell(
child: PkPassWidget(pass: pass),
onTap: () {
router.push(
navigator.pushNamed(
'/backside',
extra: PassBackSidePageArgs(pass, false),
arguments: PassBackSidePageArgs(pass, false),
);
},
),
Expand Down
37 changes: 25 additions & 12 deletions app/lib/home/home_page.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:app/home/pass_list_notifier.dart';
import 'package:app/import_order/import_order_page.dart';
import 'package:app/import_pass/import_page.dart';
import 'package:app/import_pass/pick_pass.dart';
import 'package:app/pass_backside/pass_backside_page.dart';
Expand Down Expand Up @@ -38,20 +39,30 @@ class _HomePageState extends State<HomePage> {
}
// TODO(ueman): Add more validation

await router.push(
'/import',
extra: PkPassImportSource(
bytes: await detail.files.first.readAsBytes(),
),
);
if (firstFile.name.endsWith('pkpass')) {
await navigator.pushNamed(
'/import',
arguments: PkPassImportSource(
bytes: await detail.files.first.readAsBytes(),
),
);
}
if (firstFile.name.endsWith('order')) {
await navigator.pushNamed(
'/importOrder',
arguments: PkOrderImportSource(
bytes: await detail.files.first.readAsBytes(),
),
);
}
},
child: Scaffold(
appBar: AppBar(
title: const AppIcon(),
centerTitle: true,
leading: kDebugMode
? IconButton(
onPressed: () => router.push('/examples'),
onPressed: () => navigator.pushNamed('/examples'),
icon: const Icon(Icons.card_giftcard),
)
: null,
Expand All @@ -61,7 +72,7 @@ class _HomePageState extends State<HomePage> {
icon: const Icon(Icons.file_open),
),
IconButton(
onPressed: () => router.push('/settings'),
onPressed: () => navigator.pushNamed('/settings'),
icon: const Icon(Icons.settings),
tooltip: AppLocalizations.of(context).settings,
),
Expand Down Expand Up @@ -96,9 +107,10 @@ class _HomePageState extends State<HomePage> {
return PassListTile(
pass: pass,
onTap: () {
router.push(
navigator.pushNamed(
'/backside',
extra: PassBackSidePageArgs(pass, true),
arguments:
PassBackSidePageArgs(pass, true),
);
},
);
Expand All @@ -108,9 +120,10 @@ class _HomePageState extends State<HomePage> {
child: InkWell(
child: PkPassWidget(pass: pass),
onTap: () {
router.push(
navigator.pushNamed(
'/backside',
extra: PassBackSidePageArgs(pass, true),
arguments:
PassBackSidePageArgs(pass, true),
);
},
),
Expand Down
103 changes: 103 additions & 0 deletions app/lib/import_order/import_order_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import 'dart:io';
import 'dart:typed_data';

import 'package:content_resolver/content_resolver.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:passkit/passkit.dart';
// ignore: implementation_imports
import 'package:passkit_ui/src/order/order_widget.dart';
import 'package:url_launcher/url_launcher.dart';

class PkOrderImportSource {
PkOrderImportSource({this.contentResolverPath, this.bytes, this.filePath})
: assert(
contentResolverPath != null || bytes != null || filePath != null,
);

final String? contentResolverPath;
final Uint8List? bytes;
final String? filePath;

Future<PkOrder> getOrder() async {
if (contentResolverPath != null) {
final Content content =
await ContentResolver.resolveContent(contentResolverPath!);
return PkOrder.fromBytes(
content.data,
skipChecksumVerification: true,
skipSignatureVerification: true,
);
} else if (bytes != null) {
return PkOrder.fromBytes(
bytes!,
skipChecksumVerification: true,
skipSignatureVerification: true,
);
} else if (filePath != null) {
return PkOrder.fromBytes(
await File(filePath!).readAsBytes(),
skipChecksumVerification: true,
skipSignatureVerification: true,
);
}
throw Exception('No data');
}
}

class ImportOrderPage extends StatefulWidget {
const ImportOrderPage({super.key, required this.source});

final PkOrderImportSource source;

@override
State<ImportOrderPage> createState() => _ImportOrderPageState();
}

class _ImportOrderPageState extends State<ImportOrderPage> {
PkOrder? order;

@override
void initState() {
super.initState();
_load();
}

Future<void> _load() async {
final pkOrder = await widget.source.getOrder();
setState(() {
order = pkOrder;
});
}

@override
Widget build(BuildContext context) {
if (order == null) {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context).import),
actions: [
IconButton(
// TODO(ueman): Maybe show confirmation dialog here
onPressed: () => Navigator.of(context).pop(),
icon: const Icon(Icons.delete),
),
],
),
body: const Center(child: CircularProgressIndicator()),
);
} else {
return OrderWidget(
order: order!,
isOrderImport: true,
onDeleteOrderClicked: (order) {},
onManageOrderClicked: launchUrl,
onVisitMerchantWebsiteClicked: launchUrl,
onShareClicked: (order) {},
onMarkOrderCompletedClicked: (order) {},
onTrackingLinkClicked: launchUrl,
onImportOrderClicked: (order) {},
);
}
}
}
37 changes: 37 additions & 0 deletions app/lib/import_order/pick_order.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'package:app/import_order/import_order_page.dart';
import 'package:app/router.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:path/path.dart';

Future<void> pickOrder(BuildContext context) async {
final localizations = AppLocalizations.of(context);
final result = await FilePicker.platform.pickFiles(
dialogTitle: localizations.pickPasses,
allowMultiple: false,
type: FileType.any,
//allowedExtensions: ['pkpass', 'pass'], // This seems to not work even when combined with "type: FileType.custom"
);

if (result == null) {
return;
}

final firstPath = result.files.firstOrNull?.path;

if (firstPath == null) {
return;
}

if ({'.order'}.contains(extension(firstPath))) {
// This is probably not a valid order
// TOOD show a hint to the user, that the user picked an ivalid file
return;
}

await navigator.pushNamed(
'/importOrder',
arguments: PkOrderImportSource(contentResolverPath: firstPath),
);
}
56 changes: 56 additions & 0 deletions app/lib/import_order/receive_pass.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import 'dart:async';

import 'package:app/import_pass/import_page.dart';
import 'package:app/router.dart';
import 'package:flutter/services.dart';
import 'package:receive_intent/receive_intent.dart';

Future<void> initReceiveIntent() async {
try {
final receivedIntent = await ReceiveIntent.getInitialIntent();
await _onIntent(receivedIntent);
} on PlatformException {
// Handle exception
}
}

// Does not need to be cancelled, since it's running for the lifetime of the app
// ignore: unused_element
StreamSubscription<Intent?>? _sub;

Future<void> initReceiveIntentWhileRunning() async {
_sub = ReceiveIntent.receivedIntentStream.listen(
(Intent? intent) {
// Validate receivedIntent and warn the user, if it is not correct,
_onIntent(intent);
},
onError: (err) {
// TODO(ueman): Handle exception
},
);
}

Future<void> _onIntent(Intent? receivedIntent) async {
if (receivedIntent == null || receivedIntent.isNull) {
// Validate receivedIntent and warn the user, if it is not correct,
// but keep in mind it could be `null` or "empty"(`receivedIntent.isNull`).
//
// TODO(ueman): show error popup?
return;
}
if (receivedIntent.action == 'android.intent.action.MAIN') {
return;
}
final path = receivedIntent.data;

if (path == null) {
// TODO(ueman): show error popup?
return;
}
unawaited(
navigator.pushNamed(
'/import',
arguments: PkPassImportSource(contentResolverPath: path),
),
);
}
7 changes: 3 additions & 4 deletions app/lib/import_pass/import_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import 'package:app/router.dart';
import 'package:content_resolver/content_resolver.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:go_router/go_router.dart';
import 'package:passkit/passkit.dart';
import 'package:passkit_ui/passkit_ui.dart';

Expand Down Expand Up @@ -88,9 +87,9 @@ class _ImportPassPageState extends State<ImportPassPage> {
child: InkWell(
child: Center(child: PkPassWidget(pass: pass!)),
onTap: () {
router.push(
navigator.pushNamed(
'/backside',
extra: PassBackSidePageArgs(pass!, false),
arguments: PassBackSidePageArgs(pass!, false),
);
},
),
Expand Down Expand Up @@ -128,7 +127,7 @@ class _ImportPassPageState extends State<ImportPassPage> {
actions: [
IconButton(
// TODO(ueman): Maybe show confirmation dialog here
onPressed: () => context.pop(),
onPressed: () => Navigator.of(context).pop(),
icon: const Icon(Icons.delete),
),
],
Expand Down
17 changes: 13 additions & 4 deletions app/lib/import_pass/pick_pass.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:app/import_order/import_order_page.dart';
import 'package:app/import_pass/import_page.dart';
import 'package:app/router.dart';
import 'package:file_picker/file_picker.dart';
Expand All @@ -24,11 +25,19 @@ Future<void> pickPass(BuildContext context) async {
return;
}

if (!{'.pkpass', '.pass'}.contains(extension(firstPath))) {
// This is probably not a valid PkPass
// TOOD show a hint to the user, that the user picked an ivalid file
if ({'.pkpass', '.pass'}.contains(extension(firstPath))) {
await navigator.pushNamed(
'/import',
arguments: PkPassImportSource(filePath: firstPath),
);
return;
}

await router.push('/import', extra: PkPassImportSource(filePath: firstPath));
if ('.order' == extension(firstPath)) {
await navigator.pushNamed(
'/importOrder',
arguments: PkOrderImportSource(filePath: firstPath),
);
return;
}
}
4 changes: 2 additions & 2 deletions app/lib/import_pass/receive_pass.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ Future<void> _onIntent(Intent? receivedIntent) async {
return;
}
unawaited(
router.push(
navigator.pushNamed(
'/import',
extra: PkPassImportSource(contentResolverPath: path),
arguments: PkPassImportSource(contentResolverPath: path),
),
);
}
5 changes: 3 additions & 2 deletions app/lib/main_app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@ class MainApp extends StatelessWidget {

@override
Widget build(BuildContext context) {
return MaterialApp.router(
return MaterialApp(
debugShowCheckedModeBanner: false,
routerConfig: router,
navigatorKey: navigatorKey,
scaffoldMessengerKey: scaffoldMessenger,
theme: _lightTheme(),
darkTheme: _darkTheme(),
themeMode: ThemeMode.system,
onGenerateTitle: (context) => AppLocalizations.of(context).appName,
supportedLocales: AppLocalizations.supportedLocales,
localizationsDelegates: AppLocalizations.localizationsDelegates,
onGenerateRoute: routeGenerator,
);
}
}
Expand Down
Loading
Loading