Skip to content

Commit

Permalink
Исправление бага #268 с историей поиска в расписании (#357)
Browse files Browse the repository at this point in the history
Co-authored-by: UNN MOBILE runner <unnmobile@mail.ru>
  • Loading branch information
KriseevM and BitCodersNNBot authored Aug 31, 2024
1 parent c0034e7 commit abbd377
Show file tree
Hide file tree
Showing 11 changed files with 142 additions and 114 deletions.
11 changes: 0 additions & 11 deletions lib/core/models/schedule_search_result_item.dart

This file was deleted.

40 changes: 40 additions & 0 deletions lib/core/models/schedule_search_suggestion_item.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
class _ScheduleSearchSuggestionItemKeys {
static const String id = 'id';
static const String label = 'label';
static const String description = 'description';
}

class ScheduleSearchSuggestionItem {
final String _id;
final String _label;
final String _description;

const ScheduleSearchSuggestionItem(this._id, this._label, this._description);

String get id => _id;
String get label => _label;
String get description => _description;

@override
bool operator ==(other) =>
other is ScheduleSearchSuggestionItem && (_id == other._id);

@override
int get hashCode => Object.hash(_id, _label, _description);

factory ScheduleSearchSuggestionItem.fromJson(Map<String, Object?> jsonMap) {
return ScheduleSearchSuggestionItem(
jsonMap[_ScheduleSearchSuggestionItemKeys.id] as String,
jsonMap[_ScheduleSearchSuggestionItemKeys.label] as String,
jsonMap[_ScheduleSearchSuggestionItemKeys.description] as String,
);
}

Map<String, Object?> toJson() {
return {
_ScheduleSearchSuggestionItemKeys.id: _id,
_ScheduleSearchSuggestionItemKeys.label: _label,
_ScheduleSearchSuggestionItemKeys.description: _description,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import 'dart:async';
import 'dart:collection';
import 'dart:convert';

import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:injector/injector.dart';
import 'package:unn_mobile/core/models/schedule_filter.dart';
import 'package:unn_mobile/core/models/schedule_search_suggestion_item.dart';
import 'package:unn_mobile/core/services/interfaces/schedule_search_history_service.dart';
import 'package:unn_mobile/core/services/interfaces/storage_service.dart';

class ScheduleSearchHistoryServiceImpl implements ScheduleSearchHistoryService {
final Map<IDType, Queue<String>> _historyQueues = {};
final Map<IDType, Queue<ScheduleSearchSuggestionItem>> _historyQueues = {};
final StorageService _storage = Injector.appInstance.get<StorageService>();
final _storageKeySuffix = 'ScheduleSearchHistory';
final _maxHistoryItems = 5;
Expand All @@ -18,43 +20,56 @@ class ScheduleSearchHistoryServiceImpl implements ScheduleSearchHistoryService {

Future<void> _initFromStorage() async {
for (final type in IDType.values) {
final key = type.name + _storageKeySuffix;
if (!(await _storage.containsKey(key: key))) {
final key = _getStorageKeyForType(type);
try {
final Iterable rawHistory =
jsonDecode((await _storage.read(key: key))!);
_historyQueues.putIfAbsent(
type,
() => Queue.from(
rawHistory.map(
(h) => ScheduleSearchSuggestionItem.fromJson(h),
),
),
);
} catch (e, stack) {
FirebaseCrashlytics.instance.recordError(e, stack);
} finally {
// Если что угодно пошло не так
// (нет ключа в хранилище или там невалидные данные)
// Просто добавляем пустую очередь
_historyQueues.putIfAbsent(type, () => Queue());
continue;
}
final Iterable rawHistory = jsonDecode((await _storage.read(key: key))!);
_historyQueues.putIfAbsent(
type,
() => Queue.from(rawHistory.map((e) => e.toString())),
);
}

_isInitialized = true;
}

String _getStorageKeyForType(IDType type) => type.name + _storageKeySuffix;

Future<void> _initIfNeeded() async {
if (_isInitialized) return;
await _initFromStorage();
}

@override
Future<List<String>> getHistory(IDType type) async {
Future<List<ScheduleSearchSuggestionItem>> getHistory(IDType type) async {
await _initIfNeeded();
return _historyQueues[type]!.toList(growable: false);
}

@override
FutureOr<void> pushToHistory({
required IDType type,
required String value,
}) async {
_historyQueues[type]!.remove(value);
_historyQueues[type]!.addFirst(value);
FutureOr<void> pushToHistory(
IDType type,
ScheduleSearchSuggestionItem item,
) async {
_historyQueues[type]!.remove(item);
_historyQueues[type]!.addFirst(item);
if (_historyQueues[type]!.length > _maxHistoryItems) {
_historyQueues[type]!.removeLast();
}
await _storage.write(
key: type.name + _storageKeySuffix,
key: _getStorageKeyForType(type),
value: jsonEncode(await getHistory(type)),
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'package:injector/injector.dart';
import 'package:unn_mobile/core/constants/api_url_strings.dart';
import 'package:unn_mobile/core/misc/http_helper.dart';
import 'package:unn_mobile/core/models/employee_data.dart';
import 'package:unn_mobile/core/models/schedule_search_result_item.dart';
import 'package:unn_mobile/core/models/schedule_search_suggestion_item.dart';
import 'package:unn_mobile/core/models/schedule_filter.dart';
import 'package:unn_mobile/core/models/student_data.dart';
import 'package:unn_mobile/core/services/interfaces/getting_profile_of_current_user_service.dart';
Expand Down Expand Up @@ -72,7 +72,7 @@ class SearchIdOnPortalServiceImpl implements SearchIdOnPortalService {
}

@override
Future<List<ScheduleSearchResultItem>?> findIDOnPortal(
Future<List<ScheduleSearchSuggestionItem>?> findIDOnPortal(
String value,
IDType valueType,
) async {
Expand Down Expand Up @@ -100,10 +100,10 @@ class SearchIdOnPortalServiceImpl implements SearchIdOnPortalService {
final List<dynamic> jsonList =
jsonDecode(await HttpRequestSender.responseToStringBody(response));

final List<ScheduleSearchResultItem> result = [];
final List<ScheduleSearchSuggestionItem> result = [];
for (final jsonMap in jsonList) {
result.add(
ScheduleSearchResultItem(
ScheduleSearchSuggestionItem(
jsonMap[_id],
jsonMap[_label],
jsonMap[_description],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import 'dart:async';

import 'package:unn_mobile/core/models/schedule_filter.dart';
import 'package:unn_mobile/core/models/schedule_search_suggestion_item.dart';

abstract interface class ScheduleSearchHistoryService {
FutureOr<List<String>> getHistory(IDType type);
FutureOr<void> pushToHistory({required IDType type, required String value});
FutureOr<List<ScheduleSearchSuggestionItem>> getHistory(IDType type);
FutureOr<void> pushToHistory(IDType type, ScheduleSearchSuggestionItem item);
}
6 changes: 3 additions & 3 deletions lib/core/services/interfaces/search_id_on_portal_service.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'package:unn_mobile/core/models/schedule_search_result_item.dart';
import 'package:unn_mobile/core/models/schedule_search_suggestion_item.dart';
import 'package:unn_mobile/core/models/schedule_filter.dart';

abstract interface class SearchIdOnPortalService {
Expand All @@ -12,8 +12,8 @@ abstract interface class SearchIdOnPortalService {
/// [value]: Значение, по которому ищется ID
/// [valueType]: Тип значения: группа или ФИО студента, или ФИО преподователя, или аудитория
///
/// Возвращает [ScheduleSearchResultItem] или 'null', если не вышло получить ответ от портала или statusCode не равен 200
Future<List<ScheduleSearchResultItem>?> findIDOnPortal(
/// Возвращает [ScheduleSearchSuggestionItem] или 'null', если не вышло получить ответ от портала или statusCode не равен 200
Future<List<ScheduleSearchSuggestionItem>?> findIDOnPortal(
String value,
IDType valueType,
);
Expand Down
17 changes: 9 additions & 8 deletions lib/core/viewmodels/schedule_screen_view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'package:unn_mobile/core/misc/date_time_ranges.dart';
import 'package:unn_mobile/core/misc/try_login_and_retrieve_data.dart';
import 'package:unn_mobile/core/models/online_status_data.dart';
import 'package:unn_mobile/core/models/schedule_filter.dart';
import 'package:unn_mobile/core/models/schedule_search_result_item.dart';
import 'package:unn_mobile/core/models/schedule_search_suggestion_item.dart';
import 'package:unn_mobile/core/models/student_data.dart';
import 'package:unn_mobile/core/models/subject.dart';
import 'package:unn_mobile/core/services/interfaces/export_schedule_service.dart';
Expand Down Expand Up @@ -230,8 +230,6 @@ class ScheduleScreenViewModel extends BaseViewModel {
if (searchResult == null) {
throw Exception('Schedule search result was null');
}

addHistoryItem(query);
_filter = ScheduleFilter(_idType, searchResult[0].id, displayedWeek);
} else {
_filter = ScheduleFilter(_idType, _currentId, displayedWeek);
Expand All @@ -245,21 +243,24 @@ class ScheduleScreenViewModel extends BaseViewModel {
notifyListeners();
}

FutureOr<void> addHistoryItem(String query) =>
_historyService.pushToHistory(type: _idType, value: query);
FutureOr<void> addHistoryItem(ScheduleSearchSuggestionItem item) =>
_historyService.pushToHistory(_idType, item);

Future<List<ScheduleSearchResultItem>> getSearchSuggestions(
Future<List<ScheduleSearchSuggestionItem>> getSearchSuggestions(
String value,
) async {
if (value.isEmpty) {
return await _getHistorySuggestions();
}
final suggestions = await tryLoginAndRetrieveData(
() async => await _searchIdOnPortalService.findIDOnPortal(value, _idType),
() async => <ScheduleSearchResultItem>[],
() async => <ScheduleSearchSuggestionItem>[],
);

return suggestions;
}

Future<List<String>> getHistorySuggestions() async {
Future<List<ScheduleSearchSuggestionItem>> _getHistorySuggestions() async {
return await _historyService.getHistory(_idType);
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import 'package:flutter/material.dart';
import 'package:unn_mobile/core/models/schedule_search_suggestion_item.dart';

class ScheduleSearchSuggestionItemView extends StatelessWidget {
final ScheduleSearchSuggestionItem model;

final void Function() onSelected;
const ScheduleSearchSuggestionItemView({
super.key,
required this.model,
required this.onSelected,
});

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return ListTile(
shape: const Border(bottom: BorderSide(color: Colors.black12)),
visualDensity: VisualDensity.compact,
title: Text(model.label),
subtitle: Text(
model.description,
style: theme.textTheme.labelSmall,
),
onTap: onSelected,
);
}
}
58 changes: 21 additions & 37 deletions lib/ui/views/main_page/schedule/widgets/schedule_tab.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import 'package:unn_mobile/core/viewmodels/base_view_model.dart';
import 'package:unn_mobile/core/viewmodels/schedule_screen_view_model.dart';
import 'package:unn_mobile/ui/views/base_view.dart';
import 'package:unn_mobile/ui/views/main_page/schedule/widgets/schedule_item_normal.dart';
import 'package:unn_mobile/ui/views/main_page/schedule/widgets/schedule_search_suggestion_item.dart';
import 'package:unn_mobile/ui/views/main_page/schedule/widgets/schedule_search_suggestion_item_view.dart';
import 'package:flutter_changed/search_anchor.dart' as flutter_changed;
import 'package:unn_mobile/ui/widgets/dialogs/message_dialog.dart';
import 'package:unn_mobile/ui/widgets/dialogs/radio_group_dialog.dart';
Expand Down Expand Up @@ -248,42 +248,26 @@ class ScheduleTabState extends State<ScheduleTab>
final rawSuggestions = await model.getSearchSuggestions(
controller.text,
); // Неэффективно, но работает >:(
if (controller.text == '') {
final suggestions = await model.getHistorySuggestions();
return suggestions.map(
(e) => ScheduleSearchSuggestionItem(
itemName: e,
onSelected: () async {
controller.closeView(e);
if (model.lastSearchQuery != e) {
model.lastSearchQuery = e;
await model.addHistoryItem(e);
await model.submitSearch(e);
}
},
),
);
} else {
return rawSuggestions.map<ScheduleSearchSuggestionItem>(
(e) => ScheduleSearchSuggestionItem(
itemName: e.label,
itemDescription: e.description,
onSelected: () {
controller.closeView(e.label);
Future.delayed(
const Duration(milliseconds: 50),
() {
SystemChannels.textInput.invokeMethod('TextInput.hide');
},
);
model.lastSearchQuery = controller.text;
model.addHistoryItem(e.label);
model.selectedId = e.id;
model.updateFilter(e.id);
},
),
);
}

return rawSuggestions.map<ScheduleSearchSuggestionItemView>(
(e) => ScheduleSearchSuggestionItemView(
model: e,
onSelected: () {
// controller.text = e.label;
controller.closeView(e.label);
Future.delayed(
const Duration(milliseconds: 50),
() {
SystemChannels.textInput.invokeMethod('TextInput.hide');
},
);
model.lastSearchQuery = controller.text;
model.addHistoryItem(e);
model.selectedId = e.id;
model.updateFilter(e.id);
},
),
);
},
),
);
Expand Down
3 changes: 2 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: unn_mobile
description: A mobile application for UNN Portal website
publish_to: 'none'

version: 0.2.2+198
version: 0.2.2+199

environment:
sdk: '>=3.1.2 <4.0.0'
Expand Down Expand Up @@ -78,3 +78,4 @@ flutter_launcher_icons:
image_path: "assets/images/icon.png"
min_sdk_android: 19
remove_alpha_ios: true

0 comments on commit abbd377

Please sign in to comment.