Skip to content

Commit

Permalink
Hide units (#324)
Browse files Browse the repository at this point in the history
* Moved `ChoosePropertyPage` outside `ReorderUnitsPage`

* Setup UI

* Improve "Select all" UI

* Implemented the logic (provider)

* Implemented SelectUnits page

* The hidden units are now saved

* Hide units in conversion

* Added translated strings

* Moved `ReorderPage` to `pages` for consistency

* Use `l10n` for `AppLocalizations.of(context)!`
  • Loading branch information
ferraridamiano authored Jan 5, 2025
1 parent cd17eab commit b8a2ca9
Show file tree
Hide file tree
Showing 36 changed files with 1,117 additions and 304 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build_deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ jobs:
- name: Install missing packages
run: |
sudo add-apt-repository universe
sudo apt update
sudo apt install -y clang cmake ninja-build pkg-config libgtk-3-dev desktop-file-utils libfuse2
sudo apt-get update
sudo apt-get install -y ninja-build libgtk-3-dev desktop-file-utils libfuse2
- name: Setup Flutter
uses: subosito/flutter-action@v2
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ jobs:

- name: Install missing packages
run: |
sudo apt update
sudo apt install -y clang cmake ninja-build pkg-config libgtk-3-dev
sudo apt-get update
sudo apt-get install -y ninja-build libgtk-3-dev
- name: Setup Flutter
uses: subosito/flutter-action@v2
Expand All @@ -55,7 +55,7 @@ jobs:
- name: Bootstrap
run: melos bootstrap

- name: Run large display integration testing
run: |
export DISPLAY=:99
Expand Down
88 changes: 43 additions & 45 deletions integration_test/large_display_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,49 +21,6 @@ void main() {
}

group('Common conversions tasks:', () {
testWidgets('Perform conversion, clear and undo',
(WidgetTester tester) async {
await testInit(tester);
final tffMiles = find
.byKey(const ValueKey('LENGTH.miles'))
.evaluate()
.single
.widget as TextFormField;
final tffFeet = find
.byKey(const ValueKey('LENGTH.feet'))
.evaluate()
.single
.widget as TextFormField;
final tffMeters = find
.byKey(const ValueKey('LENGTH.meters'))
.evaluate()
.single
.widget as TextFormField;

expect(find.text('Length'), findsAtLeastNWidgets(2),
reason: 'Expected the length page');

await tester.enterText(find.byKey(const ValueKey('LENGTH.miles')), '1');
await tester.pumpAndSettle();

expect(tffFeet.controller!.text, '5280', reason: 'Conversion error');
expect(tffMeters.controller!.text, '1609.344',
reason: 'Conversion error');

await tester.tap(find.byKey(const ValueKey('clearAll')));
await tester.pumpAndSettle();
expect(tffMiles.controller!.text, '', reason: 'Text not cleared');
expect(tffFeet.controller!.text, '', reason: 'Text not cleared');
expect(tffMeters.controller!.text, '', reason: 'Text not cleared');

await tester.tap(find.byKey(const ValueKey('undoClearAll')));
await tester.pumpAndSettle();
expect(tffMiles.controller!.text, '1.0', reason: 'Text not restored');
expect(tffFeet.controller!.text, '5280.0', reason: 'Text not restored');
expect(tffMeters.controller!.text, '1609.344',
reason: 'Text not restored');
});

testWidgets('Change to a new property and perform conversion',
(WidgetTester tester) async {
await testInit(tester);
Expand Down Expand Up @@ -110,6 +67,47 @@ void main() {
});
});

testWidgets('Perform conversion, clear and undo',
(WidgetTester tester) async {
await testInit(tester);
final tffMiles = find
.byKey(const ValueKey('LENGTH.miles'))
.evaluate()
.single
.widget as TextFormField;
final tffFeet = find
.byKey(const ValueKey('LENGTH.feet'))
.evaluate()
.single
.widget as TextFormField;
final tffMeters = find
.byKey(const ValueKey('LENGTH.meters'))
.evaluate()
.single
.widget as TextFormField;

expect(find.text('Length'), findsAtLeastNWidgets(2),
reason: 'Expected the length page');

await tester.enterText(find.byKey(const ValueKey('LENGTH.miles')), '1');
await tester.pumpAndSettle();

expect(tffFeet.controller!.text, '5280', reason: 'Conversion error');
expect(tffMeters.controller!.text, '1609.344', reason: 'Conversion error');

await tester.tap(find.byKey(const ValueKey('clearAll')));
await tester.pumpAndSettle();
expect(tffMiles.controller!.text, '', reason: 'Text not cleared');
expect(tffFeet.controller!.text, '', reason: 'Text not cleared');
expect(tffMeters.controller!.text, '', reason: 'Text not cleared');

await tester.tap(find.byKey(const ValueKey('undoClearAll')));
await tester.pumpAndSettle();
expect(tffMiles.controller!.text, '1.0', reason: 'Text not restored');
expect(tffFeet.controller!.text, '5280.0', reason: 'Text not restored');
expect(tffMeters.controller!.text, '1609.344', reason: 'Text not restored');
});

group('Language tasks:', () {
testWidgets('Change language', (WidgetTester tester) async {
await testInit(tester);
Expand Down Expand Up @@ -184,7 +182,7 @@ void main() {
await tester.tap(find.byKey(const ValueKey('confirm')));
await tester.pumpAndSettle();

await tester.tap(find.text('Length'));
await tester.tap(find.byKey(ValueKey('drawerItem_PROPERTYX.length')));
await tester.pumpAndSettle();

// Now the ordering should be Inches, Centimeters, Meters, ...
Expand Down Expand Up @@ -381,7 +379,7 @@ void main() {
await tester.tap(find.byKey(const ValueKey('confirm')));
await tester.pumpAndSettle();

await tester.tap(find.text('Length'));
await tester.tap(find.byKey(ValueKey('drawerItem_PROPERTYX.length')));
await tester.pumpAndSettle();

await tester.enterText(find.byKey(const ValueKey('LENGTH.miles')), '1');
Expand Down
90 changes: 44 additions & 46 deletions integration_test/small_display_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,50 +21,6 @@ void main() {
}

group('Common conversions tasks:', () {
testWidgets('Perform conversion, clear and undo',
(WidgetTester tester) async {
await testInit(tester);

final tffMiles = find
.byKey(const ValueKey('LENGTH.miles'))
.evaluate()
.single
.widget as TextFormField;
final tffFeet = find
.byKey(const ValueKey('LENGTH.feet'))
.evaluate()
.single
.widget as TextFormField;
final tffMeters = find
.byKey(const ValueKey('LENGTH.meters'))
.evaluate()
.single
.widget as TextFormField;

expect(find.text('Length'), findsAtLeastNWidgets(1),
reason: 'Expected the length page');

await tester.enterText(find.byKey(const ValueKey('LENGTH.miles')), '1');
await tester.pumpAndSettle();

expect(tffFeet.controller!.text, '5280', reason: 'Conversion error');
expect(tffMeters.controller!.text, '1609.344',
reason: 'Conversion error');

await tester.tap(find.byKey(const ValueKey('clearAll')));
await tester.pumpAndSettle();
expect(tffMiles.controller!.text, '', reason: 'Text not cleared');
expect(tffFeet.controller!.text, '', reason: 'Text not cleared');
expect(tffMeters.controller!.text, '', reason: 'Text not cleared');

await tester.tap(find.byKey(const ValueKey('undoClearAll')));
await tester.pumpAndSettle();
expect(tffMiles.controller!.text, '1.0', reason: 'Text not restored');
expect(tffFeet.controller!.text, '5280.0', reason: 'Text not restored');
expect(tffMeters.controller!.text, '1609.344',
reason: 'Text not restored');
});

testWidgets('Change to a new property and perform conversion',
(WidgetTester tester) async {
await testInit(tester);
Expand Down Expand Up @@ -115,6 +71,48 @@ void main() {
});
});

testWidgets('Perform conversion, clear and undo',
(WidgetTester tester) async {
await testInit(tester);

final tffMiles = find
.byKey(const ValueKey('LENGTH.miles'))
.evaluate()
.single
.widget as TextFormField;
final tffFeet = find
.byKey(const ValueKey('LENGTH.feet'))
.evaluate()
.single
.widget as TextFormField;
final tffMeters = find
.byKey(const ValueKey('LENGTH.meters'))
.evaluate()
.single
.widget as TextFormField;

expect(find.text('Length'), findsAtLeastNWidgets(1),
reason: 'Expected the length page');

await tester.enterText(find.byKey(const ValueKey('LENGTH.miles')), '1');
await tester.pumpAndSettle();

expect(tffFeet.controller!.text, '5280', reason: 'Conversion error');
expect(tffMeters.controller!.text, '1609.344', reason: 'Conversion error');

await tester.tap(find.byKey(const ValueKey('clearAll')));
await tester.pumpAndSettle();
expect(tffMiles.controller!.text, '', reason: 'Text not cleared');
expect(tffFeet.controller!.text, '', reason: 'Text not cleared');
expect(tffMeters.controller!.text, '', reason: 'Text not cleared');

await tester.tap(find.byKey(const ValueKey('undoClearAll')));
await tester.pumpAndSettle();
expect(tffMiles.controller!.text, '1.0', reason: 'Text not restored');
expect(tffFeet.controller!.text, '5280.0', reason: 'Text not restored');
expect(tffMeters.controller!.text, '1609.344', reason: 'Text not restored');
});

group('Language tasks:', () {
testWidgets('Change language', (WidgetTester tester) async {
await testInit(tester);
Expand Down Expand Up @@ -193,7 +191,7 @@ void main() {
await tester.tap(find.byIcon(Icons.menu)); // Open drawer
await tester.pumpAndSettle();

await tester.tap(find.text('Length'));
await tester.tap(find.byKey(ValueKey('drawerItem_PROPERTYX.length')));
await tester.pumpAndSettle();

// Now the ordering should be Inches, Centimeters, Meters, ...
Expand Down Expand Up @@ -396,7 +394,7 @@ void main() {
await tester.tap(find.byIcon(Icons.menu)); // Open drawer
await tester.pumpAndSettle();

await tester.tap(find.text('Length'));
await tester.tap(find.byKey(ValueKey('drawerItem_PROPERTYX.length')));
await tester.pumpAndSettle();

await tester.enterText(find.byKey(const ValueKey('LENGTH.miles')), '1');
Expand Down
27 changes: 25 additions & 2 deletions lib/app_router.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import 'package:converterpro/models/conversions.dart';
import 'package:converterpro/models/hide_units.dart';
import 'package:converterpro/models/order.dart';
import 'package:converterpro/models/properties_list.dart';
import 'package:converterpro/models/settings.dart';
import 'package:converterpro/pages/conversion_page.dart';
import 'package:converterpro/pages/error_page.dart';
import 'package:converterpro/pages/hide_units_page.dart';
import 'package:converterpro/pages/reorder_properties_page.dart';
import 'package:converterpro/pages/reorder_units_page.dart';
import 'package:converterpro/pages/settings_page.dart';
Expand All @@ -26,6 +29,8 @@ final isEverythingLoadedProvider = Provider<bool>((ref) =>
ref.watch(CurrentLocale.provider).hasValue &&
ref.watch(PropertiesOrderNotifier.provider).hasValue &&
ref.watch(UnitsOrderNotifier.provider).hasValue &&
ref.watch(ConversionsNotifier.provider).hasValue &&
ref.watch(HiddenUnitsNotifier.provider).hasValue &&
ref.watch(propertiesMapProvider).hasValue);

final routerProvider = Provider<GoRouter>(
Expand Down Expand Up @@ -63,14 +68,32 @@ final routerProvider = Provider<GoRouter>(
GoRoute(
path: 'reorder-units',
name: 'reorder-units',
builder: (context, state) => const ChoosePropertyPage(),
builder: (context, state) => const ReorderUnitsPage(),
routes: [
GoRoute(
path: ':property',
builder: (context, state) {
final String property = state.pathParameters['property']!;
final propertyx = kebabStringToPropertyX(property);
return ChoosePropertyPage(
return ReorderUnitsPage(
selectedProperty: propertyx,
isPropertySelected: true,
);
},
),
],
),
GoRoute(
path: 'hide-units',
name: 'hide-units',
builder: (context, state) => const HideUnitsPage(),
routes: [
GoRoute(
path: ':property',
builder: (context, state) {
final String property = state.pathParameters['property']!;
final propertyx = kebabStringToPropertyX(property);
return HideUnitsPage(
selectedProperty: propertyx,
isPropertySelected: true,
);
Expand Down
57 changes: 57 additions & 0 deletions lib/models/hide_units.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'package:collection/collection.dart';
import 'package:converterpro/data/default_order.dart';
import 'package:converterpro/models/settings.dart';
import 'package:converterpro/utils/utils.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class HiddenUnitsNotifier extends AsyncNotifier<Map<PROPERTYX, List>> {
static final provider =
AsyncNotifierProvider<HiddenUnitsNotifier, Map<PROPERTYX, List>>(
HiddenUnitsNotifier.new);

@override
Future<Map<PROPERTYX, List>> build() async {
var prefs = await ref.read(sharedPref.future);

final Map<PROPERTYX, List> newState = {};

for (final property in defaultPropertiesOrder) {
final storedList = prefs.getStringList(_storeKey(property));
final allUnits = defaultUnitsOrder[property]!;
if (storedList == null) {
newState[property] = []; // Default to no hidden units
} else {
final storedOrder = storedList
.map(
(storedString) => allUnits.firstWhereOrNull(
(unit) => storedString == unit.toString(),
),
)
.nonNulls
.cast()
.toList();
newState[property] = storedOrder;
}
}
return newState;
}

void set(List hiddenUnits, PROPERTYX property) async {
// Update the state
final newState = {...state.value!};
newState[property] = hiddenUnits;
state = AsyncData(newState);
// Store the new values
if (hiddenUnits.isEmpty) {
// if there aren't hidden units (all visible), just delete the
// corresponding value from storage
(await ref.read(sharedPref.future)).remove(_storeKey(property));
} else {
(await ref.read(sharedPref.future)).setStringList(
_storeKey(property), hiddenUnits.map((e) => e.toString()).toList());
}
}

String _storeKey(PROPERTYX property) =>
'hiddenUnits_${property.toString().substring('PROPERTYX.'.length)}';
}
Loading

0 comments on commit b8a2ca9

Please sign in to comment.