feat(apps): 重构 UI 架构为 presentation 层并新增 l10n 国际化支持
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
import 'package:social_app/core/network/i_api_client.dart';
|
||||
|
||||
class FriendsApi {
|
||||
final IApiClient _client;
|
||||
static const _prefix = '/api/v1/friends';
|
||||
|
||||
FriendsApi(this._client);
|
||||
|
||||
Future<List<FriendResponse>> getFriends() async {
|
||||
final response = await _client.get(_prefix);
|
||||
final List<dynamic> data = response.data;
|
||||
return data.map((json) => FriendResponse.fromJson(json)).toList();
|
||||
}
|
||||
|
||||
Future<List<FriendRequestResponse>> getOutgoingRequests() async {
|
||||
final response = await _client.get('$_prefix/requests/outgoing');
|
||||
final List<dynamic> data = response.data;
|
||||
return data.map((json) => FriendRequestResponse.fromJson(json)).toList();
|
||||
}
|
||||
|
||||
Future<FriendRequestResponse> sendRequest(
|
||||
String targetUserId, {
|
||||
String? content,
|
||||
}) async {
|
||||
final data = {'target_user_id': targetUserId, 'content': content};
|
||||
final response = await _client.post('$_prefix/requests', data: data);
|
||||
return FriendRequestResponse.fromJson(response.data);
|
||||
}
|
||||
|
||||
Future<FriendRequestResponse> acceptRequest(String friendshipId) async {
|
||||
final response = await _client.post(
|
||||
'$_prefix/requests/$friendshipId/accept',
|
||||
);
|
||||
return FriendRequestResponse.fromJson(response.data);
|
||||
}
|
||||
|
||||
Future<FriendRequestResponse> declineRequest(String friendshipId) async {
|
||||
final response = await _client.post(
|
||||
'$_prefix/requests/$friendshipId/decline',
|
||||
);
|
||||
return FriendRequestResponse.fromJson(response.data);
|
||||
}
|
||||
|
||||
Future<void> removeFriend(String friendshipId) async {
|
||||
await _client.delete('$_prefix/$friendshipId');
|
||||
}
|
||||
|
||||
Future<FriendRequestResponse> getRequestById(String friendshipId) async {
|
||||
final response = await _client.get('$_prefix/requests/$friendshipId');
|
||||
return FriendRequestResponse.fromJson(response.data);
|
||||
}
|
||||
}
|
||||
|
||||
class FriendResponse {
|
||||
final String id;
|
||||
final UserBasicInfo friend;
|
||||
final String status;
|
||||
final DateTime createdAt;
|
||||
final DateTime? acceptedAt;
|
||||
|
||||
FriendResponse({
|
||||
required this.id,
|
||||
required this.friend,
|
||||
required this.status,
|
||||
required this.createdAt,
|
||||
this.acceptedAt,
|
||||
});
|
||||
|
||||
factory FriendResponse.fromJson(Map<String, dynamic> json) {
|
||||
return FriendResponse(
|
||||
id: json['id'] as String,
|
||||
friend: UserBasicInfo.fromJson(json['friend'] as Map<String, dynamic>),
|
||||
status: json['status'] as String,
|
||||
createdAt: DateTime.parse(json['created_at'] as String),
|
||||
acceptedAt: json['accepted_at'] != null
|
||||
? DateTime.parse(json['accepted_at'] as String)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UserBasicInfo {
|
||||
final String id;
|
||||
final String username;
|
||||
final String? avatarUrl;
|
||||
|
||||
UserBasicInfo({required this.id, required this.username, this.avatarUrl});
|
||||
|
||||
factory UserBasicInfo.fromJson(Map<String, dynamic> json) {
|
||||
return UserBasicInfo(
|
||||
id: json['id'] as String,
|
||||
username: json['username'] as String,
|
||||
avatarUrl: json['avatar_url'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FriendRequestResponse {
|
||||
final String id;
|
||||
final UserBasicInfo sender;
|
||||
final UserBasicInfo recipient;
|
||||
final String? content;
|
||||
final String status;
|
||||
final DateTime createdAt;
|
||||
|
||||
FriendRequestResponse({
|
||||
required this.id,
|
||||
required this.sender,
|
||||
required this.recipient,
|
||||
this.content,
|
||||
required this.status,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
factory FriendRequestResponse.fromJson(Map<String, dynamic> json) {
|
||||
return FriendRequestResponse(
|
||||
id: json['id'] as String,
|
||||
sender: UserBasicInfo.fromJson(json['sender'] as Map<String, dynamic>),
|
||||
recipient: UserBasicInfo.fromJson(
|
||||
json['recipient'] as Map<String, dynamic>,
|
||||
),
|
||||
content: json['content'] as String?,
|
||||
status: json['status'] as String,
|
||||
createdAt: DateTime.parse(json['created_at'] as String),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
class UserResponse {
|
||||
final String id;
|
||||
final String username;
|
||||
final String? phone;
|
||||
final String? avatarUrl;
|
||||
final String? bio;
|
||||
|
||||
const UserResponse({
|
||||
required this.id,
|
||||
required this.username,
|
||||
this.phone,
|
||||
this.avatarUrl,
|
||||
this.bio,
|
||||
});
|
||||
|
||||
factory UserResponse.fromJson(Map<String, dynamic> json) {
|
||||
return UserResponse(
|
||||
id: json['id'] as String,
|
||||
username: json['username'] as String,
|
||||
phone: json['phone'] as String?,
|
||||
avatarUrl: json['avatar_url'] as String?,
|
||||
bio: json['bio'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UserUpdateRequest {
|
||||
final String? username;
|
||||
final String? avatarUrl;
|
||||
final String? bio;
|
||||
|
||||
const UserUpdateRequest({this.username, this.avatarUrl, this.bio});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
if (username != null) 'username': username,
|
||||
if (avatarUrl != null) 'avatar_url': avatarUrl,
|
||||
if (bio != null) 'bio': bio,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import 'dart:io';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:social_app/core/network/i_api_client.dart';
|
||||
import 'models/user_response.dart';
|
||||
|
||||
class UserBasicInfo {
|
||||
final String id;
|
||||
final String username;
|
||||
final String? avatarUrl;
|
||||
|
||||
UserBasicInfo({required this.id, required this.username, this.avatarUrl});
|
||||
|
||||
factory UserBasicInfo.fromJson(Map<String, dynamic> json) {
|
||||
return UserBasicInfo(
|
||||
id: json['id'] as String,
|
||||
username: json['username'] as String,
|
||||
avatarUrl: json['avatar_url'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UsersApi {
|
||||
final IApiClient _client;
|
||||
static const _prefix = '/api/v1/users';
|
||||
|
||||
UsersApi(this._client);
|
||||
|
||||
Future<UserBasicInfo> getById(String userId) async {
|
||||
final response = await _client.get('$_prefix/$userId');
|
||||
return UserBasicInfo.fromJson(response.data);
|
||||
}
|
||||
|
||||
Future<UserResponse> getMe() async {
|
||||
final response = await _client.get('$_prefix/me');
|
||||
return UserResponse.fromJson(response.data);
|
||||
}
|
||||
|
||||
Future<UserResponse> updateMe(UserUpdateRequest request) async {
|
||||
final response = await _client.patch('$_prefix/me', data: request.toJson());
|
||||
return UserResponse.fromJson(response.data);
|
||||
}
|
||||
|
||||
Future<String> uploadAvatar(File file) async {
|
||||
final formData = FormData.fromMap({
|
||||
'file': await MultipartFile.fromFile(
|
||||
file.path,
|
||||
filename: file.path.split('/').last,
|
||||
),
|
||||
});
|
||||
final response = await _client.post<Map<String, dynamic>>(
|
||||
'$_prefix/me/avatar',
|
||||
data: formData,
|
||||
);
|
||||
final payload = response.data;
|
||||
if (payload is! Map<String, dynamic>) {
|
||||
throw StateError('Invalid /users/me/avatar response');
|
||||
}
|
||||
final url = payload['url'];
|
||||
if (url is! String) {
|
||||
throw StateError('Missing url in /users/me/avatar response');
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
Future<List<UserResponse>> searchUsers(String query) async {
|
||||
final response = await _client.post(
|
||||
'$_prefix/search',
|
||||
data: {'query': query},
|
||||
);
|
||||
final List<dynamic> data = response.data;
|
||||
return data.map((json) => UserResponse.fromJson(json)).toList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import 'models/user_response.dart';
|
||||
|
||||
abstract class UsersRepository {
|
||||
Future<UserResponse> getMe();
|
||||
Future<UserResponse> updateMe(UserUpdateRequest request);
|
||||
Future<List<UserResponse>> searchUsers(String query);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import 'users_api.dart';
|
||||
import 'users_repository.dart';
|
||||
import 'models/user_response.dart';
|
||||
|
||||
class UsersRepositoryImpl implements UsersRepository {
|
||||
final UsersApi _api;
|
||||
|
||||
UsersRepositoryImpl({required UsersApi api}) : _api = api;
|
||||
|
||||
@override
|
||||
Future<UserResponse> getMe() {
|
||||
return _api.getMe();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<UserResponse> updateMe(UserUpdateRequest request) {
|
||||
return _api.updateMe(request);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<UserResponse>> searchUsers(String query) {
|
||||
return _api.searchUsers(query);
|
||||
}
|
||||
}
|
||||
+26
-12
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import '../../../../core/l10n/l10n.dart';
|
||||
import '../../../../core/theme/design_tokens.dart';
|
||||
import '../../../../shared/widgets/back_title_page_header.dart';
|
||||
import '../../../../shared/widgets/app_input.dart';
|
||||
@@ -42,7 +43,9 @@ class _AddContactScreenState extends State<AddContactScreen> {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
BackTitlePageHeader(
|
||||
title: isEditing ? '编辑联系人' : '添加联系人',
|
||||
title: isEditing
|
||||
? context.l10n.contactEditTitle
|
||||
: context.l10n.contactAddTitle,
|
||||
onBack: () => context.pop(),
|
||||
trailing: _buildConfirmButton(),
|
||||
),
|
||||
@@ -121,18 +124,22 @@ class _AddContactScreenState extends State<AddContactScreen> {
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
AppInput(label: '昵称', hint: '请输入昵称', controller: _nameController),
|
||||
AppInput(
|
||||
label: context.l10n.contactNickname,
|
||||
hint: context.l10n.contactNicknameHint,
|
||||
controller: _nameController,
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
AppInput(
|
||||
label: '手机号',
|
||||
hint: '+86 请输入 11 位手机号',
|
||||
label: context.l10n.contactPhone,
|
||||
hint: context.l10n.contactPhoneHint,
|
||||
controller: _phoneController,
|
||||
keyboardType: TextInputType.phone,
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
AppInput(
|
||||
label: '备注',
|
||||
hint: '请输入备注',
|
||||
label: context.l10n.contactRemark,
|
||||
hint: context.l10n.contactRemarkHint,
|
||||
controller: _remarkController,
|
||||
maxLines: 3,
|
||||
),
|
||||
@@ -145,7 +152,7 @@ class _AddContactScreenState extends State<AddContactScreen> {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: AppSpacing.sm),
|
||||
child: LinkButton(
|
||||
text: '删除联系人',
|
||||
text: context.l10n.contactDelete,
|
||||
onTap: _handleDelete,
|
||||
foregroundColor: AppColors.red600,
|
||||
),
|
||||
@@ -157,7 +164,11 @@ class _AddContactScreenState extends State<AddContactScreen> {
|
||||
final phone = _phoneController.text.trim();
|
||||
|
||||
if (name.isEmpty || phone.isEmpty) {
|
||||
Toast.show(context, '请填写昵称和手机号', type: ToastType.warning);
|
||||
Toast.show(
|
||||
context,
|
||||
context.l10n.contactFillRequired,
|
||||
type: ToastType.warning,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -169,12 +180,12 @@ class _AddContactScreenState extends State<AddContactScreen> {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('删除联系人'),
|
||||
content: const Text('确定要删除此联系人吗?'),
|
||||
title: Text(context.l10n.contactDeleteConfirmTitle),
|
||||
content: Text(context.l10n.contactDeleteConfirmMessage),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('取消'),
|
||||
child: Text(context.l10n.commonCancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
@@ -182,7 +193,10 @@ class _AddContactScreenState extends State<AddContactScreen> {
|
||||
// TODO: Implement delete logic
|
||||
context.pop();
|
||||
},
|
||||
child: const Text('删除', style: TextStyle(color: AppColors.red600)),
|
||||
child: Text(
|
||||
context.l10n.commonDelete,
|
||||
style: const TextStyle(color: AppColors.red600),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
+62
-39
@@ -1,14 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import '../../../../core/di/injection.dart';
|
||||
import '../../../../app/di/injection.dart';
|
||||
import '../../../../core/l10n/l10n.dart';
|
||||
import '../../../../core/theme/design_tokens.dart';
|
||||
import '../../../../shared/widgets/app_loading_indicator.dart';
|
||||
import '../../../../shared/widgets/toast/index.dart';
|
||||
import '../../../../shared/widgets/app_button.dart';
|
||||
import '../../../../shared/widgets/back_title_page_header.dart';
|
||||
import '../../../friends/data/friends_api.dart';
|
||||
import '../../../users/data/models/user_response.dart';
|
||||
import '../../../users/data/users_api.dart';
|
||||
import '../../../contacts/data/friends_api.dart';
|
||||
import '../../../contacts/data/users/models/user_response.dart';
|
||||
import '../../../contacts/data/users/users_api.dart';
|
||||
|
||||
class ContactsScreen extends StatefulWidget {
|
||||
const ContactsScreen({super.key});
|
||||
@@ -67,7 +68,11 @@ class _ContactsScreenState extends State<ContactsScreen> {
|
||||
final query = _searchController.text.trim();
|
||||
|
||||
if (query.isEmpty) {
|
||||
Toast.show(context, '请输入用户名或手机号', type: ToastType.warning);
|
||||
Toast.show(
|
||||
context,
|
||||
context.l10n.contactsSearchEmptyQuery,
|
||||
type: ToastType.warning,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -92,7 +97,11 @@ class _ContactsScreenState extends State<ContactsScreen> {
|
||||
setState(() {
|
||||
_isSearching = false;
|
||||
});
|
||||
Toast.show(context, '搜索失败,请稍后重试', type: ToastType.error);
|
||||
Toast.show(
|
||||
context,
|
||||
context.l10n.contactsSearchFailed,
|
||||
type: ToastType.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,14 +122,22 @@ class _ContactsScreenState extends State<ContactsScreen> {
|
||||
_sentRequestIds.add(targetUserId);
|
||||
_sendingRequestUserId = null;
|
||||
});
|
||||
Toast.show(context, '好友请求已发送', type: ToastType.success);
|
||||
Toast.show(
|
||||
context,
|
||||
context.l10n.contactsFriendRequestSent,
|
||||
type: ToastType.success,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_sendingRequestUserId = null;
|
||||
});
|
||||
Toast.show(context, '发送失败,请稍后重试', type: ToastType.error);
|
||||
Toast.show(
|
||||
context,
|
||||
context.l10n.contactsSendFailed,
|
||||
type: ToastType.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,7 +187,7 @@ class _ContactsScreenState extends State<ContactsScreen> {
|
||||
),
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
Text(
|
||||
'添加 ${user.username}',
|
||||
context.l10n.contactsAddSheetTitle(user.username),
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -178,8 +195,8 @@ class _ContactsScreenState extends State<ContactsScreen> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.sm),
|
||||
const Text(
|
||||
'发送一条验证信息,方便对方确认你的身份',
|
||||
Text(
|
||||
context.l10n.contactsAddSheetDesc,
|
||||
style: TextStyle(fontSize: 13, color: AppColors.slate500),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
@@ -194,8 +211,8 @@ class _ContactsScreenState extends State<ContactsScreen> {
|
||||
maxLines: 3,
|
||||
minLines: 2,
|
||||
maxLength: 200,
|
||||
decoration: const InputDecoration(
|
||||
hintText: '你好,我是...',
|
||||
decoration: InputDecoration(
|
||||
hintText: context.l10n.contactsAddSheetMessageHint,
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 13,
|
||||
color: AppColors.slate400,
|
||||
@@ -215,7 +232,7 @@ class _ContactsScreenState extends State<ContactsScreen> {
|
||||
children: [
|
||||
Expanded(
|
||||
child: AppButton(
|
||||
text: '取消',
|
||||
text: context.l10n.commonCancel,
|
||||
isOutlined: true,
|
||||
onPressed: () => Navigator.pop(sheetContext),
|
||||
),
|
||||
@@ -261,7 +278,10 @@ class _ContactsScreenState extends State<ContactsScreen> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
BackTitlePageHeader(title: '联系人', onBack: () => context.pop()),
|
||||
BackTitlePageHeader(
|
||||
title: context.l10n.contactsTitle,
|
||||
onBack: () => context.pop(),
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.fromLTRB(20, 8, 20, 20),
|
||||
@@ -272,12 +292,12 @@ class _ContactsScreenState extends State<ContactsScreen> {
|
||||
_buildSearchResults(),
|
||||
const SizedBox(height: 16),
|
||||
if (_pendingRequests.isNotEmpty) ...[
|
||||
_buildSectionTitle('新的联系人'),
|
||||
_buildSectionTitle(context.l10n.contactsSectionNew),
|
||||
const SizedBox(height: 8),
|
||||
_buildPendingRequestCard(_pendingRequests),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
_buildSectionTitle('全部联系人'),
|
||||
_buildSectionTitle(context.l10n.contactsSectionAll),
|
||||
const SizedBox(height: 8),
|
||||
if (_isLoading)
|
||||
const Center(
|
||||
@@ -314,8 +334,8 @@ class _ContactsScreenState extends State<ContactsScreen> {
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
focusNode: _searchFocusNode,
|
||||
decoration: const InputDecoration(
|
||||
hintText: '输入用户名或手机号',
|
||||
decoration: InputDecoration(
|
||||
hintText: context.l10n.contactsSearchHint,
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -404,9 +424,9 @@ class _ContactsScreenState extends State<ContactsScreen> {
|
||||
else if (_searchResults.isEmpty)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: const Center(
|
||||
child: Center(
|
||||
child: Text(
|
||||
'未找到该用户',
|
||||
context.l10n.contactsSearchNoUser,
|
||||
style: TextStyle(fontSize: 14, color: AppColors.slate500),
|
||||
),
|
||||
),
|
||||
@@ -479,8 +499,8 @@ class _ContactsScreenState extends State<ContactsScreen> {
|
||||
color: AppColors.slate300,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Text(
|
||||
'已是好友',
|
||||
child: Text(
|
||||
context.l10n.contactsStatusAlreadyFriend,
|
||||
style: TextStyle(fontSize: 12, color: AppColors.slate500),
|
||||
),
|
||||
);
|
||||
@@ -493,8 +513,8 @@ class _ContactsScreenState extends State<ContactsScreen> {
|
||||
color: AppColors.slate300,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Text(
|
||||
'已发送',
|
||||
child: Text(
|
||||
context.l10n.contactsStatusSent,
|
||||
style: TextStyle(fontSize: 12, color: AppColors.slate500),
|
||||
),
|
||||
);
|
||||
@@ -512,14 +532,14 @@ class _ContactsScreenState extends State<ContactsScreen> {
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: const Color(0xFFD7E6FF)),
|
||||
),
|
||||
child: const Row(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.person_add, size: 14, color: AppColors.blue500),
|
||||
SizedBox(width: 4),
|
||||
const Icon(Icons.person_add, size: 14, color: AppColors.blue500),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'添加',
|
||||
style: TextStyle(
|
||||
context.l10n.contactsAdd,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.blue500,
|
||||
@@ -544,8 +564,8 @@ class _ContactsScreenState extends State<ContactsScreen> {
|
||||
children: [
|
||||
const Icon(Icons.person_outline, size: 48, color: AppColors.slate400),
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
'暂无联系人',
|
||||
Text(
|
||||
context.l10n.contactsEmptyTitle,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -553,8 +573,8 @@ class _ContactsScreenState extends State<ContactsScreen> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const Text(
|
||||
'搜索手机号添加好友开始聊天吧',
|
||||
Text(
|
||||
context.l10n.contactsEmptyDesc,
|
||||
style: TextStyle(fontSize: 13, color: AppColors.slate400),
|
||||
),
|
||||
],
|
||||
@@ -616,8 +636,8 @@ class _ContactsScreenState extends State<ContactsScreen> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
const Text(
|
||||
'等待对方确认',
|
||||
Text(
|
||||
context.l10n.contactsPendingConfirm,
|
||||
style: TextStyle(fontSize: 12, color: AppColors.slate500),
|
||||
),
|
||||
],
|
||||
@@ -780,9 +800,12 @@ class _ContactsScreenState extends State<ContactsScreen> {
|
||||
trackColor: AppColors.blue300,
|
||||
withContainer: false,
|
||||
)
|
||||
: const Text(
|
||||
'发送',
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
: Text(
|
||||
context.l10n.contactsSend,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
Reference in New Issue
Block a user