Skip to content

Commit

Permalink
Search implementation (#132)
Browse files Browse the repository at this point in the history
* search implementation

* Apply code formatting

* Fixed revisions

* Fixed revisions

* Fixed revisions

* Fixed revisions
  • Loading branch information
NdegwaJulius authored Sep 17, 2024
1 parent 3f3895e commit 956a4f6
Show file tree
Hide file tree
Showing 10 changed files with 354 additions and 3 deletions.
6 changes: 6 additions & 0 deletions lib/bootstrap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import 'package:fluttercon/features/home/cubit/home_cubits.dart';
import 'package:fluttercon/features/sessions/cubit/bookmark_session_cubit.dart';
import 'package:fluttercon/features/sessions/cubit/fetch_grouped_sessions_cubit.dart';
import 'package:fluttercon/firebase_options.dart';
import 'package:fluttercon/search/cubit/search_cubit.dart';

class AppBlocObserver extends BlocObserver {
const AppBlocObserver();
Expand Down Expand Up @@ -146,6 +147,11 @@ Future<void> bootstrap(FutureOr<Widget> Function() builder) async {
hiveRepository: getIt(),
),
),
BlocProvider<SearchCubit>(
create: (context) => SearchCubit(
dbRepository: getIt(),
),
),
],
child: await builder(),
),
Expand Down
1 change: 1 addition & 0 deletions lib/common/data/enums/search_result_type.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
enum SearchResultType { session, speaker, organizer }
21 changes: 21 additions & 0 deletions lib/common/data/models/search_result.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:fluttercon/common/data/enums/search_result_type.dart';
import 'package:fluttercon/common/data/models/local/local_individual_organiser.dart';
import 'package:fluttercon/common/data/models/local/local_session.dart';
import 'package:fluttercon/common/data/models/local/local_speaker.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'search_result.freezed.dart';

@freezed
class SearchResult with _$SearchResult {
const factory SearchResult({
required String id,
required String title,
required String subtitle,
required String imageUrl,
required SearchResultType type,
LocalSession? session,
LocalSpeaker? speaker,
LocalIndividualOrganiser? organizer,
}) = _SearchResult;
}
38 changes: 38 additions & 0 deletions lib/common/repository/db_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -281,4 +281,42 @@ class DBRepository {
Future<LocalSession?> getSession(int sessionId) async {
return localDB.localSessions.where().serverIdEqualTo(sessionId).findFirst();
}

Future<List<LocalSession>> searchSessions(String query) async {
return localDB.localSessions
.where()
.filter()
.titleContains(query, caseSensitive: false)
.or()
.descriptionContains(query, caseSensitive: false)
.findAll();
}

Future<List<LocalSpeaker>> searchSpeakers(String query) async {
return localDB.localSpeakers
.where()
.filter()
.nameContains(query, caseSensitive: false)
.or()
.biographyContains(query, caseSensitive: false)
.or()
.taglineContains(query, caseSensitive: false)
.findAll();
}

Future<List<LocalIndividualOrganiser>> searchIndividualOrganisers(
String query,
) async {
return localDB.localIndividualOrganisers
.where()
.filter()
.nameContains(query, caseSensitive: false)
.or()
.taglineContains(query, caseSensitive: false)
.or()
.bioContains(query, caseSensitive: false)
.or()
.designationContains(query, caseSensitive: false)
.findAll();
}
}
6 changes: 4 additions & 2 deletions lib/features/home/ui/home_screen.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:fluttercon/common/utils/constants/app_assets.dart';
import 'package:fluttercon/common/utils/misc.dart';
Expand All @@ -8,6 +7,7 @@ import 'package:fluttercon/features/home/widgets/sessions_card.dart';
import 'package:fluttercon/features/home/widgets/speaker_home_card.dart';
import 'package:fluttercon/features/home/widgets/sponsors_card.dart';
import 'package:fluttercon/l10n/l10n.dart';
import 'package:fluttercon/search/ui/widgets/search_bar.dart';

class HomeScreen extends StatefulWidget {
const HomeScreen({super.key, this.switchTab});
Expand All @@ -33,7 +33,7 @@ class _HomeScreenState extends State<HomeScreen> {
const SizedBox(height: 15),
Align(
alignment: Alignment.centerLeft,
child: AutoSizeText(
child: Text(
l10n.welcomeToFlutterCon,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
Expand All @@ -43,6 +43,8 @@ class _HomeScreenState extends State<HomeScreen> {
),
),
const SizedBox(height: 15),
const SearchBarWidget(),
const SizedBox(height: 15),
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Image.asset(AppAssets.droidconBanner),
Expand Down
4 changes: 4 additions & 0 deletions lib/l10n/arb/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@
"@sponsors": {},
"sessionDetails": "Session Details",
"@sessionDetails": {},
"searchHint": "Search sessions or speakers",
"@searchHint": {},
"errorSearch": "No results found",
"@errorSearch": {},
"day": "Day {day}",
"@day": {
"placeholders": {
Expand Down
69 changes: 69 additions & 0 deletions lib/search/cubit/search_cubit.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluttercon/common/data/enums/search_result_type.dart';
import 'package:fluttercon/common/data/models/failure.dart';
import 'package:fluttercon/common/data/models/search_result.dart';
import 'package:fluttercon/common/repository/db_repository.dart';
import 'package:fluttercon/search/cubit/search_state.dart';

class SearchCubit extends Cubit<SearchState> {
SearchCubit({
required DBRepository dbRepository,
}) : super(const SearchState.initial()) {
_dbRepository = dbRepository;
}

late DBRepository _dbRepository;

Future<void> search(String query) async {
emit(const SearchState.loading());
try {
final sessions = await _dbRepository.searchSessions(query);
final speakers = await _dbRepository.searchSpeakers(query);
final individualOrganizers =
await _dbRepository.searchIndividualOrganisers(query);

final results = [
...sessions.map(
(session) => SearchResult(
id: session.serverId.toString(),
title: session.title,
subtitle: 'Session',
imageUrl: session.sessionImage,
type: SearchResultType.session,
session: session,
),
),
...speakers.map(
(speaker) => SearchResult(
id: speaker.id.toString(),
title: speaker.name,
subtitle: speaker.tagline ?? '',
imageUrl: speaker.avatar,
type: SearchResultType.speaker,
speaker: speaker,
),
),
...individualOrganizers.map(
(organizer) => SearchResult(
id: organizer.id.toString(),
title: organizer.name,
subtitle: 'Organizer',
imageUrl: organizer.photo,
type: SearchResultType.organizer,
organizer: organizer,
),
),
];

emit(SearchState.loaded(results: results));
} on Failure catch (e) {
emit(SearchState.error(message: e.message));
} catch (e) {
emit(SearchState.error(message: e.toString()));
}
}

void clearSearch() {
emit(const SearchState.initial());
}
}
12 changes: 12 additions & 0 deletions lib/search/cubit/search_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'package:fluttercon/common/data/models/search_result.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'search_state.freezed.dart';

@freezed
class SearchState with _$SearchState {
const factory SearchState.initial() = SearchInitial;
const factory SearchState.loading() = SearchLoading;
const factory SearchState.loaded({required List<SearchResult> results}) =
SearchLoaded;
const factory SearchState.error({required String message}) = SearchError;
}
Loading

0 comments on commit 956a4f6

Please sign in to comment.