didvan-app/lib/views/mentions/mentions_state.dart

354 lines
11 KiB
Dart
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'dart:convert';
import 'dart:developer' as dev;
// ignore: depend_on_referenced_packages
import 'package:http/http.dart' as http;
import 'package:didvan/config/auth_config.dart';
import 'package:didvan/config/design_config.dart';
import 'package:didvan/models/comment/user.dart';
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/mention/mention.dart';
import 'package:didvan/models/users_mention.dart';
import 'package:didvan/providers/core.dart';
import 'package:didvan/providers/user.dart';
import 'package:didvan/services/auth/auth_interceptor.dart';
import 'package:didvan/services/auth/token_storage.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart';
class MentionsState extends CoreProvier {
TextEditingController textFieldController = TextEditingController();
TextEditingController searchUsers = TextEditingController();
List<UsersMention> mentionedUsers = [];
int? commentId;
UserOverview? replyingTo;
bool showReplyBox = false;
bool showUsersForMentionsLayout = false;
bool showPrivates = false;
bool hideMentionedUser = false;
late String type;
final List<MentionData> comments = [];
final List<UsersMention> users = [];
int itemId = 0;
// UUID محتوای جدید (در صورت وجود) — برای فراخوانی content-service.
String? contentUuid;
// نگاشت UsersMention.id (hashCode) → keycloak UUID. هنگام ارسال
// mentionها، از این نگاشت استفاده می‌کنیم تا UUID واقعی فرستاده شود.
final Map<int, String> _userUuidById = {};
Future<void> getComments() async {
if (!AuthConfig.useLegacyApi) {
await _getCommentsFromCommentsService();
return;
}
final service = RequestService(
RequestHelper.mention(itemId, type),
);
await service.httpGet();
if (service.isSuccess) {
comments.clear();
final List<dynamic> messages = service.result['comments'];
for (var i = 0; i < messages.length; i++) {
comments.add(MentionData.fromJson(messages[i]));
}
appState = AppState.idle;
return;
}
appState = AppState.failed;
}
Future<void> _getCommentsFromCommentsService() async {
try {
await AuthInterceptor.ensureFreshToken();
final token = TokenStorage.accessToken;
Uri uri;
if (contentUuid != null && contentUuid!.isNotEmpty) {
final qp = <String, String>{
'filter.contentId': '\$eq:$contentUuid',
'sortBy': 'createdAt:DESC',
'limit': '50',
};
uri = Uri.parse('${AuthConfig.apiBaseUrl}/comments')
.replace(queryParameters: qp);
} else {
uri = Uri.parse(
'${AuthConfig.apiBaseUrl}/comments/me/mentions?limit=50',
);
}
final res = await http.get(uri, headers: {
'Accept': 'application/json',
if (token != null && token.isNotEmpty)
'Authorization': 'Bearer $token',
}).timeout(const Duration(seconds: 15));
if (res.statusCode != 200) {
appState = AppState.failed;
notifyListeners();
return;
}
final body = json.decode(res.body);
final List dataRaw = body is Map && body['data'] is List
? body['data'] as List
: (body is List ? body : const []);
comments.clear();
for (final c in dataRaw.whereType<Map>()) {
final map = Map<String, dynamic>.from(c);
comments.add(_mentionFromCommentJson(map));
}
appState = AppState.idle;
notifyListeners();
} catch (e) {
dev.log('getComments(new) error: $e', name: 'MentionsState');
appState = AppState.failed;
notifyListeners();
}
}
MentionData _mentionFromCommentJson(Map<String, dynamic> json) {
final user = json['user'] is Map
? Map<String, dynamic>.from(json['user'] as Map)
: <String, dynamic>{};
final fullName = [
user['firstName']?.toString() ?? '',
user['lastName']?.toString() ?? '',
].where((s) => s.isNotEmpty).join(' ').trim();
final mentionsRaw = json['mentions'];
final mentions = mentionsRaw is List
? mentionsRaw.map((e) => e.toString()).toList()
: <String>[];
return MentionData(
id: (json['id']?.toString() ?? '').hashCode,
text: (json['body'] ?? '').toString(),
createdAt: (json['createdAt'] ?? DateTime.now().toIso8601String())
.toString(),
mentions: mentions,
user: UserOverview(
id: (json['userId']?.toString() ?? '').hashCode,
fullName: fullName.isEmpty ? 'کاربر' : fullName,
photo: user['avatar']?.toString(),
),
);
}
Future<void> getUsersMention(int id) async {
if (!AuthConfig.useLegacyApi) {
await _searchUsersFromAuth(id);
return;
}
final service = RequestService(
RequestHelper.usersMentions(searchUsers.text),
);
await service.httpGet();
if (service.isSuccess) {
users.clear();
final List<dynamic> resUsers = service.data('users');
users.addAll(mentionedUsers);
for (var i = 0; i < resUsers.length; i++) {
final user = UsersMention.fromJson(resUsers[i]);
if (!mentionedUsers.map((e) => e.id).contains(user.id) &&
user.id != id) {
users.add(user);
}
}
appState = AppState.idle;
return;
}
appState = AppState.failed;
}
Future<void> _searchUsersFromAuth(int currentUserHashId) async {
try {
await AuthInterceptor.ensureFreshToken();
final token = TokenStorage.accessToken;
final q = searchUsers.text.trim();
final params = <String, String>{
'limit': '20',
if (q.isNotEmpty) 'search': q,
};
final uri = Uri.parse('${AuthConfig.apiBaseUrl}/comments/users/search')
.replace(queryParameters: params);
final res = await http.get(uri, headers: {
'Accept': 'application/json',
if (token != null && token.isNotEmpty)
'Authorization': 'Bearer $token',
}).timeout(const Duration(seconds: 15));
if (res.statusCode != 200) {
appState = AppState.failed;
notifyListeners();
return;
}
final body = json.decode(res.body);
final List dataRaw = body is Map && body['data'] is List
? body['data'] as List
: (body is List ? body : const []);
users.clear();
// اول کاربران mentionشدهی فعلی.
users.addAll(mentionedUsers);
for (final u in dataRaw.whereType<Map>()) {
final map = Map<String, dynamic>.from(u);
final uuid = map['id']?.toString() ?? '';
if (uuid.isEmpty) continue;
final int intId = uuid.hashCode;
_userUuidById[intId] = uuid;
final fullName = [
map['firstName']?.toString() ?? '',
map['lastName']?.toString() ?? '',
].where((s) => s.isNotEmpty).join(' ').trim();
final candidate = UsersMention(
id: intId,
name: fullName.isEmpty ? (map['username']?.toString() ?? '') : fullName,
type: null,
photo: map['avatar']?.toString(),
);
if (mentionedUsers.map((e) => e.id).contains(candidate.id)) continue;
if (candidate.id == currentUserHashId) continue;
users.add(candidate);
}
appState = AppState.idle;
notifyListeners();
} catch (e) {
dev.log('searchUsers(new) error: $e', name: 'MentionsState');
appState = AppState.failed;
notifyListeners();
}
}
Future<void> addMention() async {
if (!AuthConfig.useLegacyApi) {
await _addMentionViaCommentsService();
return;
}
late List<MentionData> cList = comments;
final user = DesignConfig.context!.read<UserProvider>().user;
cList.insert(
0,
MentionData(
id: 0,
user: UserOverview(
id: user.id,
fullName: user.fullName,
photo: user.photo,
),
text: textFieldController.text,
createdAt: DateTime.now().toString(),
mentions: mentionedUsers.map((user) => user.name).toList(),
),
);
final body = {};
body.addAll({
'text': textFieldController.text,
"mentions": mentionedUsers.map((user) => user.id).toList(),
});
final service = RequestService(
RequestHelper.mention(itemId, type),
body: body,
);
await service.post();
if (service.isSuccess) {
cList.firstWhere((comment) => comment.id == 0).id =
service.result['comment']['id'];
mentionedUsers.clear();
users.clear();
searchUsers.text = '';
textFieldController.text = '';
showUsersForMentionsLayout = false;
update();
}
}
Future<void> _addMentionViaCommentsService() async {
if (contentUuid == null || contentUuid!.isEmpty) {
// بدون contentUuid، نمی‌توانیم کامنت در content-service ایجاد کنیم.
return;
}
final text = textFieldController.text;
if (text.trim().isEmpty) return;
final mentionUuids = mentionedUsers
.map((u) => _userUuidById[u.id])
.whereType<String>()
.toList();
try {
await AuthInterceptor.ensureFreshToken();
final token = TokenStorage.accessToken;
final uri = Uri.parse('${AuthConfig.apiBaseUrl}/comments');
final res = await http
.post(uri,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
if (token != null && token.isNotEmpty)
'Authorization': 'Bearer $token',
},
body: json.encode({
'contentId': contentUuid,
'body': text,
'mentions': mentionUuids,
}))
.timeout(const Duration(seconds: 15));
if (res.statusCode < 200 || res.statusCode >= 300) {
dev.log('addMention(new) failed status=${res.statusCode}',
name: 'MentionsState');
return;
}
mentionedUsers.clear();
users.clear();
searchUsers.text = '';
textFieldController.text = '';
showUsersForMentionsLayout = false;
await _getCommentsFromCommentsService();
update();
} catch (e) {
dev.log('addMention(new) error: $e', name: 'MentionsState');
}
}
void deleteMention(int id) async {
if (!AuthConfig.useLegacyApi) {
// پاک‌کردن از سمت کاربر فعلی پشتیبانی نشده در API جدید.
comments.removeWhere((e) => e.id == id);
notifyListeners();
return;
}
final service = RequestService(RequestHelper.deleteComment(id));
await service.delete();
if (service.isSuccess) {
comments.removeWhere((element) => element.id == id);
}
notifyListeners();
}
}