Skip to content

Commit

Permalink
Convert ProTokenValue to a plain Either container
Browse files Browse the repository at this point in the history
We no longer need the EitherValueNotifier for the token since its owning model will update automatically.
  • Loading branch information
ashuntu committed Dec 16, 2024
1 parent 811d39d commit 224b221
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import 'package:flutter/foundation.dart';
import 'package:p4w_ms_store/p4w_ms_store.dart';

import '/core/agent_api_client.dart';
import '/core/either_value_notifier.dart';
import '/core/pro_token.dart';

class SubscribeNowModel extends ChangeNotifier {
Expand All @@ -13,7 +12,7 @@ class SubscribeNowModel extends ChangeNotifier {

final _token = ProTokenValue();
ProTokenValue get token => _token;
bool get canSubmit => token.valueOrNull != null;
bool get canSubmit => token.token != null;

/// Returns true if the environment variable 'UP4W_ALLOW_STORE_PURCHASE' has been set.
/// Since this reading won't change during the app lifetime, even if the user changes
Expand Down Expand Up @@ -59,19 +58,23 @@ class SubscribeNowModel extends ChangeNotifier {
}
}

/// A value-notifier for the [ProToken] with validation.
class ProTokenValue extends EitherValueNotifier<TokenError, ProToken?> {
ProTokenValue() : super.err(TokenError.empty);
/// A [ProToken] with validation.
///
/// Similar to a [EitherValueNotifier], without the [ValueNotifier].
class ProTokenValue {
ProTokenValue() : either = const Either.left(TokenError.empty);

String? get token => valueOrNull?.value;

bool get hasError => value.isLeft;
Either<TokenError, ProToken?> either;
ProToken? get token => either.orNull();
String? get value => either.orNull()?.value;
TokenError? get error => either.fold(ifLeft: (e) => e, ifRight: (_) => null);
bool get hasError => either.isLeft;

void update(String token) {
value = ProToken.create(token);
either = ProToken.create(token);
}

void clear() {
value = const Right<TokenError, ProToken?>(null);
either = const Either.right(null);

Check warning on line 78 in gui/packages/ubuntupro/lib/pages/subscribe_now/subscribe_now_model.dart

View check run for this annotation

Codecov / codecov/patch

gui/packages/ubuntupro/lib/pages/subscribe_now/subscribe_now_model.dart#L77-L78

Added lines #L77 - L78 were not covered by tests
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ class SubscribeNowPage extends StatelessWidget {
}

void trySubmit(SubscribeNowModel model) {
model.applyProToken(model.token.valueOrNull!).then(onSubscriptionUpdate);
model.applyProToken(model.token.token!).then(onSubscriptionUpdate);
model.token.clear();
controller.clear();

Check warning on line 99 in gui/packages/ubuntupro/lib/pages/subscribe_now/subscribe_now_page.dart

View check run for this annotation

Codecov / codecov/patch

gui/packages/ubuntupro/lib/pages/subscribe_now/subscribe_now_page.dart#L96-L99

Added lines #L96 - L99 were not covered by tests
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,39 +60,29 @@ class _ProTokenInputFieldState extends State<ProTokenInputField> {
styleSheet: linkStyle,
),
const SizedBox(height: 8),
ValueListenableBuilder(
valueListenable: model.token,
builder: (context, _, __) => Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: TextField(
inputFormatters: [
// This ignores all sorts of (Unicode) whitespaces (not only at the ends).
FilteringTextInputFormatter.deny(RegExp(r'\s')),
],
autofocus: false,
controller: widget.controller,
decoration: InputDecoration(
label: Text(lang.tokenInputHint),
error: model.token.errorOrNull?.localize(lang) != null
? Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
model.token.errorOrNull!.localize(lang)!,
style: theme.textTheme.bodySmall!.copyWith(
color: YaruColors.of(context).error,
),
),
)
: null,
),
onChanged: model.tokenUpdate,
onSubmitted: (_) => widget.onSubmit?.call(),
),
),
],
TextField(
inputFormatters: [
// This ignores all sorts of (Unicode) whitespaces (not only at the ends).
FilteringTextInputFormatter.deny(RegExp(r'\s')),
],
autofocus: false,
controller: widget.controller,
decoration: InputDecoration(
label: Text(lang.tokenInputHint),
error: model.token.error?.localize(lang) != null
? Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
model.token.error!.localize(lang)!,
style: theme.textTheme.bodySmall!.copyWith(
color: YaruColors.of(context).error,
),
),
)
: null,
),
onChanged: model.tokenUpdate,
onSubmitted: (_) => widget.onSubmit?.call(),
),
],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ void main() {
final value = ProTokenValue();

value.update('');
expect(value.errorOrNull, TokenError.empty);
expect(value.error, TokenError.empty);

for (final token in tks.invalidTokens) {
value.update(token);
expect(value.errorOrNull, TokenError.invalid);
expect(value.error, TokenError.invalid);
}
});
test('accessors on success', () {
Expand All @@ -48,23 +48,11 @@ void main() {
value.update(tks.good);

expect(value.hasError, isFalse);
expect(value.errorOrNull, isNull);
expect(value.token, tks.good);
expect(value.valueOrNull!.value, tks.good);
expect(value.valueOrNull, tokenInstance);
expect(value.value, equals(ProToken.create(tks.good)));
});

test('notify listeners', () {
final value = ProTokenValue();
var notified = false;
value.addListener(() {
notified = true;
});

value.update(tks.good);

expect(notified, isTrue);
expect(value.error, isNull);
expect(value.value, tks.good);
expect(value.value, tks.good);
expect(value.token, tokenInstance);
expect(value.either, equals(ProToken.create(tks.good)));
});
});

Expand Down

0 comments on commit 224b221

Please sign in to comment.