implemented global user search integration for Talk chats with role-coded badges and direct chat creation logic
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import '../../marianumconnect/queries/user_search/user_search_response.dart';
|
||||
|
||||
/// Demo fixtures for the Talk user search — a small mixed set of teachers and
|
||||
/// students, filtered client-side so the demo search feels responsive.
|
||||
class DemoUsers {
|
||||
const DemoUsers._();
|
||||
|
||||
static final List<McUserSearchResult> _all = [
|
||||
McUserSearchResult(
|
||||
username: 'm.muster',
|
||||
firstName: 'Maria',
|
||||
lastName: 'Mustermann',
|
||||
userType: 'TEACHER',
|
||||
),
|
||||
McUserSearchResult(
|
||||
username: 'j.beispiel',
|
||||
firstName: 'Jonas',
|
||||
lastName: 'Beispiel',
|
||||
userType: 'TEACHER',
|
||||
),
|
||||
McUserSearchResult(
|
||||
username: 'l.schueler',
|
||||
firstName: 'Lena',
|
||||
lastName: 'Schüler',
|
||||
userType: 'STUDENT',
|
||||
className: '9c',
|
||||
),
|
||||
McUserSearchResult(
|
||||
username: 'p.probe',
|
||||
firstName: 'Paul',
|
||||
lastName: 'Probe',
|
||||
userType: 'STUDENT',
|
||||
className: 'Q2',
|
||||
),
|
||||
];
|
||||
|
||||
static List<McUserSearchResult> search(String query) {
|
||||
final q = query.trim().toLowerCase();
|
||||
if (q.length < 2) return const [];
|
||||
return _all
|
||||
.where(
|
||||
(u) =>
|
||||
u.firstName.toLowerCase().contains(q) ||
|
||||
u.lastName.toLowerCase().contains(q) ||
|
||||
u.username.toLowerCase().contains(q) ||
|
||||
'${u.firstName} ${u.lastName}'.toLowerCase().contains(q),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'data/demo_breaker.dart';
|
||||
import 'data/demo_holidays.dart';
|
||||
import 'data/demo_timetable.dart';
|
||||
import 'data/demo_users.dart';
|
||||
|
||||
/// Single source of truth for the MarianumConnect demo responses. The demo
|
||||
/// interceptor asks this for the body of any MC request. Read endpoints reuse
|
||||
@@ -32,6 +33,10 @@ class DemoMarianumConnect {
|
||||
return DemoTimetable.holidays().result.map((e) => e.toJson()).toList();
|
||||
case 'holidays':
|
||||
return DemoHolidays.upcoming().map((e) => e.toJson()).toList();
|
||||
case 'users/search':
|
||||
return DemoUsers.search(query['q']?.toString() ?? '')
|
||||
.map((e) => e.toJson())
|
||||
.toList();
|
||||
case 'breaker':
|
||||
return DemoBreaker.none().toJson();
|
||||
case 'timetable/elements/teachers':
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import '../talk_api.dart';
|
||||
import 'create_room_params.dart';
|
||||
import 'create_room_response.dart';
|
||||
|
||||
class CreateRoom extends TalkApi {
|
||||
class CreateRoom extends TalkApi<CreateRoomResponse> {
|
||||
CreateRoomParams params;
|
||||
|
||||
CreateRoom(this.params) : super('v4/room', params);
|
||||
|
||||
@override
|
||||
Null assemble(String raw) => null;
|
||||
CreateRoomResponse assemble(String raw) {
|
||||
final decoded = jsonDecode(raw) as Map<String, dynamic>;
|
||||
return CreateRoomResponse.fromJson(decoded['ocs'] as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response>? request(
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import '../../../api_response.dart';
|
||||
|
||||
part 'create_room_response.g.dart';
|
||||
|
||||
@JsonSerializable(explicitToJson: true)
|
||||
class CreateRoomResponse extends ApiResponse {
|
||||
final CreateRoomResponseData data;
|
||||
|
||||
CreateRoomResponse(this.data);
|
||||
|
||||
factory CreateRoomResponse.fromJson(Map<String, dynamic> json) =>
|
||||
_$CreateRoomResponseFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$CreateRoomResponseToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class CreateRoomResponseData {
|
||||
final String token;
|
||||
|
||||
CreateRoomResponseData(this.token);
|
||||
|
||||
factory CreateRoomResponseData.fromJson(Map<String, dynamic> json) =>
|
||||
_$CreateRoomResponseDataFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$CreateRoomResponseDataToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'create_room_response.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
CreateRoomResponse _$CreateRoomResponseFromJson(Map<String, dynamic> json) =>
|
||||
CreateRoomResponse(
|
||||
CreateRoomResponseData.fromJson(json['data'] as Map<String, dynamic>),
|
||||
)
|
||||
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$CreateRoomResponseToJson(CreateRoomResponse instance) =>
|
||||
<String, dynamic>{
|
||||
'headers': ?instance.headers,
|
||||
'data': instance.data.toJson(),
|
||||
};
|
||||
|
||||
CreateRoomResponseData _$CreateRoomResponseDataFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => CreateRoomResponseData(json['token'] as String);
|
||||
|
||||
Map<String, dynamic> _$CreateRoomResponseDataToJson(
|
||||
CreateRoomResponseData instance,
|
||||
) => <String, dynamic>{'token': instance.token};
|
||||
@@ -0,0 +1,30 @@
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../../errors/marianumconnect_error.dart';
|
||||
import '../../marianumconnect_api.dart';
|
||||
import '../../marianumconnect_endpoint.dart';
|
||||
import 'user_search_response.dart';
|
||||
|
||||
/// Searches active users (students, teachers, staff) via the MarianumConnect
|
||||
/// mobile API. Returns each match's Nextcloud username plus role, so the Talk
|
||||
/// search can start a direct chat and label results without hitting Nextcloud.
|
||||
class UserSearch {
|
||||
final Dio _dio;
|
||||
|
||||
UserSearch({Dio? dio}) : _dio = dio ?? MarianumConnectApi.dio();
|
||||
|
||||
Future<UserSearchResponse> run(String query) async {
|
||||
try {
|
||||
final response = await _dio.get<List<dynamic>>(
|
||||
MarianumConnectEndpoint.resolve('users/search'),
|
||||
queryParameters: {'q': query},
|
||||
);
|
||||
final list = response.data!
|
||||
.map((e) => McUserSearchResult.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
return UserSearchResponse(result: list);
|
||||
} on DioException catch (e) {
|
||||
throw mapMarianumConnectError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import '../../../api_response.dart';
|
||||
|
||||
part 'user_search_response.g.dart';
|
||||
|
||||
@JsonSerializable(explicitToJson: true)
|
||||
class McUserSearchResult {
|
||||
/// Nextcloud-/Talk-Username — dient als `invite` beim Chat-Start.
|
||||
final String username;
|
||||
final String firstName;
|
||||
final String lastName;
|
||||
|
||||
/// STUDENT, TEACHER oder STAFF.
|
||||
final String userType;
|
||||
final String? className;
|
||||
|
||||
McUserSearchResult({
|
||||
required this.username,
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
required this.userType,
|
||||
this.className,
|
||||
});
|
||||
|
||||
factory McUserSearchResult.fromJson(Map<String, dynamic> json) =>
|
||||
_$McUserSearchResultFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$McUserSearchResultToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable(explicitToJson: true)
|
||||
class UserSearchResponse extends ApiResponse {
|
||||
final List<McUserSearchResult> result;
|
||||
|
||||
UserSearchResponse({required this.result});
|
||||
|
||||
factory UserSearchResponse.fromJson(Map<String, dynamic> json) =>
|
||||
_$UserSearchResponseFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$UserSearchResponseToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'user_search_response.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
McUserSearchResult _$McUserSearchResultFromJson(Map<String, dynamic> json) =>
|
||||
McUserSearchResult(
|
||||
username: json['username'] as String,
|
||||
firstName: json['firstName'] as String,
|
||||
lastName: json['lastName'] as String,
|
||||
userType: json['userType'] as String,
|
||||
className: json['className'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$McUserSearchResultToJson(McUserSearchResult instance) =>
|
||||
<String, dynamic>{
|
||||
'username': instance.username,
|
||||
'firstName': instance.firstName,
|
||||
'lastName': instance.lastName,
|
||||
'userType': instance.userType,
|
||||
'className': instance.className,
|
||||
};
|
||||
|
||||
UserSearchResponse _$UserSearchResponseFromJson(Map<String, dynamic> json) =>
|
||||
UserSearchResponse(
|
||||
result: (json['result'] as List<dynamic>)
|
||||
.map((e) => McUserSearchResult.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
)
|
||||
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$UserSearchResponseToJson(UserSearchResponse instance) =>
|
||||
<String, dynamic>{
|
||||
'headers': ?instance.headers,
|
||||
'result': instance.result.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
Reference in New Issue
Block a user