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

feat: 5568 - optimized search for price locations #5587

Merged
merged 6 commits into from
Sep 25, 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
21 changes: 17 additions & 4 deletions packages/smooth_app/lib/data_models/location_list_supplier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,22 @@ import 'package:smooth_app/query/product_query.dart';
class LocationListSupplier {
LocationListSupplier(
this.query,
this.optimizedSearch,
);

/// Query text.
final String query;

/// True if we want to focus on shops.
final bool optimizedSearch;

/// Locations as result.
final List<OsmLocation> locations = <OsmLocation>[];

/// Returns additional query parameters.
String _getAdditionalParameters() =>
optimizedSearch ? '&osm_tag=shop&osm_tag=amenity' : '';

/// Returns null if OK, or the message error
Future<String?> asyncLoad() async {
// don't ask me why, but it looks like we need to explicitly set a language,
Expand All @@ -35,10 +45,9 @@ class LocationListSupplier {
scheme: 'https',
host: 'photon.komoot.io',
path: 'api',
queryParameters: <String, String>{
'q': query,
'lang': getQueryLanguage().offTag,
},
query: 'q=${Uri.encodeComponent(query)}'
'&lang=${getQueryLanguage().offTag}'
'${_getAdditionalParameters()}',
),
);
if (response.statusCode != 200) {
Expand Down Expand Up @@ -70,6 +79,8 @@ class LocationListSupplier {
final String? countryCode = properties['countrycode'];
final String? country = properties['country'];
final String? postCode = properties['postcode'];
final String? osmKey = properties['osm_key'];
final String? osmValue = properties['osm_value'];
final OsmLocation osmLocation = OsmLocation(
osmId: osmId,
osmType: osmType,
Expand All @@ -81,6 +92,8 @@ class LocationListSupplier {
street: street,
country: country,
countryCode: countryCode,
osmKey: osmKey,
osmValue: osmValue,
);
locations.add(osmLocation);
}
Expand Down
47 changes: 33 additions & 14 deletions packages/smooth_app/lib/data_models/location_query_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ import 'package:smooth_app/pages/locations/osm_location.dart';
import 'package:smooth_app/pages/product/common/loading_status.dart';

/// Location query model.
///
/// We use 2 location suppliers:
/// * the first one optimized on shops, as it's what we want
/// * an optional one with no restrictions, in case OSM data is a bit clumsy
class LocationQueryModel with ChangeNotifier {
LocationQueryModel(this.query) {
_asyncLoad(notify: true);
_asyncLoad(_supplierOptimized);
}

final String query;
Expand All @@ -15,39 +19,54 @@ class LocationQueryModel with ChangeNotifier {
String? _loadingError;
List<OsmLocation> displayedResults = <OsmLocation>[];

bool _isOptimized = true;
bool get isOptimized => _isOptimized;

bool isEmpty() => displayedResults.isEmpty;

String? get loadingError => _loadingError;
LoadingStatus get loadingStatus => _loadingStatus;

late final LocationListSupplier supplier = LocationListSupplier(query);
/// A location supplier focused on shops.
late final LocationListSupplier _supplierOptimized =
LocationListSupplier(query, true);

/// A location supplier without restrictions.
late final LocationListSupplier _supplierBroader =
LocationListSupplier(query, false);

Future<bool> _asyncLoad({
final bool notify = false,
final bool fromScratch = false,
}) async {
Future<bool> _asyncLoad(final LocationListSupplier supplier) async {
_loadingStatus = LoadingStatus.LOADING;
notifyListeners();
_loadingError = await supplier.asyncLoad();
if (_loadingError != null) {
_loadingStatus = LoadingStatus.ERROR;
} else {
await _process(supplier.locations, fromScratch);
await _process(supplier.locations);
_loadingStatus = LoadingStatus.LOADED;
}
if (notify) {
notifyListeners();
}
notifyListeners();
return _loadingStatus == LoadingStatus.LOADED;
}

final Set<String> _locationKeys = <String>{};

Future<void> _process(
final List<OsmLocation> locations,
final bool fromScratch,
) async {
if (fromScratch) {
displayedResults.clear();
for (final OsmLocation location in locations) {
final String primaryKey = location.primaryKey;
if (_locationKeys.contains(primaryKey)) {
continue;
}
displayedResults.add(location);
_locationKeys.add(primaryKey);
}
displayedResults.addAll(locations);
_loadingStatus = LoadingStatus.LOADED;
}

Future<void> loadMore() async {
_isOptimized = false;
_asyncLoad(_supplierBroader);
}
}
12 changes: 12 additions & 0 deletions packages/smooth_app/lib/database/dao_osm_location.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class DaoOsmLocation extends AbstractSqlDao {
static const String _columnPostCode = 'post_code';
static const String _columnCountry = 'country';
static const String _columnCountryCode = 'country_code';
static const String _columnOsmKey = 'osm_key';
static const String _columnOsmValue = 'osm_value';
static const String _columnLastAccess = 'last_access';

static const List<String> _columns = <String>[
Expand All @@ -33,6 +35,8 @@ class DaoOsmLocation extends AbstractSqlDao {
_columnPostCode,
_columnCountry,
_columnCountryCode,
_columnOsmKey,
_columnOsmValue,
_columnLastAccess,
];

Expand All @@ -58,6 +62,10 @@ class DaoOsmLocation extends AbstractSqlDao {
',PRIMARY KEY($_columnId,$_columnType) on conflict replace'
')');
}
if (oldVersion < 7) {
await db.execute('alter table $_table add column $_columnOsmKey TEXT');
await db.execute('alter table $_table add column $_columnOsmValue TEXT');
}
}

/// Deletes the [OsmLocation] that matches the key.
Expand Down Expand Up @@ -100,6 +108,8 @@ class DaoOsmLocation extends AbstractSqlDao {
_columnPostCode: osmLocation.postcode,
_columnCountry: osmLocation.country,
_columnCountryCode: osmLocation.countryCode,
_columnOsmKey: osmLocation.osmKey,
_columnOsmValue: osmLocation.osmValue,
_columnLastAccess: LocalDatabase.nowInMillis(),
},
);
Expand All @@ -122,6 +132,8 @@ class DaoOsmLocation extends AbstractSqlDao {
postcode: row[_columnPostCode] as String?,
country: row[_columnCountry] as String?,
countryCode: row[_columnCountryCode] as String?,
osmKey: row[_columnOsmKey] as String?,
osmValue: row[_columnOsmValue] as String?,
);
}
}
2 changes: 1 addition & 1 deletion packages/smooth_app/lib/database/local_database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class LocalDatabase extends ChangeNotifier {
final String databasePath = join(databasesRootPath, 'smoothie.db');
final Database database = await openDatabase(
databasePath,
version: 6,
version: 7,
singleInstance: true,
onUpgrade: _onUpgrade,
);
Expand Down
1 change: 1 addition & 0 deletions packages/smooth_app/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -1911,6 +1911,7 @@
"prices_location_subtitle": "Shop",
"prices_location_find": "Find a shop",
"prices_location_mandatory": "You need to select a shop!",
"prices_location_search_broader": "Couldn't find what you were looking for? Let's try a broader search!",
"prices_proof_subtitle": "Proof",
"prices_proof_find": "Select a proof",
"prices_proof_receipt": "Receipt",
Expand Down
1 change: 1 addition & 0 deletions packages/smooth_app/lib/l10n/app_fr.arb
Original file line number Diff line number Diff line change
Expand Up @@ -1899,6 +1899,7 @@
"prices_location_subtitle": "Magasin",
"prices_location_find": "Chercher un magasin",
"prices_location_mandatory": "Vous devez choisir un magasin !",
"prices_location_search_broader": "Vous n'avez pas trouvé ce que vous cherchiez ? Essayons une recherche plus large !",
"prices_proof_subtitle": "Preuve",
"prices_proof_find": "Choisir une preuve",
"prices_proof_receipt": "Ticket de caisse",
Expand Down
39 changes: 32 additions & 7 deletions packages/smooth_app/lib/pages/locations/location_query_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:matomo_tracker/matomo_tracker.dart';
import 'package:provider/provider.dart';
import 'package:smooth_app/data_models/location_query_model.dart';
import 'package:smooth_app/generic_lib/buttons/smooth_large_button_with_icon.dart';
import 'package:smooth_app/generic_lib/design_constants.dart';
import 'package:smooth_app/generic_lib/widgets/smooth_back_button.dart';
import 'package:smooth_app/generic_lib/widgets/smooth_card.dart';
import 'package:smooth_app/generic_lib/widgets/smooth_error_card.dart';
import 'package:smooth_app/pages/locations/search_location_preloaded_item.dart';
import 'package:smooth_app/pages/product/common/loading_status.dart';
Expand Down Expand Up @@ -67,7 +69,7 @@ class _LocationQueryPageState extends State<LocationQueryPage>
}
break;
case LoadingStatus.LOADED:
if (_model.isEmpty()) {
if (_model.isEmpty() && !_model.isOptimized) {
return SearchEmptyScreen(
name: widget.query,
emptiness: _getEmptyText(
Expand Down Expand Up @@ -114,12 +116,35 @@ class _LocationQueryPageState extends State<LocationQueryPage>
textColor: Theme.of(context).colorScheme.onSurface,
),
child: ListView.builder(
itemBuilder: (BuildContext context, int index) =>
SearchLocationPreloadedItem(
_model.displayedResults[index],
popFirst: true,
).getWidget(context),
itemCount: _model.displayedResults.length,
itemBuilder: (BuildContext context, int index) {
if (index >= _model.displayedResults.length) {
if (_model.isOptimized) {
return SmoothCard(
child: SmoothLargeButtonWithIcon(
text: appLocalizations.prices_location_search_broader,
icon: Icons.search,
onPressed: () => _model.loadMore(),
),
);
}
return const Padding(
padding: EdgeInsets.only(top: SMALL_SPACE),
child: Center(
child: CircularProgressIndicator.adaptive(),
),
);
}
return SearchLocationPreloadedItem(
_model.displayedResults[index],
popFirst: true,
).getWidget(context);
},
itemCount: _model.displayedResults.length +
(_model.isOptimized
? 1
: _model.loadingStatus == LoadingStatus.LOADING
? 1
: 0),
),
),
);
Expand Down
12 changes: 12 additions & 0 deletions packages/smooth_app/lib/pages/locations/osm_location.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class OsmLocation {
this.postcode,
this.country,
this.countryCode,
this.osmKey,
this.osmValue,
});

final int osmId;
Expand All @@ -26,6 +28,8 @@ class OsmLocation {
final String? postcode;
final String? country;
final String? countryCode;
final String? osmKey;
final String? osmValue;

LatLng getLatLng() => LatLng(latitude, longitude);

Expand Down Expand Up @@ -65,9 +69,17 @@ class OsmLocation {
}
result.write(country);
}
if (osmKey != null && osmValue != null) {
if (result.isNotEmpty) {
result.write(', ');
}
result.write('$osmKey:$osmValue');
}
if (result.isEmpty) {
return null;
}
return result.toString();
}

String get primaryKey => '${osmType.offTag}$osmId';
}
Loading