diff --git a/packages/shorebird_cli/lib/src/commands/account/account_usage_command.dart b/packages/shorebird_cli/lib/src/commands/account/account_usage_command.dart index e6699882..060dc325 100644 --- a/packages/shorebird_cli/lib/src/commands/account/account_usage_command.dart +++ b/packages/shorebird_cli/lib/src/commands/account/account_usage_command.dart @@ -42,8 +42,6 @@ class AccountUsageCommand extends ShorebirdCommand extension on GetUsageResponse { String prettyPrint() { - final currencyFormatter = NumberFormat('#,##0.00', 'en_US'); - const cellStyle = CellStyle( paddingLeft: 1, paddingRight: 1, @@ -99,7 +97,7 @@ ${Table( ${styleBold.wrap('${lightCyan.wrap(remainingPatchInstalls)} patch installs remaining in the current billing period.')} Current Billing Period: ${lightCyan.wrap(DateFormat.yMMMd().format(currentPeriodStart))} - ${lightCyan.wrap(DateFormat.yMMMd().format(currentPeriodEnd))} -Month-to-date cost: ${lightCyan.wrap('\$${currencyFormatter.format(currentPeriodCost / 100.0)}')} +Month-to-date cost: ${lightCyan.wrap(currentPeriodCost.toString())} ${styleBold.wrap('*Usage data is not reported in real-time and may be delayed by up to 48 hours.')}'''; } diff --git a/packages/shorebird_cli/lib/src/commands/apps/delete_apps_command.dart b/packages/shorebird_cli/lib/src/commands/apps/delete_apps_command.dart index 348b9043..9a10556f 100644 --- a/packages/shorebird_cli/lib/src/commands/apps/delete_apps_command.dart +++ b/packages/shorebird_cli/lib/src/commands/apps/delete_apps_command.dart @@ -30,7 +30,6 @@ Defaults to the app_id in "shorebird.yaml".''', help: 'Release without confirmation if there are no errors.', negatable: false, ); - ; } @override diff --git a/packages/shorebird_cli/lib/src/validators/shorebird_flutter_validator.dart b/packages/shorebird_cli/lib/src/validators/shorebird_flutter_validator.dart index dbb44438..570a5eef 100644 --- a/packages/shorebird_cli/lib/src/validators/shorebird_flutter_validator.dart +++ b/packages/shorebird_cli/lib/src/validators/shorebird_flutter_validator.dart @@ -1,5 +1,5 @@ -import 'package:shorebird_cli/src/shorebird_environment.dart'; import 'package:shorebird_cli/src/process.dart'; +import 'package:shorebird_cli/src/shorebird_environment.dart'; import 'package:shorebird_cli/src/validators/validators.dart'; import 'package:version/version.dart'; diff --git a/packages/shorebird_cli/pubspec.yaml b/packages/shorebird_cli/pubspec.yaml index 0bf3d66d..b0b0029d 100644 --- a/packages/shorebird_cli/pubspec.yaml +++ b/packages/shorebird_cli/pubspec.yaml @@ -23,6 +23,7 @@ dependencies: json_annotation: ^4.8.0 mason_logger: ^0.2.4 meta: ^1.9.0 + money2: ^3.4.1 path: ^1.8.3 platform: ^3.1.0 propertylistserialization: ^1.3.0 diff --git a/packages/shorebird_cli/test/src/code_push_client_wrapper_test.dart b/packages/shorebird_cli/test/src/code_push_client_wrapper_test.dart index 11be65c6..56fa9102 100644 --- a/packages/shorebird_cli/test/src/code_push_client_wrapper_test.dart +++ b/packages/shorebird_cli/test/src/code_push_client_wrapper_test.dart @@ -1,6 +1,7 @@ import 'package:http/http.dart' as http; import 'package:mason_logger/mason_logger.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:money2/money2.dart'; import 'package:path/path.dart' as p; import 'package:platform/platform.dart'; import 'package:scoped/scoped.dart'; @@ -1679,9 +1680,9 @@ Please bump your version number and try again.''', test('returns usage when succeeds', () async { final usage = GetUsageResponse( - plan: const ShorebirdPlan( + plan: ShorebirdPlan( name: 'Hobby', - monthlyCost: 0, + monthlyCost: Money.fromIntWithCurrency(0, usd), patchInstallLimit: 1000, maxTeamSize: 1, ), @@ -1693,7 +1694,7 @@ Please bump your version number and try again.''', ), ], patchInstallLimit: 20000, - currentPeriodCost: 0, + currentPeriodCost: Money.fromIntWithCurrency(0, usd), currentPeriodStart: DateTime(2023), currentPeriodEnd: DateTime(2023, 2), ); diff --git a/packages/shorebird_cli/test/src/command_runner_test.dart b/packages/shorebird_cli/test/src/command_runner_test.dart index 627ffc92..166b85d3 100644 --- a/packages/shorebird_cli/test/src/command_runner_test.dart +++ b/packages/shorebird_cli/test/src/command_runner_test.dart @@ -6,8 +6,8 @@ import 'package:scoped/scoped.dart'; import 'package:shorebird_cli/src/auth/auth.dart' hide auth; import 'package:shorebird_cli/src/command_runner.dart'; import 'package:shorebird_cli/src/logger.dart' hide logger; -import 'package:shorebird_cli/src/shorebird_environment.dart'; import 'package:shorebird_cli/src/process.dart'; +import 'package:shorebird_cli/src/shorebird_environment.dart'; import 'package:shorebird_cli/src/version.dart'; import 'package:test/test.dart'; diff --git a/packages/shorebird_cli/test/src/commands/account/account_usage_command_test.dart b/packages/shorebird_cli/test/src/commands/account/account_usage_command_test.dart index 8c54f575..155e5ae0 100644 --- a/packages/shorebird_cli/test/src/commands/account/account_usage_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/account/account_usage_command_test.dart @@ -1,6 +1,7 @@ import 'package:intl/intl.dart'; import 'package:mason_logger/mason_logger.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:money2/money2.dart'; import 'package:scoped/scoped.dart'; import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; @@ -68,9 +69,9 @@ void main() { test('exits with code 0 when usage is fetched.', () async { final usage = GetUsageResponse( - plan: const ShorebirdPlan( + plan: ShorebirdPlan( name: 'Team', - monthlyCost: 2000, + monthlyCost: Money.fromIntWithCurrency(2000, usd), patchInstallLimit: 1000, maxTeamSize: 1, ), @@ -87,7 +88,7 @@ void main() { ), ], patchInstallLimit: 20000, - currentPeriodCost: 2000, + currentPeriodCost: Money.fromIntWithCurrency(2000, usd), currentPeriodStart: DateTime(2023), currentPeriodEnd: DateTime(2023, 2), ); @@ -129,9 +130,9 @@ ${styleBold.wrap('*Usage data is not reported in real-time and may be delayed by test('exits with code 0 when usage is fetched (unlimited).', () async { final usage = GetUsageResponse( - plan: const ShorebirdPlan( + plan: ShorebirdPlan( name: 'Hobby', - monthlyCost: 0, + monthlyCost: Money.fromIntWithCurrency(0, usd), patchInstallLimit: 1000, maxTeamSize: 1, ), @@ -147,7 +148,7 @@ ${styleBold.wrap('*Usage data is not reported in real-time and may be delayed by patchInstallCount: 42, ), ], - currentPeriodCost: 0, + currentPeriodCost: Money.fromIntWithCurrency(0, usd), currentPeriodStart: DateTime(2023), currentPeriodEnd: DateTime(2023, 2), ); diff --git a/packages/shorebird_cli/test/src/shorebird_process_test.dart b/packages/shorebird_cli/test/src/shorebird_process_test.dart index 5496bb54..2c1c9c50 100644 --- a/packages/shorebird_cli/test/src/shorebird_process_test.dart +++ b/packages/shorebird_cli/test/src/shorebird_process_test.dart @@ -28,7 +28,6 @@ void main() { startProcess = _MockProcess(); shorebirdProcess = ShorebirdProcess( processWrapper: processWrapper, - engineConfig: const EngineConfig.empty(), ); when( diff --git a/packages/shorebird_cli/test/src/validators/shorebird_flutter_validator_test.dart b/packages/shorebird_cli/test/src/validators/shorebird_flutter_validator_test.dart index 3440e619..83dd8a6c 100644 --- a/packages/shorebird_cli/test/src/validators/shorebird_flutter_validator_test.dart +++ b/packages/shorebird_cli/test/src/validators/shorebird_flutter_validator_test.dart @@ -5,8 +5,8 @@ import 'package:path/path.dart' as p; import 'package:platform/platform.dart'; import 'package:scoped/scoped.dart'; import 'package:shorebird_cli/src/platform.dart'; -import 'package:shorebird_cli/src/shorebird_environment.dart'; import 'package:shorebird_cli/src/process.dart'; +import 'package:shorebird_cli/src/shorebird_environment.dart'; import 'package:shorebird_cli/src/validators/validators.dart'; import 'package:test/test.dart'; diff --git a/packages/shorebird_code_push_client/pubspec.yaml b/packages/shorebird_code_push_client/pubspec.yaml index 9445397a..a894497b 100644 --- a/packages/shorebird_code_push_client/pubspec.yaml +++ b/packages/shorebird_code_push_client/pubspec.yaml @@ -10,6 +10,7 @@ environment: dependencies: http: ^1.0.0 + money2: ^3.4.1 shorebird_code_push_protocol: path: ../shorebird_code_push_protocol diff --git a/packages/shorebird_code_push_client/test/src/code_push_client_test.dart b/packages/shorebird_code_push_client/test/src/code_push_client_test.dart index 63497017..126e30ca 100644 --- a/packages/shorebird_code_push_client/test/src/code_push_client_test.dart +++ b/packages/shorebird_code_push_client/test/src/code_push_client_test.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:http/http.dart' as http; import 'package:mocktail/mocktail.dart'; +import 'package:money2/money2.dart'; import 'package:path/path.dart' as path; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; import 'package:test/test.dart'; @@ -1930,9 +1931,9 @@ void main() { test('completes when request succeeds', () async { final expected = GetUsageResponse( - plan: const ShorebirdPlan( + plan: ShorebirdPlan( name: 'Hobby', - monthlyCost: 0, + monthlyCost: Money.fromIntWithCurrency(0, usd), patchInstallLimit: 1000, maxTeamSize: 1, ), @@ -1944,7 +1945,7 @@ void main() { ) ], patchInstallLimit: 1337, - currentPeriodCost: 0, + currentPeriodCost: Money.fromIntWithCurrency(0, usd), currentPeriodStart: DateTime(2023), currentPeriodEnd: DateTime(2023, 2), ); diff --git a/packages/shorebird_code_push_protocol/lib/src/converters/converters.dart b/packages/shorebird_code_push_protocol/lib/src/converters/converters.dart index 0f233241..b7bdc0ad 100644 --- a/packages/shorebird_code_push_protocol/lib/src/converters/converters.dart +++ b/packages/shorebird_code_push_protocol/lib/src/converters/converters.dart @@ -1 +1,2 @@ +export 'money_converter.dart'; export 'timestamp_converter.dart'; diff --git a/packages/shorebird_code_push_protocol/lib/src/converters/money_converter.dart b/packages/shorebird_code_push_protocol/lib/src/converters/money_converter.dart new file mode 100644 index 00000000..129ddcd4 --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/src/converters/money_converter.dart @@ -0,0 +1,19 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:money2/money2.dart'; + +/// The US dollar. +Currency get usd => Currency.create('USD', 2); + +/// {@template money_converter} +/// Converts between [Money] and [String]. +/// {@endtemplate} +class MoneyConverter implements JsonConverter { + /// {@macro money_converter} + const MoneyConverter(); + + @override + Money fromJson(String cents) => Money.parseWithCurrency(cents, usd); + + @override + String toJson(Money money) => money.minorUnits.toString(); +} diff --git a/packages/shorebird_code_push_protocol/lib/src/messages/get_usage/get_usage_response.dart b/packages/shorebird_code_push_protocol/lib/src/messages/get_usage/get_usage_response.dart index 90cae455..4ce3c82d 100644 --- a/packages/shorebird_code_push_protocol/lib/src/messages/get_usage/get_usage_response.dart +++ b/packages/shorebird_code_push_protocol/lib/src/messages/get_usage/get_usage_response.dart @@ -1,4 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; +import 'package:money2/money2.dart'; import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart'; part 'get_usage_response.g.dart'; @@ -39,7 +40,8 @@ class GetUsageResponse { /// The total cost so far for the current billing period. Includes base /// monthly cost and any overages. - final int currentPeriodCost; + @MoneyConverter() + final Money currentPeriodCost; /// The upper limit of patch installs for the current billing period. /// If `null`, there is no limit. diff --git a/packages/shorebird_code_push_protocol/lib/src/messages/get_usage/get_usage_response.g.dart b/packages/shorebird_code_push_protocol/lib/src/messages/get_usage/get_usage_response.g.dart index a37589b9..088d7faa 100644 --- a/packages/shorebird_code_push_protocol/lib/src/messages/get_usage/get_usage_response.g.dart +++ b/packages/shorebird_code_push_protocol/lib/src/messages/get_usage/get_usage_response.g.dart @@ -25,8 +25,8 @@ GetUsageResponse _$GetUsageResponseFromJson(Map json) => 'current_period_start', (v) => DateTime.parse(v as String)), currentPeriodEnd: $checkedConvert( 'current_period_end', (v) => DateTime.parse(v as String)), - currentPeriodCost: - $checkedConvert('current_period_cost', (v) => v as int), + currentPeriodCost: $checkedConvert('current_period_cost', + (v) => const MoneyConverter().fromJson(v as String)), patchInstallLimit: $checkedConvert('patch_install_limit', (v) => v as int?), ); @@ -46,7 +46,8 @@ Map _$GetUsageResponseToJson(GetUsageResponse instance) => 'apps': instance.apps.map((e) => e.toJson()).toList(), 'current_period_start': instance.currentPeriodStart.toIso8601String(), 'current_period_end': instance.currentPeriodEnd.toIso8601String(), - 'current_period_cost': instance.currentPeriodCost, + 'current_period_cost': + const MoneyConverter().toJson(instance.currentPeriodCost), 'patch_install_limit': instance.patchInstallLimit, }; diff --git a/packages/shorebird_code_push_protocol/lib/src/models/shorebird_plan.dart b/packages/shorebird_code_push_protocol/lib/src/models/shorebird_plan.dart index c2f0cfbd..e2075cdd 100644 --- a/packages/shorebird_code_push_protocol/lib/src/models/shorebird_plan.dart +++ b/packages/shorebird_code_push_protocol/lib/src/models/shorebird_plan.dart @@ -1,4 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; +import 'package:money2/money2.dart'; import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart'; part 'shorebird_plan.g.dart'; @@ -25,8 +26,9 @@ class ShorebirdPlan { /// The name of the plan. final String name; - /// Billing rate, in cents. - final int monthlyCost; + /// Monthly billing rate. + @MoneyConverter() + final Money monthlyCost; /// The number of patch installs allowed per billing period. This will be null /// for accounts with unlimited patch installs. diff --git a/packages/shorebird_code_push_protocol/lib/src/models/shorebird_plan.g.dart b/packages/shorebird_code_push_protocol/lib/src/models/shorebird_plan.g.dart index 17eaa103..5edf378e 100644 --- a/packages/shorebird_code_push_protocol/lib/src/models/shorebird_plan.g.dart +++ b/packages/shorebird_code_push_protocol/lib/src/models/shorebird_plan.g.dart @@ -15,7 +15,8 @@ ShorebirdPlan _$ShorebirdPlanFromJson(Map json) => ($checkedConvert) { final val = ShorebirdPlan( name: $checkedConvert('name', (v) => v as String), - monthlyCost: $checkedConvert('monthly_cost', (v) => v as int), + monthlyCost: $checkedConvert('monthly_cost', + (v) => const MoneyConverter().fromJson(v as String)), patchInstallLimit: $checkedConvert('patch_install_limit', (v) => v as int?), maxTeamSize: $checkedConvert('max_team_size', (v) => v as int?), @@ -32,7 +33,7 @@ ShorebirdPlan _$ShorebirdPlanFromJson(Map json) => Map _$ShorebirdPlanToJson(ShorebirdPlan instance) => { 'name': instance.name, - 'monthly_cost': instance.monthlyCost, + 'monthly_cost': const MoneyConverter().toJson(instance.monthlyCost), 'patch_install_limit': instance.patchInstallLimit, 'max_team_size': instance.maxTeamSize, }; diff --git a/packages/shorebird_code_push_protocol/pubspec.yaml b/packages/shorebird_code_push_protocol/pubspec.yaml index d04d963e..2a46683b 100644 --- a/packages/shorebird_code_push_protocol/pubspec.yaml +++ b/packages/shorebird_code_push_protocol/pubspec.yaml @@ -10,6 +10,7 @@ environment: dependencies: json_annotation: ^4.8.0 + money2: ^3.4.1 dev_dependencies: build_runner: ^2.0.0 diff --git a/packages/shorebird_code_push_protocol/test/src/messages/get_usage/get_usage_response_test.dart b/packages/shorebird_code_push_protocol/test/src/messages/get_usage/get_usage_response_test.dart index 5fcaf405..8284bbf3 100644 --- a/packages/shorebird_code_push_protocol/test/src/messages/get_usage/get_usage_response_test.dart +++ b/packages/shorebird_code_push_protocol/test/src/messages/get_usage/get_usage_response_test.dart @@ -1,3 +1,4 @@ +import 'package:money2/money2.dart'; import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart'; import 'package:test/test.dart'; @@ -5,9 +6,9 @@ void main() { group(GetUsageResponse, () { test('can be (de)serialized', () { final response = GetUsageResponse( - plan: const ShorebirdPlan( + plan: ShorebirdPlan( name: 'Hobby', - monthlyCost: 0, + monthlyCost: Money.fromIntWithCurrency(0, usd), patchInstallLimit: 1000, maxTeamSize: 1, ), @@ -15,7 +16,7 @@ void main() { const AppUsage(id: 'app-id', name: 'My app', patchInstallCount: 1337), ], patchInstallLimit: 42, - currentPeriodCost: 0, + currentPeriodCost: Money.fromIntWithCurrency(0, usd), currentPeriodStart: DateTime(2021), currentPeriodEnd: DateTime(2021, 1, 2), ); diff --git a/packages/shorebird_code_push_protocol/test/src/models/shorebird_plan_test.dart b/packages/shorebird_code_push_protocol/test/src/models/shorebird_plan_test.dart index db0d0a6a..73fa10a9 100644 --- a/packages/shorebird_code_push_protocol/test/src/models/shorebird_plan_test.dart +++ b/packages/shorebird_code_push_protocol/test/src/models/shorebird_plan_test.dart @@ -1,12 +1,13 @@ +import 'package:money2/money2.dart'; import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart'; import 'package:test/test.dart'; void main() { group(ShorebirdPlan, () { test('can be (de)serialized', () { - const plan = ShorebirdPlan( + final plan = ShorebirdPlan( name: 'Hobby', - monthlyCost: 0, + monthlyCost: Money.fromIntWithCurrency(0, usd), patchInstallLimit: 1000, maxTeamSize: 1, ); diff --git a/packages/shorebird_code_push_protocol/test/src/models/subscription_test.dart b/packages/shorebird_code_push_protocol/test/src/models/subscription_test.dart index 81d73827..ca22bfec 100644 --- a/packages/shorebird_code_push_protocol/test/src/models/subscription_test.dart +++ b/packages/shorebird_code_push_protocol/test/src/models/subscription_test.dart @@ -1,11 +1,12 @@ +import 'package:money2/money2.dart'; import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart'; import 'package:test/test.dart'; void main() { group(Subscription, () { - const plan = ShorebirdPlan( + final plan = ShorebirdPlan( name: 'Hobby', - monthlyCost: 0, + monthlyCost: Money.fromIntWithCurrency(0, usd), patchInstallLimit: 1000, maxTeamSize: 1, );