Chat module
This commit is contained in:
18
lib/app/bindings/controllersBindings.dart
Normal file
18
lib/app/bindings/controllersBindings.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
import 'package:caller/app/modules/chat/MessageModule/controllers/chatInfoController.dart';
|
||||
import 'package:caller/app/modules/chat/MessageModule/controllers/chatSearchController.dart';
|
||||
import 'package:caller/app/modules/chat/contactModule/controllers/contactController.dart';
|
||||
import 'package:caller/app/modules/chat/discoverModule/controller/discover_controller.dart';
|
||||
import 'package:caller/translations/controller/language_controller.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class AppBindings extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
|
||||
Get.lazyPut(() => LanguageController());
|
||||
Get.lazyPut(() => DiscoverController());
|
||||
Get.lazyPut(() => ContactController());
|
||||
Get.lazyPut(()=>ChatInfoController());
|
||||
Get.lazyPut(()=>ChatSearchController());
|
||||
}
|
||||
}
|
77
lib/app/bindings/themeController.dart
Normal file
77
lib/app/bindings/themeController.dart
Normal file
@@ -0,0 +1,77 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class ThemeController extends GetxController {
|
||||
// Observable theme mode, default to system.
|
||||
var themeMode = ThemeMode.system.obs;
|
||||
|
||||
void toggleTheme(bool isDark) {
|
||||
themeMode.value = isDark ? ThemeMode.dark : ThemeMode.light;
|
||||
}
|
||||
|
||||
void setSystemTheme() {
|
||||
themeMode.value = ThemeMode.system;
|
||||
}
|
||||
|
||||
bool get isDarkMode {
|
||||
if (themeMode.value == ThemeMode.system) {
|
||||
final brightness = WidgetsBinding.instance.platformDispatcher.platformBrightness;
|
||||
return brightness == Brightness.dark;
|
||||
}
|
||||
return themeMode.value == ThemeMode.dark;
|
||||
}
|
||||
}
|
||||
// light theme
|
||||
final ThemeData lightTheme = ThemeData(
|
||||
brightness: Brightness.light,
|
||||
scaffoldBackgroundColor: Colors.white,
|
||||
appBarTheme: const AppBarTheme(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.black,
|
||||
),
|
||||
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
|
||||
backgroundColor: Colors.white,
|
||||
selectedItemColor: Colors.green,
|
||||
unselectedItemColor: Colors.grey,
|
||||
elevation: 8,
|
||||
),
|
||||
iconTheme: const IconThemeData(color: Colors.black87),
|
||||
dividerColor: Colors.grey.shade300,
|
||||
cardColor: Colors.white,
|
||||
textTheme: const TextTheme(
|
||||
bodyLarge: TextStyle(color: Colors.black),
|
||||
bodyMedium: TextStyle(color: Colors.black87),
|
||||
),
|
||||
primaryColor: Colors.green,
|
||||
floatingActionButtonTheme: const FloatingActionButtonThemeData(
|
||||
backgroundColor: Colors.green,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
);
|
||||
//dark theme
|
||||
final ThemeData darkTheme = ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
scaffoldBackgroundColor: Colors.black,
|
||||
appBarTheme: const AppBarTheme(
|
||||
backgroundColor: Colors.black,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
|
||||
backgroundColor: Colors.black,
|
||||
selectedItemColor: Colors.teal,
|
||||
unselectedItemColor: Colors.grey,
|
||||
elevation: 8,
|
||||
),
|
||||
iconTheme: const IconThemeData(color: Colors.white70),
|
||||
dividerColor: Colors.grey.shade700,
|
||||
cardColor: Colors.grey.shade900,
|
||||
textTheme: const TextTheme(
|
||||
bodyLarge: TextStyle(color: Colors.white),
|
||||
bodyMedium: TextStyle(color: Colors.white70),
|
||||
),
|
||||
primaryColor: Colors.teal,
|
||||
floatingActionButtonTheme: const FloatingActionButtonThemeData(
|
||||
backgroundColor: Colors.teal,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
);
|
@@ -9,6 +9,7 @@ class ChatContactModel {
|
||||
final String? statusMessage;
|
||||
final String? lastMessage;
|
||||
final DateTime? lastMessageTime;
|
||||
final bool isGroup;
|
||||
|
||||
ChatContactModel({
|
||||
required this.id,
|
||||
@@ -21,10 +22,10 @@ class ChatContactModel {
|
||||
this.statusMessage,
|
||||
this.lastMessage,
|
||||
this.lastMessageTime,
|
||||
this.isGroup = false,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
class ContactItem {
|
||||
final String avatar;
|
||||
final String title;
|
||||
@@ -34,6 +35,19 @@ class ContactItem {
|
||||
|
||||
|
||||
final dummyContacts = [
|
||||
|
||||
ChatContactModel(
|
||||
id: 'family_group',
|
||||
name: '家人群',
|
||||
nameIndex: 'J',
|
||||
avatarUrl: 'https://picsum.photos/id/18/200/200',
|
||||
isOnline: false,
|
||||
phoneNumber: 'N/A',
|
||||
region: '家庭群组',
|
||||
lastMessage: '妈妈: 记得回家吃饭',
|
||||
lastMessageTime: DateTime.now().subtract(Duration(hours: 12)),
|
||||
isGroup: true, // Mark as group
|
||||
),
|
||||
ChatContactModel(
|
||||
id: 'wx_helper',
|
||||
name: '文件传输助手',
|
||||
|
65
lib/app/models/contactModels/groupContactsModel.dart
Normal file
65
lib/app/models/contactModels/groupContactsModel.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
import 'package:caller/app/models/contactModels/contactModels.dart';
|
||||
|
||||
class GroupDetails {
|
||||
final String groupId;
|
||||
final List<ChatContactModel> members;
|
||||
final ChatContactModel? admin;
|
||||
final DateTime? createdAt;
|
||||
|
||||
GroupDetails({
|
||||
required this.groupId,
|
||||
required this.members,
|
||||
this.admin,
|
||||
this.createdAt,
|
||||
});
|
||||
}
|
||||
|
||||
final dummyGroupDetails = [
|
||||
GroupDetails(
|
||||
groupId: 'family_group',
|
||||
members: [
|
||||
dummyContacts.firstWhere((c) => c.id == 'zhang_san'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'li_si'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'wang_wu'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'alex_wang'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'emma_smith'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'david_zhang'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'liu_fang'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'mike_johnson'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'wei_xin'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'zhou_jie'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'sarah_li'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'chen_qi'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'zhao_liu'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'tencent_service'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'wechat_team'),
|
||||
|
||||
dummyContacts.firstWhere((c) => c.id == 'zhang_san'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'li_si'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'wang_wu'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'alex_wang'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'emma_smith'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'david_zhang'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'liu_fang'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'mike_johnson'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'wei_xin'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'zhou_jie'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'sarah_li'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'chen_qi'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'zhao_liu'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'tencent_service'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'wechat_team'),
|
||||
|
||||
],
|
||||
admin: dummyContacts.firstWhere((c) => c.id == 'zhang_san'),
|
||||
),
|
||||
GroupDetails(
|
||||
groupId: 'project_group',
|
||||
members: [
|
||||
dummyContacts.firstWhere((c) => c.id == 'alex_wang'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'emma_smith'),
|
||||
dummyContacts.firstWhere((c) => c.id == 'david_zhang'),
|
||||
],
|
||||
admin: dummyContacts.firstWhere((c) => c.id == 'alex_wang'),
|
||||
),
|
||||
];
|
@@ -63,7 +63,24 @@ Future<void> stopRecordingAndSend() async {
|
||||
|
||||
if (path != null) {
|
||||
final file = File(path);
|
||||
sendFileMessage(file);
|
||||
|
||||
//livekit backend
|
||||
// sendFileMessage(file);
|
||||
|
||||
// Temporarily add the recording file to the UI as a message
|
||||
final tempMsg = ChatMessageModel(
|
||||
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
text: '[Sent audio file: ${p.basename(file.path)}]',
|
||||
fileName: p.basename(file.path),
|
||||
mimeType: 'audio/m4a',
|
||||
fileUrl: file.path,
|
||||
participantIdentity: 'Khan',
|
||||
timestamp: DateTime.now(),
|
||||
isMe: true,
|
||||
isPrivate: false,
|
||||
);
|
||||
|
||||
messages.add(tempMsg);
|
||||
} else {
|
||||
Get.snackbar('Error', 'Recording failed or was cancelled.');
|
||||
}
|
||||
|
@@ -0,0 +1,11 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class ChatInfoController extends GetxController {
|
||||
final RxBool muteNotifications = false.obs;
|
||||
final RxBool stickyOnTop = false.obs;
|
||||
final RxBool alert = false.obs;
|
||||
|
||||
void toggleMuteNotifications() => muteNotifications.toggle();
|
||||
void toggleStickyOnTop() => stickyOnTop.toggle();
|
||||
void toggleAlert() => alert.toggle();
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ChatSearchController extends GetxController {
|
||||
final RxString searchQuery = ''.obs;
|
||||
final TextEditingController textController = TextEditingController();
|
||||
|
||||
// Method to update the search query
|
||||
void updateSearchQuery(String query) {
|
||||
searchQuery.value = query;
|
||||
|
||||
}
|
||||
}
|
@@ -2,9 +2,10 @@ import 'package:caller/app/constants/colors/colors.dart';
|
||||
import 'package:caller/app/modules/chat/MessageModule/views/messageChatViews/chatListPage.dart';
|
||||
import 'package:caller/app/modules/chat/contactModule/contactViews/ContactPage/contactPage.dart';
|
||||
import 'package:caller/app/modules/chat/discoverModule/views/discoverPage.dart';
|
||||
import 'package:caller/app/modules/chat/mineModule/views/minePage.dart';
|
||||
import 'package:caller/app/modules/chat/mineModule/views/homePage/mineHomePage.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:caller/app/modules/selfMedia/video/views/videoFeed.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class ChatHomeScreen extends StatefulWidget {
|
||||
const ChatHomeScreen({Key? key}) : super(key: key);
|
||||
@@ -28,13 +29,14 @@ class _ChatHomeScreenState extends State<ChatHomeScreen> {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: const Text('消息'),
|
||||
title: Text('app_bar_title'.tr),
|
||||
backgroundColor: AppColors.primary,
|
||||
leading: null,
|
||||
elevation: 0,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.switch_account),
|
||||
onPressed: () => Navigator.pushReplacement(
|
||||
onPressed: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => VideoFeedScreen())
|
||||
),
|
||||
@@ -43,7 +45,7 @@ class _ChatHomeScreenState extends State<ChatHomeScreen> {
|
||||
),
|
||||
body: _pages[_currentIndex],
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
backgroundColor: Colors.white,
|
||||
backgroundColor: Theme.of(context).bottomNavigationBarTheme.backgroundColor,
|
||||
type: BottomNavigationBarType.fixed,
|
||||
currentIndex: _currentIndex,
|
||||
selectedItemColor: AppColors.primary,
|
||||
@@ -53,22 +55,22 @@ class _ChatHomeScreenState extends State<ChatHomeScreen> {
|
||||
_currentIndex = index;
|
||||
});
|
||||
},
|
||||
items: const [
|
||||
items: [
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.chat),
|
||||
label: '消息',
|
||||
label: 'message'.tr,
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.contacts),
|
||||
label: '联系人',
|
||||
label: 'contacts'.tr,
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.photo_camera),
|
||||
label: '发现',
|
||||
label: 'discover'.tr,
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.person),
|
||||
label: '我的',
|
||||
label: 'mine'.tr,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@@ -0,0 +1,70 @@
|
||||
import 'package:caller/app/modules/chat/MessageModule/widgets/dotted_box_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:caller/app/models/contactModels/contactModels.dart'; // Import the models
|
||||
|
||||
class GroupMembersPage extends StatelessWidget {
|
||||
final List<ChatContactModel> members;
|
||||
|
||||
const GroupMembersPage({Key? key, required this.members}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('All Group Members'), centerTitle: true),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
|
||||
child: GridView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 5,
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisSpacing: 10,
|
||||
),
|
||||
itemCount: members.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index < members.length) {
|
||||
final member = members[index];
|
||||
return Column(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: member.avatarUrl,
|
||||
width: 50,
|
||||
height: 50,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
Text(member.name, style: TextStyle(fontSize: 10)),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return GestureDetector(
|
||||
onTap: () {},
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
FittedBox(
|
||||
child: SizedBox(
|
||||
width: 50,
|
||||
height: 50,
|
||||
child: DottedBorderBox(
|
||||
child: Center(child: Icon(Icons.add, size: 16)),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text("")
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,130 @@
|
||||
import 'package:caller/app/modules/chat/MessageModule/controllers/chatSearchController.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class ChatSearchScreen extends StatelessWidget {
|
||||
final ChatSearchController controller = Get.find<ChatSearchController>();
|
||||
|
||||
ChatSearchScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
child: Scaffold(
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16,vertical: 40),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 300,
|
||||
child: TextField(
|
||||
minLines: 1,
|
||||
maxLines: 4,
|
||||
controller: controller.textController,
|
||||
cursorColor: Theme.of(context).primaryColor,
|
||||
onTap: () {},
|
||||
style: TextStyle(color: Theme.of(context).textTheme.bodyLarge!.color),
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: Icon(Icons.search),
|
||||
hintText: 'search'.tr,
|
||||
hintStyle: Theme.of(context).textTheme.bodyLarge,
|
||||
filled: true,
|
||||
fillColor: Colors.grey.withOpacity(0.25),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
),
|
||||
onSubmitted: (text) {},
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
},
|
||||
child: Text(
|
||||
'cancel'.tr,
|
||||
style: TextStyle(color: Theme.of(context).textTheme.bodyLarge!.color),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
'filter_by'.tr,
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
Wrap(
|
||||
spacing: 8.0,
|
||||
runSpacing: 8.0,
|
||||
children: [
|
||||
FilterChip(
|
||||
label: Text('date'.tr),
|
||||
onSelected: (value) {
|
||||
},
|
||||
),
|
||||
FilterChip(
|
||||
label: Text('photos_and_videos'.tr),
|
||||
onSelected: (value) {
|
||||
},
|
||||
),
|
||||
FilterChip(
|
||||
label: Text('files'.tr),
|
||||
onSelected: (value) {
|
||||
},
|
||||
),
|
||||
FilterChip(
|
||||
label: Text('links'.tr),
|
||||
onSelected: (value) {
|
||||
},
|
||||
),
|
||||
FilterChip(
|
||||
label: Text('music_and_audio'.tr),
|
||||
onSelected: (value) {
|
||||
},
|
||||
),
|
||||
FilterChip(
|
||||
label: Text('transactions'.tr),
|
||||
onSelected: (value) {
|
||||
},
|
||||
),
|
||||
FilterChip(
|
||||
label: Text('mini'.tr),
|
||||
onSelected: (value) {
|
||||
},
|
||||
),
|
||||
FilterChip(
|
||||
label: Text('channels'.tr),
|
||||
onSelected: (value) {
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: Obx(() {
|
||||
return Center(
|
||||
child: Text(
|
||||
'${'search_results_for'.tr}: ${controller.searchQuery.value}',
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,350 @@
|
||||
import 'package:caller/app/models/contactModels/groupContactsModel.dart';
|
||||
import 'package:caller/app/modules/chat/MessageModule/controllers/chatInfoController.dart';
|
||||
import 'package:caller/app/modules/chat/MessageModule/views/messageChatViews/allGroupMembersInfo.dart';
|
||||
import 'package:caller/app/modules/chat/MessageModule/widgets/dotted_box_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:caller/app/models/contactModels/contactModels.dart'; // Import your models
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class ChatInfoPage extends StatefulWidget {
|
||||
final ChatContactModel contact;
|
||||
final GroupDetails? groupDetails;
|
||||
const ChatInfoPage({Key? key, required this.contact, this.groupDetails})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<ChatInfoPage> createState() => _ChatInfoPageState();
|
||||
}
|
||||
|
||||
class _ChatInfoPageState extends State<ChatInfoPage> {
|
||||
final ChatInfoController controller = Get.find<ChatInfoController>();
|
||||
|
||||
GroupDetails? get group =>
|
||||
widget.groupDetails ??
|
||||
dummyGroupDetails.firstWhere(
|
||||
(g) => g.groupId == widget.contact.id,
|
||||
orElse: () => GroupDetails(groupId: widget.contact.id, members: []),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: Icon(
|
||||
Icons.arrow_back_ios_new,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
),
|
||||
centerTitle: true,
|
||||
title: Text('chat_info'.tr),
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
//profile for individual contact
|
||||
if (!widget.contact.isGroup)
|
||||
Container(
|
||||
color: Theme.of(context).cardColor,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: widget.contact.avatarUrl,
|
||||
width: 60,
|
||||
height: 60,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'${widget.contact.name.substring(0, 3)}...',
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
SizedBox(
|
||||
width: 55,
|
||||
height: 55,
|
||||
child: DottedBorderBox(
|
||||
child: Center(child: Icon(Icons.add)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 10),
|
||||
|
||||
// Group members info
|
||||
if (widget.contact.isGroup) ...[
|
||||
Container(
|
||||
color: Theme.of(context).cardColor,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 10),
|
||||
child: Column(
|
||||
children: [
|
||||
Wrap(
|
||||
spacing: 15,
|
||||
runSpacing: 15,
|
||||
children: [
|
||||
...group!.members.take(19).map((member) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: member.avatarUrl,
|
||||
width: 55,
|
||||
height: 55,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
SizedBox(
|
||||
width: 55,
|
||||
child: Text(
|
||||
member.name,
|
||||
style: TextStyle(fontSize: 11),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
// Dotted container
|
||||
SizedBox(
|
||||
width: 55,
|
||||
height: 55,
|
||||
child: DottedBorderBox(
|
||||
child: Center(child: Icon(Icons.add)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => GroupMembersPage(members: group!.members),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'View group members',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Theme.of(context).textTheme.bodyLarge!.color,
|
||||
),
|
||||
),
|
||||
Icon(Icons.chevron_right, size: 16, color: Theme.of(context).iconTheme.color),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
],
|
||||
SizedBox(height: 10,),
|
||||
if (widget.contact.isGroup)...[
|
||||
Container(
|
||||
color: Theme.of(context).cardColor,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
color: Theme.of(context).cardColor,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('group_name'.tr,
|
||||
style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color,fontSize: 16),),
|
||||
Spacer(),
|
||||
Text(widget.contact.name),
|
||||
SizedBox(width: 5,),
|
||||
Icon(Icons.chevron_right),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
thickness: 0.3,
|
||||
height: 0.1,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
Container(
|
||||
color: Theme.of(context).cardColor,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('group_qr_code'.tr,
|
||||
style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color,fontSize: 16),),
|
||||
Spacer(),
|
||||
Icon(Icons.qr_code_outlined),
|
||||
SizedBox(width: 5,),
|
||||
Icon(Icons.chevron_right),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
],
|
||||
SizedBox(height: 10,),
|
||||
|
||||
// Common Options
|
||||
|
||||
Container(
|
||||
color: Theme.of(context).cardColor,
|
||||
child: ListTile(
|
||||
title: Text('search_chat'.tr),
|
||||
trailing: Icon(Icons.chevron_right),
|
||||
onTap: () {},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
|
||||
Container(
|
||||
color: Theme.of(context).cardColor,
|
||||
child: Column(
|
||||
children: [
|
||||
Obx(
|
||||
() => ListTile(
|
||||
title: Text('mute_notification'.tr),
|
||||
trailing: Switch(
|
||||
activeColor: Theme.of(context).cardColor,
|
||||
inactiveThumbColor: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge!.color,
|
||||
activeTrackColor: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge!.color,
|
||||
inactiveTrackColor: Theme.of(context).cardColor,
|
||||
|
||||
value: controller.muteNotifications.value,
|
||||
onChanged: (value) =>
|
||||
controller.toggleMuteNotifications(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
thickness: 0.3,
|
||||
height: 0.1,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
Obx(
|
||||
() => ListTile(
|
||||
title: Text('sticky_top'.tr),
|
||||
trailing: Switch(
|
||||
activeColor: Theme.of(context).cardColor,
|
||||
inactiveThumbColor: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge!.color,
|
||||
activeTrackColor: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge!.color,
|
||||
inactiveTrackColor: Theme.of(context).cardColor,
|
||||
value: controller.stickyOnTop.value,
|
||||
onChanged: (value) => controller.toggleStickyOnTop(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
|
||||
Container(
|
||||
color: Theme.of(context).cardColor,
|
||||
child: ListTile(
|
||||
title: Text('background'.tr),
|
||||
trailing: Icon(Icons.chevron_right),
|
||||
onTap: () {},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
|
||||
Container(
|
||||
color: Theme.of(context).cardColor,
|
||||
child: ListTile(
|
||||
title: Text('clear_chat_history'.tr),
|
||||
trailing: Icon(Icons.chevron_right),
|
||||
onTap: () {},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
|
||||
Container(
|
||||
color: Theme.of(context).cardColor,
|
||||
child: ListTile(
|
||||
title: Text('report'.tr),
|
||||
trailing: Icon(Icons.chevron_right),
|
||||
onTap: () {},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
|
||||
if (widget.contact.isGroup)
|
||||
Container(
|
||||
color: Theme.of(context).cardColor,
|
||||
child: ListTile(
|
||||
title: Center(
|
||||
child: Text(
|
||||
'leave_group'.tr,
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('leave_group'.tr),
|
||||
content: Text('are_you_sure_leave_group'.tr),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('cancel'.tr),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text('leave'.tr, style: TextStyle(color: Colors.red)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -6,18 +6,42 @@ import 'package:caller/app/modules/chat/contactModule/widgets/contactPageWidgets
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
|
||||
class ChatListPage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor:Colors.white,
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
body: ListView.builder(
|
||||
itemCount: dummyContacts.length,
|
||||
itemBuilder: (context, index) {
|
||||
final contact = dummyContacts[index];
|
||||
return ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundImage: CachedNetworkImageProvider(contact.avatarUrl),
|
||||
return Column(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: contact.isGroup
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: SizedBox(
|
||||
height: 50,
|
||||
width: 50,
|
||||
child: Stack(
|
||||
children: [
|
||||
CachedNetworkImage(
|
||||
imageUrl: contact.avatarUrl,
|
||||
width: 50,
|
||||
height: 50,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Icon(Icons.group,size: 18,)),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: CircleAvatar(
|
||||
backgroundImage: CachedNetworkImageProvider(
|
||||
contact.avatarUrl,
|
||||
),
|
||||
radius: 24,
|
||||
),
|
||||
title: Text(contact.name),
|
||||
@@ -46,6 +70,13 @@ class ChatListPage extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
onTap: () => _openChat(context, contact),
|
||||
),
|
||||
Divider(
|
||||
thickness: 0.3,
|
||||
height: 0.1,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@@ -2,7 +2,9 @@ import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:caller/app/models/chatModels/chatMeesageModel.dart';
|
||||
import 'package:caller/app/models/contactModels/contactModels.dart';
|
||||
import 'package:caller/app/models/contactModels/groupContactsModel.dart';
|
||||
import 'package:caller/app/modules/chat/MessageModule/controllers/chatController.dart';
|
||||
import 'package:caller/app/modules/chat/MessageModule/views/messageChatViews/chatInfoPage.dart';
|
||||
import 'package:caller/app/modules/chat/contactModule/contactViews/ContactPage/contactInfo.dart';
|
||||
import 'package:caller/app/modules/chat/MessageModule/views/messageChatViews/fullscreenChatPlay.dart';
|
||||
import 'package:caller/app/modules/chat/MessageModule/widgets/animated_wave.dart';
|
||||
@@ -18,6 +20,9 @@ import 'package:open_filex/open_filex.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:mime/mime.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
class ChatMessageScreen extends StatefulWidget {
|
||||
final ChatContactModel contact;
|
||||
ChatMessageScreen({Key? key, required this.contact}) : super(key: key);
|
||||
@@ -27,7 +32,8 @@ class ChatMessageScreen extends StatefulWidget {
|
||||
State<ChatMessageScreen> createState() => _ChatMessageScreenState();
|
||||
}
|
||||
|
||||
class _ChatMessageScreenState extends State<ChatMessageScreen> with TickerProviderStateMixin {
|
||||
class _ChatMessageScreenState extends State<ChatMessageScreen>
|
||||
with TickerProviderStateMixin {
|
||||
final FocusNode _focusNode = FocusNode();
|
||||
late AnimationController _emojiController;
|
||||
late Animation<Offset> _emojiOffset;
|
||||
@@ -96,7 +102,6 @@ void _updateRecordingDialog(String text, Color color) {
|
||||
(recordingDialogContext as Element).markNeedsBuild();
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -124,7 +129,23 @@ void _updateRecordingDialog(String text, Color color) {
|
||||
|
||||
if (photo != null) {
|
||||
final file = File(photo.path);
|
||||
widget.chatController.sendFileMessage(file);
|
||||
|
||||
//backend live-kit
|
||||
// widget.chatController.sendFileMessage(file);
|
||||
|
||||
final tempMsg = ChatMessageModel(
|
||||
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
text: '[Picked file: ${p.basename(file.path)}]',
|
||||
fileName: p.basename(file.path),
|
||||
mimeType: lookupMimeType(file.path) ?? 'application/octet-stream',
|
||||
fileUrl: file.path,
|
||||
participantIdentity: 'Khan',
|
||||
timestamp: DateTime.now(),
|
||||
isMe: true,
|
||||
isPrivate: false,
|
||||
);
|
||||
widget.chatController.messages.add(tempMsg); // Add file to UI immediately
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +161,25 @@ void _updateRecordingDialog(String text, Color color) {
|
||||
if (assets != null && assets.isNotEmpty) {
|
||||
final file = await assets.first.file;
|
||||
if (file != null) {
|
||||
widget.chatController.sendFileMessage(file);
|
||||
//backend live-kit
|
||||
// widget.chatController.sendFileMessage(file);
|
||||
|
||||
// Temporarily show the file in the UI by directly adding it
|
||||
final tempMsg = ChatMessageModel(
|
||||
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
text: '[Picked file: ${p.basename(file.path)}]',
|
||||
fileName: p.basename(file.path),
|
||||
mimeType: lookupMimeType(file.path) ?? 'application/octet-stream',
|
||||
fileUrl: file.path,
|
||||
participantIdentity: 'Khan',
|
||||
timestamp: DateTime.now(),
|
||||
isMe: true,
|
||||
isPrivate: false,
|
||||
);
|
||||
widget.chatController.messages.add(
|
||||
tempMsg,
|
||||
); // Add file to UI immediately
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,16 +194,54 @@ void _updateRecordingDialog(String text, Color color) {
|
||||
final picked = result.files.first;
|
||||
if (picked.path != null) {
|
||||
final file = File(picked.path!);
|
||||
widget.chatController.sendFileMessage(file);
|
||||
|
||||
//backend live-kit
|
||||
// widget.chatController.sendFileMessage(file);
|
||||
|
||||
// Temporarily show the file in the UI by directly adding it
|
||||
final tempMsg = ChatMessageModel(
|
||||
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
text: '[Picked file: ${p.basename(file.path)}]',
|
||||
fileName: p.basename(file.path),
|
||||
mimeType: lookupMimeType(file.path) ?? 'application/octet-stream',
|
||||
fileUrl: file.path,
|
||||
participantIdentity: 'Khan',
|
||||
timestamp: DateTime.now(),
|
||||
isMe: true,
|
||||
isPrivate: false,
|
||||
);
|
||||
widget.chatController.messages.add(
|
||||
tempMsg,
|
||||
); // Add file to UI immediately
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//filter query
|
||||
String searchQuery = "";
|
||||
|
||||
// Function to filter the messages
|
||||
List<ChatMessageModel> _getFilteredMessages() {
|
||||
if (searchQuery.isEmpty) {
|
||||
return widget.chatController.messages;
|
||||
} else {
|
||||
return widget.chatController.messages
|
||||
.where(
|
||||
(msg) => msg.text.toLowerCase().contains(searchQuery.toLowerCase()),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
FocusScope.of(context).unfocus();
|
||||
widget.chatController.isEmojiPickerVisible.value = false;
|
||||
_emojiController.reverse();
|
||||
widget.chatController.isAttachmentPanelVisible.value = false;
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
@@ -177,23 +254,56 @@ void _updateRecordingDialog(String text, Color color) {
|
||||
centerTitle: true,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.more_vert,color: Colors.white,),
|
||||
icon: const Icon(Icons.more_vert, color: Colors.white),
|
||||
onPressed: () {
|
||||
Navigator.push(context, MaterialPageRoute(builder: (context)=> ContactInfoPage(contact: widget.contact),));
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ChatInfoPage(
|
||||
contact: widget.contact,
|
||||
|
||||
groupDetails: widget.contact.isGroup
|
||||
? dummyGroupDetails.firstWhere(
|
||||
(g) => g.groupId == widget.contact.id,
|
||||
orElse: () => GroupDetails(
|
||||
groupId: widget.contact.id,
|
||||
members: [],
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
title: Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
backgroundImage: CachedNetworkImageProvider(widget.contact.avatarUrl),
|
||||
widget.contact.isGroup
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: SizedBox(
|
||||
height: 50,
|
||||
width: 50,
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: widget.contact.avatarUrl,
|
||||
width: 50,
|
||||
height: 50,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
)
|
||||
: CircleAvatar(
|
||||
backgroundImage: CachedNetworkImageProvider(
|
||||
widget.contact.avatarUrl,
|
||||
),
|
||||
radius: 24,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(widget.contact.name,style: TextStyle(color: Colors.white),),
|
||||
SizedBox(width: 16),
|
||||
Text(widget.contact.name, style: TextStyle(color: Colors.white)),
|
||||
],
|
||||
),
|
||||
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
@@ -521,12 +631,18 @@ void _updateRecordingDialog(String text, Color color) {
|
||||
if (dragOffset < -50) {
|
||||
if (!isCancelled) {
|
||||
isCancelled = true;
|
||||
_updateRecordingDialog("Release to cancel", Colors.redAccent);
|
||||
_updateRecordingDialog(
|
||||
"Release to cancel",
|
||||
Colors.redAccent,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (isCancelled) {
|
||||
isCancelled = false;
|
||||
_updateRecordingDialog("Slide up to cancel", Colors.white70);
|
||||
_updateRecordingDialog(
|
||||
"Slide up to cancel",
|
||||
Colors.white70,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -545,14 +661,18 @@ void _updateRecordingDialog(String text, Color color) {
|
||||
color: Colors.grey.withOpacity(0.25),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
child: Obx(() => Text(
|
||||
child: Obx(
|
||||
() => Text(
|
||||
"Hold to talk",
|
||||
style: TextStyle(
|
||||
color: widget.chatController.isRecording.value ? Colors.red : Colors.white,
|
||||
color: widget.chatController.isRecording.value
|
||||
? Colors.red
|
||||
: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
)),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
@@ -586,8 +706,20 @@ void _updateRecordingDialog(String text, Color color) {
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
),
|
||||
onSubmitted: (text) =>
|
||||
widget.chatController.sendMessage(text),
|
||||
onSubmitted: (text) {
|
||||
final newMessage = ChatMessageModel(
|
||||
text: text.trim(),
|
||||
isMe: true,
|
||||
timestamp: DateTime.now(),
|
||||
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
participantIdentity: 'Khan',
|
||||
);
|
||||
|
||||
widget.chatController.messages.add(newMessage);
|
||||
setState(() {});
|
||||
|
||||
// widget.chatController.sendMessage(text);
|
||||
},
|
||||
);
|
||||
}
|
||||
}),
|
||||
@@ -620,9 +752,22 @@ void _updateRecordingDialog(String text, Color color) {
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: hasText
|
||||
? () => widget.chatController.sendMessage(
|
||||
widget.chatController.textController.text,
|
||||
)
|
||||
? () {
|
||||
final newMessage = ChatMessageModel(
|
||||
text: widget.chatController.textController.text
|
||||
.trim(),
|
||||
isMe: true,
|
||||
timestamp: DateTime.now(),
|
||||
id: DateTime.now().millisecondsSinceEpoch
|
||||
.toString(),
|
||||
participantIdentity: 'Khan',
|
||||
);
|
||||
widget.chatController.messages.add(newMessage);
|
||||
|
||||
// widget.chatController.sendMessage(
|
||||
// widget.chatController.textController.text,
|
||||
// );
|
||||
}
|
||||
: () {
|
||||
FocusScope.of(context).unfocus();
|
||||
widget.chatController.isEmojiPickerVisible.value =
|
||||
@@ -657,7 +802,11 @@ Widget _buildAttachmentPanel() {
|
||||
children: [
|
||||
_buildAttachmentButton(Icons.camera_alt, 'Camera', _openCamera),
|
||||
_buildAttachmentButton(Icons.image, 'Gallery', _pickFromGallery),
|
||||
_buildAttachmentButton(Icons.insert_drive_file, 'Document', _pickDocument),
|
||||
_buildAttachmentButton(
|
||||
Icons.insert_drive_file,
|
||||
'Document',
|
||||
_pickDocument,
|
||||
),
|
||||
_buildAttachmentButton(Icons.video_call, 'Call', _showCallOptions),
|
||||
],
|
||||
),
|
||||
@@ -701,7 +850,10 @@ void _showCallOptions() {
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Text('Make a call', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
child: Text(
|
||||
'Make a call',
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.call, color: Colors.green),
|
||||
@@ -727,16 +879,13 @@ void _showCallOptions() {
|
||||
}
|
||||
|
||||
void _startAudioCall() {
|
||||
// TODO: your audio call logic
|
||||
print("Starting audio call...");
|
||||
}
|
||||
|
||||
void _startVideoCall() {
|
||||
// TODO: your video call logic
|
||||
print("Starting video call...");
|
||||
}
|
||||
|
||||
|
||||
Widget _buildEmojiPickerContainer() {
|
||||
return Obx(() {
|
||||
if (!widget.chatController.isEmojiPickerVisible.value) {
|
||||
|
@@ -0,0 +1,102 @@
|
||||
// import 'package:flutter/material.dart';
|
||||
|
||||
// class DottedBorderBox extends StatelessWidget {
|
||||
// final Widget child;
|
||||
|
||||
// DottedBorderBox({required this.child});
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return CustomPaint(
|
||||
// painter: _DottedBorderPainter(),
|
||||
// child: child,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// class _DottedBorderPainter extends CustomPainter {
|
||||
// @override
|
||||
// void paint(Canvas canvas, Size size) {
|
||||
// final paint = Paint()
|
||||
// ..color = Colors.white ..strokeWidth = 2
|
||||
// ..style = PaintingStyle.stroke;
|
||||
|
||||
// final dashWidth = 4.0;
|
||||
// final gapWidth = 4.0;
|
||||
|
||||
// // Draw top border
|
||||
// double dashCount = (size.width / (dashWidth + gapWidth)).floorToDouble();
|
||||
// for (double i = 0; i < dashCount; i++) {
|
||||
// final startX = i * (dashWidth + gapWidth);
|
||||
// final endX = startX + dashWidth;
|
||||
// canvas.drawLine(
|
||||
// Offset(startX, 0),
|
||||
// Offset(endX, 0),
|
||||
// paint,
|
||||
// );
|
||||
// }
|
||||
|
||||
// // Draw bottom border
|
||||
// for (double i = 0; i < dashCount; i++) {
|
||||
// final startX = i * (dashWidth + gapWidth);
|
||||
// final endX = startX + dashWidth;
|
||||
// canvas.drawLine(
|
||||
// Offset(startX, size.height),
|
||||
// Offset(endX, size.height),
|
||||
// paint,
|
||||
// );
|
||||
// }
|
||||
|
||||
// // Draw left border
|
||||
// double dashCountVertical = (size.height / (dashWidth + gapWidth)).floorToDouble();
|
||||
// for (double i = 0; i < dashCountVertical; i++) {
|
||||
// final startY = i * (dashWidth + gapWidth);
|
||||
// final endY = startY + dashWidth;
|
||||
// canvas.drawLine(
|
||||
// Offset(0, startY),
|
||||
// Offset(0, endY),
|
||||
// paint,
|
||||
// );
|
||||
// }
|
||||
|
||||
// // Draw right border
|
||||
// for (double i = 0; i < dashCountVertical; i++) {
|
||||
// final startY = i * (dashWidth + gapWidth);
|
||||
// final endY = startY + dashWidth;
|
||||
// canvas.drawLine(
|
||||
// Offset(size.width, startY),
|
||||
// Offset(size.width, endY),
|
||||
// paint,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// @override
|
||||
// bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||
// }
|
||||
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:dotted_decoration/dotted_decoration.dart';
|
||||
|
||||
class DottedBorderBox extends StatelessWidget {
|
||||
final Widget child;
|
||||
|
||||
const DottedBorderBox({required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: DottedDecoration(
|
||||
color: Theme.of(context).textTheme.bodyLarge!.color!,
|
||||
strokeWidth: 1,
|
||||
dash: [4, 4],
|
||||
shape: Shape.box,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: SizedBox.expand(
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -28,6 +28,7 @@ class ContactInfoPage extends StatelessWidget {
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: Text('联系方式'),
|
||||
@@ -39,12 +40,12 @@ class ContactInfoPage extends StatelessWidget {
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
_buildProfileHeader(),
|
||||
_buildProfileHeader(context),
|
||||
Divider(height: 1, thickness: 0.5),
|
||||
_buildInfoSection(context),
|
||||
Divider(height: 1, thickness: 0.5),
|
||||
SizedBox(height: 20,),
|
||||
_buildActionButtons(),
|
||||
_buildActionButtons(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -54,7 +55,7 @@ class ContactInfoPage extends StatelessWidget {
|
||||
}
|
||||
|
||||
// Profile header including avatar, name, online status and status message
|
||||
Widget _buildProfileHeader() {
|
||||
Widget _buildProfileHeader(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Row(
|
||||
@@ -75,18 +76,25 @@ class ContactInfoPage extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
contact.name,
|
||||
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
|
||||
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold,
|
||||
|
||||
color: Theme.of(context).textTheme.bodyLarge!.color
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
"Weixin ID:${contact.id}",
|
||||
style: TextStyle(fontSize: 14),
|
||||
style: TextStyle(fontSize: 14,
|
||||
color: Theme.of(context).textTheme.bodyLarge!.color
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
"Region:${contact.region}",
|
||||
style: TextStyle(fontSize: 14),
|
||||
style: TextStyle(fontSize: 14,
|
||||
color: Theme.of(context).textTheme.bodyLarge!.color
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
@@ -152,7 +160,7 @@ class ContactInfoPage extends StatelessWidget {
|
||||
Text("11",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black,
|
||||
color: Theme.of(context).textTheme.bodyLarge!.color
|
||||
),
|
||||
)
|
||||
],),
|
||||
@@ -168,7 +176,7 @@ class ContactInfoPage extends StatelessWidget {
|
||||
Text("Scan QR Code",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black,
|
||||
color: Theme.of(context).textTheme.bodyLarge!.color
|
||||
),
|
||||
|
||||
)
|
||||
@@ -180,29 +188,33 @@ class ContactInfoPage extends StatelessWidget {
|
||||
}
|
||||
|
||||
// Action buttons for messages, voice call, video call, etc.
|
||||
Widget _buildActionButtons() {
|
||||
Widget _buildActionButtons(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_buildActionButton(Icons.message, 'Messages'),
|
||||
_buildActionButton(Icons.phone, 'Voice Call'),
|
||||
_buildActionButton(Icons.video_call, 'Video Call'),
|
||||
_buildActionButton(Icons.message, 'Messages',context),
|
||||
_buildActionButton(Icons.phone, 'Voice Call',context),
|
||||
_buildActionButton(Icons.video_call, 'Video Call',context),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Helper widget for action buttons
|
||||
Widget _buildActionButton(IconData icon, String label) {
|
||||
Widget _buildActionButton(IconData icon, String label,BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(icon, color: Colors.green),
|
||||
onPressed: () {},
|
||||
),
|
||||
Text(label),
|
||||
Text(label,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).textTheme.bodyLarge!.color
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@@ -1,115 +1,39 @@
|
||||
import 'dart:async';
|
||||
import 'package:caller/app/constants/constants.dart';
|
||||
import 'package:caller/app/models/contactModels/contactModels.dart';
|
||||
import 'package:caller/app/modules/chat/contactModule/contactViews/ContactPage/contactInfo.dart';
|
||||
import 'package:caller/app/modules/chat/contactModule/controllers/contactController.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:caller/app/models/contactModels/contactModels.dart';
|
||||
|
||||
// === VIEWMODEL ===
|
||||
class ContactsViewModel extends GetxController {
|
||||
final RxList<ChatContactModel> contacts = <ChatContactModel>[].obs;
|
||||
final RxList<ContactItem> functionButtons = <ContactItem>[].obs;
|
||||
final RxString currentLetter = ''.obs;
|
||||
|
||||
Timer? _letterTimer;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
_initializeMockData();
|
||||
}
|
||||
|
||||
void _initializeMockData() {
|
||||
functionButtons.value = [
|
||||
ContactItem(avatar: '${contactAssets}ic_new_friend.webp', title: '新的朋友'),
|
||||
ContactItem(avatar: '${contactAssets}ic_group.webp', title: '群聊'),
|
||||
ContactItem(avatar: '${contactAssets}ic_tag.webp', title: '标签'),
|
||||
ContactItem(avatar: '${contactAssets}ic_no_public.webp', title: '公众号'),
|
||||
];
|
||||
|
||||
contacts.value = dummyContacts; // your dummy contacts list
|
||||
contacts.sort((a, b) => a.nameIndex!.compareTo(b.nameIndex!));
|
||||
}
|
||||
|
||||
/// Jump scroll to letter header inside contacts list.
|
||||
/// [keys] maps letters to GlobalKeys of header widgets.
|
||||
void jumpToIndex(
|
||||
String letter,
|
||||
ScrollController controller,
|
||||
Map<String, GlobalKey> keys,
|
||||
) {
|
||||
if (!keys.containsKey(letter)) return;
|
||||
|
||||
final keyContext = keys[letter]!.currentContext;
|
||||
if (keyContext != null) {
|
||||
final box = keyContext.findRenderObject() as RenderBox;
|
||||
final viewport =
|
||||
controller.position.context.storageContext.findRenderObject()
|
||||
as RenderBox;
|
||||
|
||||
// Offset of header relative to the viewport top
|
||||
final offset = box.localToGlobal(Offset.zero, ancestor: viewport).dy;
|
||||
|
||||
// Calculate absolute scroll offset + height of functionButtons area (to compensate)
|
||||
// We calculate functionButtons height assuming each ListTile is 56 pixels (default)
|
||||
final functionButtonsHeight = functionButtons.length * 56.0;
|
||||
|
||||
final targetScrollOffset =
|
||||
controller.offset + offset - functionButtonsHeight;
|
||||
|
||||
// Animate scroll to position
|
||||
controller.animateTo(
|
||||
targetScrollOffset.clamp(0, controller.position.maxScrollExtent),
|
||||
duration: Duration(milliseconds: 300),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
}
|
||||
currentLetter.value = letter;
|
||||
_resetLetterTimer();
|
||||
}
|
||||
|
||||
void _resetLetterTimer() {
|
||||
_letterTimer?.cancel();
|
||||
_letterTimer = Timer(Duration(milliseconds: 800), () {
|
||||
currentLetter.value = '';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// === PAGE ===
|
||||
class ContactsPage extends StatelessWidget {
|
||||
final ScrollController sC = ScrollController();
|
||||
final Map<String, GlobalKey> letterKeys = {};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final viewModel = Get.put(ContactsViewModel());
|
||||
// Access ContactController
|
||||
final contactController = Get.find<ContactController>();
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade100,
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
body: Stack(
|
||||
children: [
|
||||
Obx(() {
|
||||
// total items = function buttons + contacts
|
||||
final totalCount =
|
||||
viewModel.functionButtons.length + viewModel.contacts.length;
|
||||
final totalCount = contactController.functionButtons.length + contactController.contacts.length;
|
||||
return ListView.builder(
|
||||
controller: sC,
|
||||
itemCount: totalCount,
|
||||
itemBuilder: (context, index) {
|
||||
if (index < viewModel.functionButtons.length) {
|
||||
if (index < contactController.functionButtons.length) {
|
||||
// Show function buttons first
|
||||
final item = viewModel.functionButtons[index];
|
||||
return _buildFunctionButton(item);
|
||||
final item = contactController.functionButtons[index];
|
||||
return _buildFunctionButton(item,context);
|
||||
} else {
|
||||
// Show contacts after function buttons
|
||||
final contactIndex = index - viewModel.functionButtons.length;
|
||||
final contact = viewModel.contacts[contactIndex];
|
||||
final contactIndex = index - contactController.functionButtons.length;
|
||||
final contact = contactController.contacts[contactIndex];
|
||||
|
||||
final showHeader =
|
||||
contactIndex == 0 ||
|
||||
contact.nameIndex !=
|
||||
viewModel.contacts[contactIndex - 1].nameIndex;
|
||||
final showHeader = contactIndex == 0 ||
|
||||
contact.nameIndex != contactController.contacts[contactIndex - 1].nameIndex;
|
||||
|
||||
final letterKey = showHeader
|
||||
? letterKeys.putIfAbsent(
|
||||
@@ -138,13 +62,11 @@ class ContactsPage extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Container(
|
||||
color: Colors.white,
|
||||
color: Theme.of(context).cardColor,
|
||||
child: ListTile(
|
||||
leading: CircleAvatar(
|
||||
radius: 24,
|
||||
backgroundImage: CachedNetworkImageProvider(
|
||||
contact.avatarUrl,
|
||||
),
|
||||
backgroundImage: CachedNetworkImageProvider(contact.avatarUrl),
|
||||
),
|
||||
title: Text(
|
||||
contact.name,
|
||||
@@ -153,22 +75,14 @@ class ContactsPage extends StatelessWidget {
|
||||
subtitle: contact.statusMessage != null
|
||||
? Text(contact.statusMessage!)
|
||||
: null,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 2,
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
ContactInfoPage(contact: contact),
|
||||
),
|
||||
);
|
||||
Navigator.push(context, MaterialPageRoute(builder: (_)=>ContactInfoPage(contact: contact,)));
|
||||
},
|
||||
),
|
||||
),
|
||||
Divider(height: 1),
|
||||
Divider(thickness: 0.3,
|
||||
height: 0.1, color: Theme.of(context).dividerColor),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -180,59 +94,62 @@ class ContactsPage extends StatelessWidget {
|
||||
top: 100,
|
||||
bottom: 100,
|
||||
width: 24,
|
||||
child: _buildIndexBar(context, viewModel),
|
||||
child: _buildIndexBar(context, contactController),
|
||||
),
|
||||
Obx(() => _buildCurrentLetterIndicator(viewModel)),
|
||||
Obx(() => _buildCurrentLetterIndicator(contactController)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFunctionButton(ContactItem item) {
|
||||
// Build function button UI
|
||||
Widget _buildFunctionButton(ContactItem item,BuildContext context) {
|
||||
return Container(
|
||||
color: Colors.white,
|
||||
color: Theme.of(context).cardColor,
|
||||
child: ListTile(
|
||||
leading: Image.asset(item.avatar, width: 36, height: 36),
|
||||
title: Text(item.title),
|
||||
title: Text(item.title.tr),
|
||||
onTap: () {},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIndexBar(BuildContext context, ContactsViewModel viewModel) {
|
||||
// Index bar on the right side
|
||||
Widget _buildIndexBar(BuildContext context, ContactController contactController) {
|
||||
return GestureDetector(
|
||||
onVerticalDragUpdate: (details) =>
|
||||
_handleDrag(details.localPosition.dy, context, viewModel),
|
||||
_handleDrag(details.localPosition.dy, context, contactController),
|
||||
onVerticalDragStart: (details) =>
|
||||
_handleDrag(details.localPosition.dy, context, viewModel),
|
||||
_handleDrag(details.localPosition.dy, context, contactController),
|
||||
onTapDown: (details) =>
|
||||
_handleDrag(details.localPosition.dy, context, viewModel),
|
||||
_handleDrag(details.localPosition.dy, context, contactController),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: INDEX_BAR_WORDS
|
||||
.map(
|
||||
(e) =>
|
||||
Text(e, style: TextStyle(fontSize: 12, color: Colors.grey)),
|
||||
(e) => Text(e, style: TextStyle(fontSize: 12, color: Colors.grey)),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Handle drag events when the user drags on the index bar
|
||||
void _handleDrag(
|
||||
double localDy,
|
||||
BuildContext context,
|
||||
ContactsViewModel viewModel,
|
||||
ContactController contactController,
|
||||
) {
|
||||
final box = context.findRenderObject() as RenderBox;
|
||||
final tileHeight = box.size.height / INDEX_BAR_WORDS.length;
|
||||
final index = (localDy ~/ tileHeight).clamp(0, INDEX_BAR_WORDS.length - 1);
|
||||
final letter = INDEX_BAR_WORDS[index];
|
||||
viewModel.jumpToIndex(letter, sC, letterKeys);
|
||||
contactController.jumpToIndex(letter, sC, letterKeys);
|
||||
}
|
||||
|
||||
Widget _buildCurrentLetterIndicator(ContactsViewModel viewModel) {
|
||||
if (viewModel.currentLetter.value.isEmpty) return SizedBox.shrink();
|
||||
// Display current letter indicator when dragging
|
||||
Widget _buildCurrentLetterIndicator(ContactController contactController) {
|
||||
if (contactController.currentLetter.value.isEmpty) return SizedBox.shrink();
|
||||
return Center(
|
||||
child: Container(
|
||||
width: 80,
|
||||
@@ -243,7 +160,7 @@ class ContactsPage extends StatelessWidget {
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
viewModel.currentLetter.value,
|
||||
contactController.currentLetter.value,
|
||||
style: TextStyle(
|
||||
fontSize: 32,
|
||||
color: Colors.white,
|
||||
@@ -257,30 +174,5 @@ class ContactsPage extends StatelessWidget {
|
||||
|
||||
// === MOCK INDEX BAR WORDS ===
|
||||
const List<String> INDEX_BAR_WORDS = [
|
||||
'A',
|
||||
'B',
|
||||
'C',
|
||||
'D',
|
||||
'E',
|
||||
'F',
|
||||
'G',
|
||||
'H',
|
||||
'I',
|
||||
'J',
|
||||
'K',
|
||||
'L',
|
||||
'M',
|
||||
'N',
|
||||
'O',
|
||||
'P',
|
||||
'Q',
|
||||
'R',
|
||||
'S',
|
||||
'T',
|
||||
'U',
|
||||
'V',
|
||||
'W',
|
||||
'X',
|
||||
'Y',
|
||||
'Z',
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||
];
|
||||
|
@@ -1,308 +0,0 @@
|
||||
// import 'package:caller/app/constants/constants.dart';
|
||||
// import 'package:flutter/cupertino.dart';
|
||||
// import 'package:flutter/material.dart';
|
||||
// import 'package:get/get.dart';
|
||||
|
||||
// class ChatInfoPage extends StatefulWidget {
|
||||
// // final String id;
|
||||
|
||||
// // ChatInfoPage(this.id);
|
||||
|
||||
// @override
|
||||
// _ChatInfoPageState createState() => _ChatInfoPageState();
|
||||
// }
|
||||
|
||||
// class _ChatInfoPageState extends State<ChatInfoPage> {
|
||||
|
||||
// bool isRemind = false;
|
||||
// bool isTop = false;
|
||||
// bool isDoNotDisturb = true;
|
||||
|
||||
// Widget buildSwitch(item) {
|
||||
// return new LabelRow(
|
||||
// label: item['label'] as String,
|
||||
// margin: item['label'] == '消息免打扰' ? EdgeInsets.only(top: 10.0) : null,
|
||||
// isLine: item['label'] != '强提醒',
|
||||
// isRight: false,
|
||||
// rightW: new SizedBox(
|
||||
// height: 25.0,
|
||||
// child: new CupertinoSwitch(
|
||||
// value: item['value'] as bool,
|
||||
// onChanged: (v) {},
|
||||
// ),
|
||||
// ),
|
||||
// onPressed: () {},
|
||||
// );
|
||||
// }
|
||||
|
||||
// List<Widget> body() {
|
||||
// List switchItems = [
|
||||
// {"label": '消息免打扰', 'value': isDoNotDisturb},
|
||||
// {"label": '置顶聊天', 'value': isTop},
|
||||
// {"label": '强提醒', 'value': isRemind},
|
||||
// ];
|
||||
|
||||
// return [
|
||||
// // new ChatMamBer(model: model),
|
||||
// new LabelRow(
|
||||
// label: '查找聊天记录',
|
||||
// margin: EdgeInsets.only(top: 10.0),
|
||||
// // onPressed: () => Get.to<void>(new SearchPage()),
|
||||
// ),
|
||||
// new Column(
|
||||
// children: switchItems.map(buildSwitch).toList(),
|
||||
// ),
|
||||
// new LabelRow(
|
||||
// label: '设置当前聊天背景',
|
||||
// margin: EdgeInsets.only(top: 10.0),
|
||||
// // onPressed: () => Get.to<void>(new ChatBackgroundPage()),
|
||||
// ),
|
||||
// new LabelRow(
|
||||
// label: '清空聊天记录',
|
||||
// margin: EdgeInsets.only(top: 10.0),
|
||||
// // onPressed: () {
|
||||
// // // confirmAlert(
|
||||
// // // context,
|
||||
// // // (isOK) {
|
||||
// // // if (isOK) showToast('敬请期待');
|
||||
// // },
|
||||
// // tips: '确定删除群的聊天记录吗?',
|
||||
// // okBtn: '清空',
|
||||
// // );
|
||||
// // },
|
||||
// ),
|
||||
// new LabelRow(
|
||||
// label: '投诉',
|
||||
// margin: EdgeInsets.only(top: 10.0),
|
||||
// // onPressed: () =>
|
||||
// // Get.to<void>(new WebViewPage(url: helpUrl, title: '投诉')),
|
||||
// ),
|
||||
// ];
|
||||
// }
|
||||
|
||||
// @override
|
||||
// void initState() {
|
||||
// super.initState();
|
||||
// getInfo();
|
||||
// }
|
||||
|
||||
// Future<void> getInfo() async {
|
||||
// // final List<V2TimUserFullInfo> infoList = await getUsersProfile([widget.id]);
|
||||
// // if (infoList.isEmpty) {
|
||||
// // // showToast('获取用户信息错误');
|
||||
// // return;
|
||||
// // }
|
||||
// setState(() {
|
||||
// // model = infoList[0];
|
||||
// });
|
||||
// }
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return new Scaffold(
|
||||
// backgroundColor: chatBg,
|
||||
// appBar: new ComMomBar(title: '聊天信息'),
|
||||
// body: new SingleChildScrollView(
|
||||
// child: new Column(children: body()),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// class LabelRow extends StatelessWidget {
|
||||
// final String? label;
|
||||
// final VoidCallback? onPressed;
|
||||
// final double? labelWidth;
|
||||
// final bool isRight;
|
||||
// final bool isLine;
|
||||
// final String? value;
|
||||
// final String? rValue;
|
||||
// final Widget? rightW;
|
||||
// final EdgeInsetsGeometry? margin;
|
||||
// final EdgeInsetsGeometry padding;
|
||||
// final Widget? headW;
|
||||
// final double lineWidth;
|
||||
|
||||
// LabelRow({
|
||||
// this.label,
|
||||
// this.onPressed,
|
||||
// this.value,
|
||||
// this.labelWidth,
|
||||
// this.isRight = true,
|
||||
// this.isLine = false,
|
||||
// this.rightW,
|
||||
// this.rValue,
|
||||
// this.margin,
|
||||
// this.padding = const EdgeInsets.only(top: 15.0, bottom: 15.0, right: 5.0),
|
||||
// this.headW,
|
||||
// this.lineWidth = mainLineWidth,
|
||||
// });
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return Container(
|
||||
// margin: margin,
|
||||
// child: TextButton(
|
||||
// style: TextButton.styleFrom(
|
||||
// backgroundColor: Colors.white,
|
||||
// padding: EdgeInsets.all(0),
|
||||
// ),
|
||||
// onPressed: onPressed ?? () {},
|
||||
// child: Container(
|
||||
// padding: padding,
|
||||
// margin: EdgeInsets.only(left: 20.0),
|
||||
// decoration: BoxDecoration(
|
||||
// border: isLine
|
||||
// ? Border(bottom: BorderSide(color: lineColor, width: lineWidth))
|
||||
// : null,
|
||||
// ),
|
||||
// child: Row(
|
||||
// children: <Widget>[
|
||||
// if (headW != null) headW!,
|
||||
// SizedBox(
|
||||
// width: labelWidth,
|
||||
// child: Text(
|
||||
// label ?? '',
|
||||
// style: TextStyle(fontSize: 17.0),
|
||||
// ),
|
||||
// ),
|
||||
// if (value != null)
|
||||
// Text(
|
||||
// value!,
|
||||
// style: TextStyle(
|
||||
// color: mainTextColor.withOpacity(0.7),
|
||||
// ),
|
||||
// ),
|
||||
// Spacer(),
|
||||
// if (rValue != null)
|
||||
// Text(
|
||||
// rValue!,
|
||||
// style: TextStyle(
|
||||
// color: mainTextColor.withOpacity(0.7),
|
||||
// fontWeight: FontWeight.w400,
|
||||
// ),
|
||||
// ),
|
||||
// if (rightW != null) rightW!,
|
||||
// if (isRight)
|
||||
// Icon(
|
||||
// CupertinoIcons.right_chevron,
|
||||
// color: mainTextColor.withOpacity(0.5),
|
||||
// )
|
||||
// else
|
||||
// Container(width: 10.0),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// class ComMomBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
// const ComMomBar({
|
||||
// Key? key,
|
||||
// this.title = '',
|
||||
// this.showShadow = false,
|
||||
// this.rightDMActions,
|
||||
// this.backgroundColor = appBarColor,
|
||||
// this.mainColor = Colors.black,
|
||||
// this.titleW,
|
||||
// this.bottom,
|
||||
// this.leadingImg = '',
|
||||
// this.leadingW,
|
||||
// }) : super(key: key);
|
||||
|
||||
// final String title;
|
||||
// final bool showShadow;
|
||||
// final List<Widget>? rightDMActions;
|
||||
// final Color backgroundColor;
|
||||
// final Color mainColor;
|
||||
// final Widget? titleW;
|
||||
// final Widget? leadingW;
|
||||
// final PreferredSizeWidget? bottom;
|
||||
// final String leadingImg;
|
||||
|
||||
// @override
|
||||
// Size get preferredSize => const Size.fromHeight(50.0);
|
||||
|
||||
// Widget? leading(BuildContext context) {
|
||||
// final bool isShow = Navigator.canPop(context);
|
||||
// if (isShow) {
|
||||
// return InkWell(
|
||||
// child: Container(
|
||||
// width: 15,
|
||||
// height: 28,
|
||||
// child: leadingImg.isNotEmpty
|
||||
// ? Image.asset(leadingImg)
|
||||
// : Icon(CupertinoIcons.back, color: mainColor),
|
||||
// ),
|
||||
// onTap: () {
|
||||
// if (Navigator.canPop(context)) {
|
||||
// FocusScope.of(context).requestFocus(FocusNode());
|
||||
// Navigator.pop(context);
|
||||
// }
|
||||
// },
|
||||
// );
|
||||
// } else {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return showShadow
|
||||
// ? Container(
|
||||
// decoration: BoxDecoration(
|
||||
// border: Border(
|
||||
// bottom: BorderSide(color: Colors.grey, width: showShadow ? 0.5 : 0.0),
|
||||
// ),
|
||||
// ),
|
||||
// child: AppBar(
|
||||
// title: titleW ?? Text(
|
||||
// title,
|
||||
// style: TextStyle(
|
||||
// color: mainColor,
|
||||
// fontSize: 17.0,
|
||||
// fontWeight: FontWeight.w600,
|
||||
// ),
|
||||
// ),
|
||||
// backgroundColor: mainColor,
|
||||
// elevation: 0.0,
|
||||
// // brightness: Brightness.light,
|
||||
// leading: leadingW ?? leading(context),
|
||||
// centerTitle: true,
|
||||
// actions: rightDMActions ?? [Center()],
|
||||
// bottom: bottom,
|
||||
// ),
|
||||
// )
|
||||
// : AppBar(
|
||||
// title: titleW ?? Text(
|
||||
// title,
|
||||
// style: TextStyle(
|
||||
// color: mainColor,
|
||||
// fontSize: 17.0,
|
||||
// fontWeight: FontWeight.w600,
|
||||
// ),
|
||||
// ),
|
||||
// backgroundColor: backgroundColor,
|
||||
// elevation: 0.0,
|
||||
// // brightness: Brightness.light,
|
||||
// leading: leadingW ?? leading(context),
|
||||
// centerTitle: false,
|
||||
// bottom: bottom,
|
||||
// actions: rightDMActions ?? [Center()],
|
||||
// );
|
||||
// }
|
||||
// }
|
@@ -1,13 +1,11 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:caller/app/constants/constants.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:caller/app/models/contactModels/contactModels.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
|
||||
// === VIEWMODEL ===
|
||||
class ContactsViewModel extends GetxController {
|
||||
import 'package:flutter/material.dart';
|
||||
class ContactController extends GetxController {
|
||||
final RxList<ChatContactModel> contacts = <ChatContactModel>[].obs;
|
||||
final RxList<ContactItem> functionButtons = <ContactItem>[].obs;
|
||||
final RxString currentLetter = ''.obs;
|
||||
|
||||
Timer? _letterTimer;
|
||||
@@ -19,12 +17,23 @@ class ContactsViewModel extends GetxController {
|
||||
}
|
||||
|
||||
void _initializeMockData() {
|
||||
contacts.value = dummyContacts; // your dummy contacts list
|
||||
functionButtons.value = [
|
||||
ContactItem(avatar: '${contactAssets}ic_new_friend.webp', title: 'friends_circle'),
|
||||
ContactItem(avatar: '${contactAssets}ic_group.webp', title: 'scan'),
|
||||
ContactItem(avatar: '${contactAssets}ic_tag.webp', title: 'shake'),
|
||||
ContactItem(avatar: '${contactAssets}ic_no_public.webp', title: 'search'),
|
||||
];
|
||||
|
||||
contacts.value = dummyContacts;
|
||||
contacts.sort((a, b) => a.nameIndex!.compareTo(b.nameIndex!));
|
||||
}
|
||||
|
||||
// Jump scroll to the letter header inside contacts list
|
||||
void jumpToIndex(
|
||||
String letter, ScrollController controller, Map<String, GlobalKey> keys) {
|
||||
String letter,
|
||||
ScrollController controller,
|
||||
Map<String, GlobalKey> keys,
|
||||
) {
|
||||
if (!keys.containsKey(letter)) return;
|
||||
|
||||
final keyContext = keys[letter]!.currentContext;
|
||||
@@ -36,12 +45,14 @@ class ContactsViewModel extends GetxController {
|
||||
// Offset of header relative to the viewport top
|
||||
final offset = box.localToGlobal(Offset.zero, ancestor: viewport).dy;
|
||||
|
||||
// Calculate absolute scroll offset
|
||||
final targetScrollOffset = controller.offset + offset;
|
||||
// Calculate absolute scroll offset + height of functionButtons area (to compensate)
|
||||
final functionButtonsHeight = functionButtons.length * 56.0;
|
||||
|
||||
// Animate scroll to position
|
||||
final targetScrollOffset = controller.offset + offset - functionButtonsHeight;
|
||||
|
||||
// Animate scroll to the calculated position
|
||||
controller.animateTo(
|
||||
targetScrollOffset,
|
||||
targetScrollOffset.clamp(0, controller.position.maxScrollExtent),
|
||||
duration: Duration(milliseconds: 300),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
@@ -50,6 +61,7 @@ class ContactsViewModel extends GetxController {
|
||||
_resetLetterTimer();
|
||||
}
|
||||
|
||||
// Reset current letter after 800ms of inactivity
|
||||
void _resetLetterTimer() {
|
||||
_letterTimer?.cancel();
|
||||
_letterTimer = Timer(Duration(milliseconds: 800), () {
|
||||
|
@@ -0,0 +1,17 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class DiscoverController extends GetxController {
|
||||
// List of menu items with dynamic labels
|
||||
final List<Map<String, String>> menuItems = [
|
||||
{'label': 'friends_circle', 'icon': 'assets/images/discover/ff_Icon_album.webp'},
|
||||
{'label': 'scan', 'icon': 'assets/images/discover/ff_Icon_qr_code.webp'},
|
||||
{'label': 'shake', 'icon': 'assets/images/discover/ff_Icon_shake.webp'},
|
||||
{'label': 'search', 'icon': 'assets/images/discover/ff_Icon_browse.webp'},
|
||||
{'label': 'nearby_people', 'icon': 'assets/images/discover/ff_Icon_search.webp'},
|
||||
{'label': 'drift_bottle', 'icon': 'assets/images/discover/ff_Icon_nearby.webp'},
|
||||
{'label': 'nearby_restaurants', 'icon': 'assets/images/discover/ff_Icon_qr_code.webp'},
|
||||
{'label': 'shopping', 'icon': 'assets/images/discover/ff_Icon_qr_code.webp'},
|
||||
{'label': 'game', 'icon': 'assets/images/discover/game_center_h5.webp'},
|
||||
{'label': 'mini_program', 'icon': 'assets/images/discover/mini_program.webp'},
|
||||
];
|
||||
}
|
@@ -1,24 +1,21 @@
|
||||
import 'package:caller/app/constants/constants.dart';
|
||||
import 'package:caller/app/modules/chat/discoverModule/controller/discover_controller.dart';
|
||||
import 'package:caller/app/modules/chat/discoverModule/widgets/listTileViewWidget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class DiscoverPage extends StatefulWidget {
|
||||
class DiscoverPage extends StatelessWidget {
|
||||
const DiscoverPage({super.key});
|
||||
|
||||
@override
|
||||
State<DiscoverPage> createState() => _DiscoverPageState();
|
||||
}
|
||||
|
||||
class _DiscoverPageState extends State<DiscoverPage> {
|
||||
Widget buildContent(Map<String, String> item) {
|
||||
Widget buildContent(Map<String, String> item,BuildContext context) {
|
||||
bool isShow() {
|
||||
if (item['name'] == '朋友圈' ||
|
||||
item['name'] == '摇一摇' ||
|
||||
item['name'] == '搜一搜' ||
|
||||
item['name'] == '附近的餐厅' ||
|
||||
item['name'] == '游戏' ||
|
||||
item['name'] == '小程序') {
|
||||
// Check the item name and return true/false for whether it should show
|
||||
if (item['label'] == 'friends_circle' ||
|
||||
item['label'] == 'shake' ||
|
||||
item['label'] == 'search' ||
|
||||
item['label'] == 'nearby_restaurants' ||
|
||||
item['label'] == 'game' ||
|
||||
item['label'] == 'mini_program') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -26,23 +23,19 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
}
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.zero,
|
||||
),
|
||||
|
||||
color: Theme.of(context).cardColor,
|
||||
child: ListTileView(
|
||||
title: item['name']!,
|
||||
title: item['label']!.tr, // Use .tr to get the translated label
|
||||
titleStyle: const TextStyle(fontSize: 15.0),
|
||||
isLabel: false,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||
icon: item['icon']!,
|
||||
margin: EdgeInsets.only(bottom: isShow() ? 10.0 : 0.0),
|
||||
onPressed: () {
|
||||
if (item['name'] == '朋友圈') {
|
||||
// Get.to<void>(WeChatFriendsCircle());
|
||||
if (item['label'] == 'friends_circle') {
|
||||
// Handle navigation or action
|
||||
} else {
|
||||
// Get.to<void>(LanguagePage());
|
||||
// Handle other items
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -51,30 +44,23 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<Map<String, String>> data = [
|
||||
{'icon': 'assets/images/discover/ff_Icon_album.webp', 'name': '朋友圈'},
|
||||
{'icon': 'assets/images/discover/ff_Icon_qr_code.webp', 'name': '扫一扫'},
|
||||
{'icon': 'assets/images/discover/ff_Icon_shake.webp', 'name': '摇一摇'},
|
||||
{'icon': 'assets/images/discover/ff_Icon_browse.webp', 'name': '看一看'},
|
||||
{'icon': 'assets/images/discover/ff_Icon_search.webp', 'name': '搜一搜'},
|
||||
{'icon': 'assets/images/discover/ff_Icon_nearby.webp', 'name': '附近的人'},
|
||||
{'icon': 'assets/images/discover/ff_Icon_bottle.webp', 'name': '漂流瓶'},
|
||||
{'icon': 'assets/images/discover/ff_Icon_qr_code.webp', 'name': '附近的餐厅'},
|
||||
{'icon': 'assets/images/discover/ff_Icon_qr_code.webp', 'name': '购物'},
|
||||
{'icon': 'assets/images/discover/game_center_h5.webp', 'name': '游戏'},
|
||||
{'icon': 'assets/images/discover/mini_program.webp', 'name': '小程序'},
|
||||
];
|
||||
final DiscoverController discoverController = Get.find<DiscoverController>();
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: appBarColor,
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
body: ScrollConfiguration(
|
||||
behavior: MyBehavior(),
|
||||
behavior: ScrollBehavior(),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(children: data.map(buildContent).toList()),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Column(
|
||||
children: discoverController.menuItems
|
||||
.map((item) => buildContent(item,context))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyBehavior extends ScrollBehavior {}
|
||||
|
@@ -42,19 +42,18 @@ class ListTileView extends StatelessWidget {
|
||||
if (label != null)
|
||||
Text(
|
||||
label!,
|
||||
style: TextStyle(color: mainTextColor, fontSize: 12),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
var view = [
|
||||
isLabel ? text : Text(title, style: titleStyle),
|
||||
isLabel ? text : Text(title, style: TextStyle(color: Theme.of(context).textTheme.bodyLarge!.color,)),
|
||||
Spacer(),
|
||||
Container(
|
||||
width: 7.0,
|
||||
child: Image.asset(
|
||||
'assets/images/ic_right_arrow_grey.webp',
|
||||
color: mainTextColor.withOpacity(0.5),
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
@@ -78,15 +77,22 @@ class ListTileView extends StatelessWidget {
|
||||
);
|
||||
|
||||
return Container(
|
||||
color: Theme.of(context).cardColor,
|
||||
margin: margin,
|
||||
child: TextButton(
|
||||
child: Column(
|
||||
children: [
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Theme.of(context).cardColor,
|
||||
padding: EdgeInsets.all(0),
|
||||
),
|
||||
onPressed: onPressed ?? () {},
|
||||
child: row,
|
||||
),
|
||||
Divider(thickness: 0.3,
|
||||
height: 0.1, color: Theme.of(context).dividerColor),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -7,13 +7,13 @@ class MineViewModel extends GetxController {
|
||||
final RxString nickName = '张三'.obs;
|
||||
final RxString account = 'wxid_123456'.obs;
|
||||
|
||||
// Mock data for menu items
|
||||
// Menu items with dynamic labels
|
||||
final List<Map<String, String>> menuItems = [
|
||||
{'label': '支付', 'icon': 'assets/images/mine/ic_pay.png'},
|
||||
{'label': '收藏', 'icon': 'assets/images/favorite.webp'},
|
||||
{'label': '相册', 'icon': 'assets/images/mine/ic_card_package.png'},
|
||||
{'label': '卡片', 'icon': 'assets/images/mine/ic_card_package.png'},
|
||||
{'label': '表情', 'icon': 'assets/images/mine/ic_emoji.png'},
|
||||
{'label': '设置', 'icon': 'assets/images/mine/ic_setting.png'},
|
||||
{'label': 'pay', 'icon': 'assets/images/mine/ic_pay.png'},
|
||||
{'label': 'favorites', 'icon': 'assets/images/favorite.webp'},
|
||||
{'label': 'album', 'icon': 'assets/images/mine/ic_card_package.png'},
|
||||
{'label': 'cards', 'icon': 'assets/images/mine/ic_card_package.png'},
|
||||
{'label': 'emojis', 'icon': 'assets/images/mine/ic_emoji.png'},
|
||||
{'label': 'settings', 'icon': 'assets/images/mine/ic_setting.png'},
|
||||
];
|
||||
}
|
231
lib/app/modules/chat/mineModule/views/homePage/mineHomePage.dart
Normal file
231
lib/app/modules/chat/mineModule/views/homePage/mineHomePage.dart
Normal file
@@ -0,0 +1,231 @@
|
||||
import 'package:caller/app/bindings/themeController.dart';
|
||||
import 'package:caller/app/constants/constants.dart' as AppColors;
|
||||
import 'package:caller/app/modules/chat/discoverModule/widgets/ImageWidget.dart';
|
||||
import 'package:caller/app/modules/chat/discoverModule/widgets/listTileViewWidget.dart';
|
||||
import 'package:caller/app/modules/chat/mineModule/controller/mineController.dart';
|
||||
import 'package:caller/app/modules/chat/mineModule/views/settingsPage/settingPage.dart';
|
||||
import 'package:caller/translations/controller/language_controller.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
|
||||
// Main View
|
||||
class MinePage extends StatelessWidget {
|
||||
MinePage({super.key});
|
||||
|
||||
final MineViewModel viewModel = Get.put(MineViewModel());
|
||||
final LanguageController languageController = Get.put(LanguageController());
|
||||
final ThemeController themeController = Get.find();
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
body: SingleChildScrollView(child: _buildBody(context)),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildUserInfoSection(context),
|
||||
SizedBox(height: 16.0),
|
||||
_buildMenuList(context),
|
||||
Container(
|
||||
color: Theme.of(context).cardColor,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Icon(Icons.language),
|
||||
SizedBox(width: 10),
|
||||
Text(
|
||||
"switch_language".tr,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).textTheme.bodyLarge!.color,
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
|
||||
// Obx(() {
|
||||
// return Text(
|
||||
// languageController.isEnglish.value ? 'English' : 'Chinese',
|
||||
// style: TextStyle(fontSize: 18),
|
||||
// );
|
||||
// }),
|
||||
SizedBox(width: 20),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Switch(
|
||||
activeColor: Theme.of(context).cardColor,
|
||||
inactiveThumbColor: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge!.color,
|
||||
activeTrackColor: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge!.color,
|
||||
inactiveTrackColor: Theme.of(context).cardColor,
|
||||
value: languageController.isEnglish.value,
|
||||
onChanged: (value) {
|
||||
languageController.changeLanguage(
|
||||
value ? 'en' : 'zh',
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Obx(() {
|
||||
final isDark = themeController.isDarkMode;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.change_circle),
|
||||
SizedBox(width: 10),
|
||||
Text(
|
||||
"theme".tr,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).textTheme.bodyLarge!.color,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Switch(
|
||||
activeColor: Theme.of(context).cardColor,
|
||||
inactiveThumbColor: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge!.color,
|
||||
activeTrackColor: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyLarge!.color,
|
||||
inactiveTrackColor: Theme.of(context).cardColor,
|
||||
value: isDark,
|
||||
onChanged: (value) {
|
||||
themeController.toggleTheme(value);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// User info section (avatar, name, account)
|
||||
Widget _buildUserInfoSection(BuildContext context) {
|
||||
return Obx(
|
||||
() => InkWell(
|
||||
onTap: () {},
|
||||
child: Container(
|
||||
color: Theme.of(context).cardColor,
|
||||
child: ListTile(
|
||||
leading: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: viewModel.avatar.value.isNotEmpty
|
||||
? viewModel.avatar.value
|
||||
: "https://picsum.photos/id/1/200/200",
|
||||
width: 80,
|
||||
height: 80,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
title: Text('文件传输助手'),
|
||||
subtitle: Text('微信号:wx_000001'),
|
||||
trailing: IconButton(
|
||||
icon: ImageIcon(
|
||||
AssetImage("assets/images/mine/addfriend_icon_myqr.png"),
|
||||
size: 24.0,
|
||||
),
|
||||
onPressed: () {},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Menu list (支付、收藏、相册等)
|
||||
Widget _buildMenuList(BuildContext context) {
|
||||
return Column(
|
||||
children: viewModel.menuItems
|
||||
.map((item) => _buildMenuItem(item, context))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
// Individual menu item
|
||||
Widget _buildMenuItem(Map<String, String> item, BuildContext context) {
|
||||
String translatedLabel = item['label']!.tr;
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.zero,
|
||||
color: Theme.of(context).cardColor,
|
||||
),
|
||||
child: ListTileView(
|
||||
border: _shouldShowBorder(item['label'])
|
||||
? Border(bottom: BorderSide(color: AppColors.lineColor, width: 0.2))
|
||||
: null,
|
||||
title: translatedLabel,
|
||||
titleStyle: const TextStyle(fontSize: 15.0),
|
||||
isLabel: false,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||
icon: item['icon']!,
|
||||
margin: EdgeInsets.symmetric(
|
||||
vertical: _shouldAddVerticalMargin(item['label']) ? 10.0 : 0.0,
|
||||
),
|
||||
onPressed: () => _handleMenuItemTap(item['label'],context),
|
||||
width: 25.0,
|
||||
fit: BoxFit.cover,
|
||||
horizontal: 15.0,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Helper: Check if border should be shown
|
||||
bool _shouldShowBorder(String? label) {
|
||||
return label != '支付' && label != '设置' && label != '表情';
|
||||
}
|
||||
|
||||
// Helper: Check if vertical margin should be added
|
||||
bool _shouldAddVerticalMargin(String? label) {
|
||||
return label == '支付' || label == '设置';
|
||||
}
|
||||
|
||||
void _handleMenuItemTap(String? label, BuildContext context) {
|
||||
print('Tapped label: "$label"');
|
||||
if (label == 'pay') {
|
||||
|
||||
} else if (label == 'favorites') {
|
||||
} else if (label == 'album') {
|
||||
} else if (label == 'cards') {
|
||||
} else if (label == 'emojis') {
|
||||
} else if (label == 'settings') {
|
||||
print('settings');
|
||||
Get.to(() => SettingPage());
|
||||
} else {
|
||||
print('No action for $label');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Calculate top bar height
|
||||
double topBarHeight() {
|
||||
return MediaQuery.of(Get.context!).padding.top + kToolbarHeight;
|
||||
}
|
||||
}
|
@@ -1,160 +0,0 @@
|
||||
import 'package:caller/app/constants/constants.dart' as AppColors;
|
||||
import 'package:caller/app/modules/chat/discoverModule/widgets/ImageWidget.dart';
|
||||
import 'package:caller/app/modules/chat/discoverModule/widgets/listTileViewWidget.dart';
|
||||
import 'package:caller/app/modules/chat/mineModule/controller/mineController.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
|
||||
|
||||
// Main View
|
||||
class MinePage extends StatelessWidget {
|
||||
MinePage({super.key});
|
||||
|
||||
final MineViewModel viewModel = Get.put(MineViewModel());
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
color: AppColors.appBarColor,
|
||||
child: Scaffold(
|
||||
backgroundColor: AppColors.appBarColor,
|
||||
body: SingleChildScrollView(
|
||||
child: _buildBody(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
return Column(
|
||||
children: [
|
||||
_buildUserInfoSection(),
|
||||
SizedBox(height: 16.0),
|
||||
_buildMenuList(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// User info section (avatar, name, account)
|
||||
Widget _buildUserInfoSection() {
|
||||
return Obx(() => InkWell(
|
||||
onTap: () {},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white
|
||||
),
|
||||
child: ListTile(
|
||||
leading: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: viewModel.avatar.value.isNotEmpty
|
||||
? viewModel.avatar.value
|
||||
: "https://picsum.photos/id/1/200/200",
|
||||
width: 80,
|
||||
height: 80,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
title: Text('文件传输助手'),
|
||||
subtitle: Text('微信号:wx_000001'),
|
||||
trailing: IconButton(
|
||||
icon: ImageIcon(
|
||||
AssetImage("assets/images/mine/addfriend_icon_myqr.png"),
|
||||
size: 24.0,
|
||||
),
|
||||
onPressed: () {},
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
// Avatar widget (handles mock/default avatar)
|
||||
Widget _buildAvatar() {
|
||||
if (viewModel.avatar.value.isNotEmpty) {
|
||||
return ImageView(
|
||||
img: viewModel.avatar.value,
|
||||
width: 60,
|
||||
height: 60,
|
||||
fit: BoxFit.fill,
|
||||
);
|
||||
} else {
|
||||
return Icon(Icons.person );
|
||||
}
|
||||
}
|
||||
|
||||
// Menu list (支付、收藏、相册等)
|
||||
Widget _buildMenuList() {
|
||||
return Column(
|
||||
children: viewModel.menuItems
|
||||
.map((item) => _buildMenuItem(item))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
// Individual menu item
|
||||
Widget _buildMenuItem(Map<String, String> item) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.zero,
|
||||
),
|
||||
child: ListTileView(
|
||||
border: _shouldShowBorder(item['label'])
|
||||
? Border(
|
||||
bottom: BorderSide(
|
||||
color: AppColors.lineColor,
|
||||
width: 0.2,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
title: item['label']!,
|
||||
titleStyle: const TextStyle(fontSize: 15.0),
|
||||
isLabel: false,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||
icon: item['icon']!,
|
||||
margin: EdgeInsets.symmetric(
|
||||
vertical: _shouldAddVerticalMargin(item['label']) ? 10.0 : 0.0,
|
||||
),
|
||||
onPressed: () => _handleMenuItemTap(item['label']),
|
||||
width: 25.0,
|
||||
fit: BoxFit.cover,
|
||||
horizontal: 15.0,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Helper: Check if border should be shown
|
||||
bool _shouldShowBorder(String? label) {
|
||||
return label != '支付' && label != '设置' && label != '表情';
|
||||
}
|
||||
|
||||
// Helper: Check if vertical margin should be added
|
||||
bool _shouldAddVerticalMargin(String? label) {
|
||||
return label == '支付' || label == '设置';
|
||||
}
|
||||
|
||||
// Handle menu item taps (replace with your navigation logic)
|
||||
void _handleMenuItemTap(String? label) {
|
||||
switch (label) {
|
||||
case '支付':
|
||||
// Get.to(() => const PayHomePage()); // Replace with your page
|
||||
break;
|
||||
case '设置':
|
||||
// Add your settings navigation
|
||||
break;
|
||||
// Add other cases as needed
|
||||
}
|
||||
}
|
||||
|
||||
// Navigate to personal info page (replace with your page)
|
||||
void _navigateToPersonalInfo() {
|
||||
// Get.to(() => const PersonalInfoPage());
|
||||
}
|
||||
|
||||
// Calculate top bar height
|
||||
double topBarHeight() {
|
||||
return MediaQuery.of(Get.context!).padding.top + kToolbarHeight;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SettingPage extends StatelessWidget {
|
||||
const SettingPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Center(child: Text("under construction"))
|
||||
],),
|
||||
);
|
||||
}
|
||||
}
|
@@ -235,8 +235,6 @@
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// import 'package:flutter/material.dart';
|
||||
// import 'package:cached_video_player_plus/cached_video_player_plus.dart';
|
||||
|
||||
@@ -438,10 +436,13 @@
|
||||
import 'package:caller/app/modules/chat/MessageModule/views/messageChatViews/ChatHomePage.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cached_video_player_plus/cached_video_player_plus.dart';
|
||||
import 'package:flutter_vlc_player/flutter_vlc_player.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../../../../constants/colors/colors.dart';
|
||||
import '../services/videoPreloader.dart';
|
||||
import '../widgets/videoWidget.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class VideoFeedScreen extends StatefulWidget {
|
||||
const VideoFeedScreen({Key? key}) : super(key: key);
|
||||
@@ -450,7 +451,8 @@ class VideoFeedScreen extends StatefulWidget {
|
||||
_VideoFeedScreenState createState() => _VideoFeedScreenState();
|
||||
}
|
||||
|
||||
class _VideoFeedScreenState extends State<VideoFeedScreen> {
|
||||
class _VideoFeedScreenState extends State<VideoFeedScreen>
|
||||
with WidgetsBindingObserver {
|
||||
final PageController _pageController = PageController();
|
||||
final VideoPreloader _preloader = VideoPreloader();
|
||||
|
||||
@@ -464,11 +466,14 @@ class _VideoFeedScreenState extends State<VideoFeedScreen> {
|
||||
];
|
||||
|
||||
int currentIndex = 0;
|
||||
|
||||
final Map<int, CachedVideoPlayerPlusController> _controllers = {};
|
||||
final Map<int, VlcPlayerController> _rtmpControllers = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
_initializePreloader();
|
||||
_loadSavedIndex();
|
||||
}
|
||||
@@ -532,6 +537,7 @@ class _VideoFeedScreenState extends State<VideoFeedScreen> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_controllers.forEach((key, controller) {
|
||||
controller.dispose();
|
||||
});
|
||||
@@ -539,17 +545,51 @@ class _VideoFeedScreenState extends State<VideoFeedScreen> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
if (state == AppLifecycleState.paused ||
|
||||
state == AppLifecycleState.inactive ||
|
||||
state == AppLifecycleState.detached) {
|
||||
for (var controller in _controllers.values) {
|
||||
if (controller.value.isInitialized) {
|
||||
controller.setVolume(0);
|
||||
controller.pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.black,
|
||||
title: const Text('Video Feed'),
|
||||
backgroundColor: AppColors.primary,
|
||||
centerTitle: true,
|
||||
leading: null,
|
||||
title: Text(
|
||||
'self_media_title'.tr,
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.switch_account),
|
||||
onPressed: ()=>Navigator.pushReplacement(context, MaterialPageRoute(builder: (_)=>ChatHomeScreen())),
|
||||
icon: Icon(Icons.switch_account,),
|
||||
onPressed: () async {
|
||||
for (var controller in _controllers.values) {
|
||||
if (controller.value.isInitialized) {
|
||||
controller.setVolume(0);
|
||||
await controller.pause();
|
||||
}
|
||||
}
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
if (mounted) {
|
||||
await Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => ChatHomeScreen()),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
// IconButton(
|
||||
// icon: const Icon(Icons.info),
|
||||
@@ -610,7 +650,9 @@ class _VideoFeedScreenState extends State<VideoFeedScreen> {
|
||||
children: [
|
||||
Text('Cache Size: $cacheSize'),
|
||||
Text('Cached Videos: $cachedCount'),
|
||||
Text('Max Cache Size: ${(_preloader.maxCacheSize / (1024 * 1024)).toStringAsFixed(0)}MB'),
|
||||
Text(
|
||||
'Max Cache Size: ${(_preloader.maxCacheSize / (1024 * 1024)).toStringAsFixed(0)}MB',
|
||||
),
|
||||
Text('Preload Count: ${_preloader.maxPreloadCount}'),
|
||||
],
|
||||
),
|
||||
@@ -629,7 +671,9 @@ class _VideoFeedScreenState extends State<VideoFeedScreen> {
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Clear Cache'),
|
||||
content: const Text('Are you sure you want to clear all cached videos?'),
|
||||
content: const Text(
|
||||
'Are you sure you want to clear all cached videos?',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import 'package:caller/app/bindings/controllersBindings.dart';
|
||||
import 'package:caller/app/bindings/themeController.dart';
|
||||
import 'package:caller/app/modules/chat/MessageModule/controllers/audioPlayerController.dart';
|
||||
import 'package:caller/app/modules/chat/MessageModule/views/messageChatViews/ChatHomePage.dart';
|
||||
import 'package:caller/app/modules/chat/contactModule/contactViews/homePage/chatInfo.dart';
|
||||
import 'package:caller/app/modules/chat/MessageModule/views/messageChatViews/chatLayout.dart';
|
||||
import 'package:caller/app/modules/chat/MessageModule/views/createroomPage/views/roomCheckPage.dart';
|
||||
import 'package:caller/app/modules/chat/MessageModule/views/room/views/connect.dart';
|
||||
@@ -8,6 +9,9 @@ import 'package:caller/app/modules/chat/MessageModule/views/room/views/liveKitRo
|
||||
import 'package:caller/app/modules/chat/MessageModule/views/room/views/room.dart';
|
||||
import 'package:caller/app/modules/chat/MessageModule/views/room/views/viewerPage.dart';
|
||||
import 'package:caller/app/constants/services/theme.dart';
|
||||
import 'package:caller/translations/controller/language_controller.dart';
|
||||
import 'package:caller/translations/en_Us.dart';
|
||||
import 'package:caller/translations/zh_CN.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
@@ -28,14 +32,17 @@ void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
SystemChrome.setSystemUIOverlayStyle(
|
||||
SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness: Brightness.dark,
|
||||
));
|
||||
),
|
||||
);
|
||||
|
||||
await _requestPermissions();
|
||||
runApp(const LiveKitExampleApp());
|
||||
}
|
||||
|
||||
// Function to request permissions
|
||||
Future<void> _requestPermissions() async {
|
||||
Map<Permission, PermissionStatus> statuses = await [
|
||||
@@ -54,16 +61,35 @@ Future<void> _requestPermissions() async {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class LiveKitExampleApp extends StatelessWidget {
|
||||
//
|
||||
const LiveKitExampleApp({
|
||||
super.key,
|
||||
});
|
||||
const LiveKitExampleApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => GetMaterialApp(
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeController themeController = Get.put(ThemeController());
|
||||
return Obx(() {
|
||||
return GetMaterialApp(
|
||||
theme: lightTheme,
|
||||
darkTheme: darkTheme,
|
||||
themeMode: themeController.themeMode.value,
|
||||
initialBinding: AppBindings(),
|
||||
locale: Locale('en', 'US'),
|
||||
translations: MyTranslations(),
|
||||
fallbackLocale: Locale('en', 'US'),
|
||||
title: 'LiveKit',
|
||||
// theme: LiveKitTheme().buildThemeData(context),
|
||||
home: ChatHomeScreen(),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class MyTranslations extends Translations {
|
||||
@override
|
||||
Map<String, Map<String, String>> get keys => {
|
||||
'en_US': EnUs.values,
|
||||
'zh_CN': ZhCn.values,
|
||||
};
|
||||
}
|
||||
|
18
lib/translations/controller/language_controller.dart
Normal file
18
lib/translations/controller/language_controller.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
// lib/controllers/language_controller.dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class LanguageController extends GetxController {
|
||||
var isEnglish = true.obs;
|
||||
// Method to change the language
|
||||
void changeLanguage(String languageCode) {
|
||||
print("Changing language to: $languageCode");
|
||||
if (languageCode == 'en') {
|
||||
Get.updateLocale(Locale('en', 'US'));
|
||||
isEnglish.value = true;
|
||||
} else if (languageCode == 'zh') {
|
||||
Get.updateLocale(Locale('zh', 'CN'));
|
||||
isEnglish.value = false;
|
||||
}
|
||||
}
|
||||
}
|
59
lib/translations/en_Us.dart
Normal file
59
lib/translations/en_Us.dart
Normal file
@@ -0,0 +1,59 @@
|
||||
class EnUs {
|
||||
static const Map<String, String> values = {
|
||||
//mine screen
|
||||
'pay': 'Pay',
|
||||
'favorites': 'Favorites',
|
||||
'album': 'Album',
|
||||
'cards': 'Cards',
|
||||
'emojis': 'Emojis',
|
||||
'settings': 'Settings',
|
||||
'switch_language': 'Switch Language',
|
||||
'theme':'Switch Theme',
|
||||
//discover screen
|
||||
'friends_circle': 'Friends Circle',
|
||||
'scan': 'Scan',
|
||||
'shake': 'Shake',
|
||||
'search': 'Search',
|
||||
'nearby_people': 'Nearby People',
|
||||
'drift_bottle': 'Drift Bottle',
|
||||
'nearby_restaurants': 'Nearby Restaurants',
|
||||
'shopping': 'Shopping',
|
||||
'game': 'Game',
|
||||
'mini_program': 'Mini Program',
|
||||
//navbar
|
||||
'app_bar_title': 'Chat Module',
|
||||
'self_media_title':'Self Media',
|
||||
'message':'Message',
|
||||
'contacts':'Contacts',
|
||||
'discover':'Discover',
|
||||
'mine':'Mine',
|
||||
//chat info screen
|
||||
'search_chat':'Search Chat History',
|
||||
'mute_notification':'Mute Notifications',
|
||||
'sticky_top':'Sticky on Top',
|
||||
'alert':'Alert',
|
||||
'background':'Background',
|
||||
'clear_chat_history':'Clear Chat History',
|
||||
'report':'Report',
|
||||
'chat_info':'Chat Info',
|
||||
// Search Screen
|
||||
'filter_by':'Filter by',
|
||||
'date':'Date',
|
||||
'photos_and_videos':'Photos & Videos',
|
||||
'files':'Files',
|
||||
'links':'Links',
|
||||
'music_and_audio':'Music & Audio',
|
||||
'transactions':'Transactions',
|
||||
'mini':'Mini Program',
|
||||
'channels':'Channels',
|
||||
'cancel':'Cancel',
|
||||
'search_results_for':'Search results for: ',
|
||||
'leave_group':'Leave Group',
|
||||
'group_name':'Group Name',
|
||||
'group_qr_code':'Group QR Code',
|
||||
'group_notice':'Group Notice'
|
||||
|
||||
|
||||
|
||||
};
|
||||
}
|
56
lib/translations/zh_CN.dart
Normal file
56
lib/translations/zh_CN.dart
Normal file
@@ -0,0 +1,56 @@
|
||||
class ZhCn {
|
||||
static const Map<String, String> values = {
|
||||
//mine screen
|
||||
'pay': '支付',
|
||||
'favorites': '收藏',
|
||||
'album': '相册',
|
||||
'cards': '卡片',
|
||||
'emojis': '表情',
|
||||
'settings': '设置',
|
||||
'switch_language': '切换语言',
|
||||
'theme': '切换主题',
|
||||
//discover screen
|
||||
'friends_circle': '朋友圈',
|
||||
'scan': '扫一扫',
|
||||
'shake': '摇一摇',
|
||||
'search': '搜一搜',
|
||||
'nearby_people': '附近的人',
|
||||
'drift_bottle': '漂流瓶',
|
||||
'nearby_restaurants': '附近的餐厅',
|
||||
'shopping': '购物',
|
||||
'game': '游戏',
|
||||
'mini_program': '小程序',
|
||||
//navbar
|
||||
'app_bar_title': '聊天模块',
|
||||
'self_media_title': '我的媒体',
|
||||
'message': '消息',
|
||||
'contacts': '联系人',
|
||||
'discover': '发现',
|
||||
'mine': '我的',
|
||||
//chat info screen
|
||||
'search_chat': '搜索聊天记录',
|
||||
'mute_notification': '静音通知',
|
||||
'sticky_top': '置顶聊天',
|
||||
'alert': '举报',
|
||||
'background': '背景',
|
||||
'clear_chat_history': '清空聊天记录',
|
||||
'report': '举报',
|
||||
'chat_info': '聊天信息',
|
||||
// Search Screen
|
||||
'filter_by': '筛选',
|
||||
'date': '日期',
|
||||
'photos_and_videos': '图片和视频',
|
||||
'files': '文件',
|
||||
'links': '链接',
|
||||
'music_and_audio': '音乐和音频',
|
||||
'transactions': '交易',
|
||||
'mini': '小程序',
|
||||
'channels': '频道',
|
||||
'search_results_for': '搜索结果:',
|
||||
'cancel': '取消',
|
||||
'leave_group': '离开群组',
|
||||
'group_name': '群组名称',
|
||||
'group_qr_code': '群组二维码',
|
||||
'group_notice': '群组公告'
|
||||
};
|
||||
}
|
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <emoji_picker_flutter/emoji_picker_flutter_plugin.h>
|
||||
#include <file_selector_linux/file_selector_plugin.h>
|
||||
#include <flutter_localization/flutter_localization_plugin.h>
|
||||
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
|
||||
#include <record_linux/record_linux_plugin.h>
|
||||
|
||||
@@ -18,6 +19,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
||||
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) flutter_localization_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterLocalizationPlugin");
|
||||
flutter_localization_plugin_register_with_registrar(flutter_localization_registrar);
|
||||
g_autoptr(FlPluginRegistrar) flutter_webrtc_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterWebRTCPlugin");
|
||||
flutter_web_r_t_c_plugin_register_with_registrar(flutter_webrtc_registrar);
|
||||
|
@@ -5,6 +5,7 @@
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
emoji_picker_flutter
|
||||
file_selector_linux
|
||||
flutter_localization
|
||||
flutter_webrtc
|
||||
record_linux
|
||||
)
|
||||
|
@@ -11,6 +11,7 @@ import device_info_plus
|
||||
import emoji_picker_flutter
|
||||
import file_picker
|
||||
import file_selector_macos
|
||||
import flutter_localization
|
||||
import flutter_webrtc
|
||||
import just_audio
|
||||
import livekit_client
|
||||
@@ -30,6 +31,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
EmojiPickerFlutterPlugin.register(with: registry.registrar(forPlugin: "EmojiPickerFlutterPlugin"))
|
||||
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
FlutterLocalizationPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalizationPlugin"))
|
||||
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
|
||||
JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin"))
|
||||
LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin"))
|
||||
|
29
pubspec.lock
29
pubspec.lock
@@ -201,6 +201,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.3"
|
||||
dotted_decoration:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dotted_decoration
|
||||
sha256: a5c5771367690b4f64ebfa7911954ab472b9675f025c373f514e32ac4bb81d5e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
dropdown_button2:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -334,6 +342,19 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
flutter_localization:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_localization
|
||||
sha256: "578a73455a0deffc4169ef9372ba0562a3e2cff563e5c524ea87bc96daa519c0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.3"
|
||||
flutter_localizations:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1365,6 +1386,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.5.0"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
youtube_explode_dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@@ -30,6 +30,9 @@ environment:
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_localization: ^0.3.3
|
||||
|
||||
|
||||
# ffmpeg_kit_flutter_new: ^2.0.0
|
||||
visibility_detector: ^0.4.0+2
|
||||
flutter_vlc_player: ^7.4.3
|
||||
@@ -57,6 +60,7 @@ dependencies:
|
||||
crypto: ^3.0.6
|
||||
flutter_background: ^1.1.0
|
||||
intl: ^0.20.2
|
||||
dotted_decoration: ^2.0.0
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
@@ -84,6 +88,7 @@ dev_dependencies:
|
||||
|
||||
|
||||
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
|
@@ -9,6 +9,7 @@
|
||||
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
|
||||
#include <emoji_picker_flutter/emoji_picker_flutter_plugin_c_api.h>
|
||||
#include <file_selector_windows/file_selector_windows.h>
|
||||
#include <flutter_localization/flutter_localization_plugin_c_api.h>
|
||||
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
|
||||
#include <livekit_client/live_kit_plugin.h>
|
||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||
@@ -21,6 +22,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
registry->GetRegistrarForPlugin("EmojiPickerFlutterPluginCApi"));
|
||||
FileSelectorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||
FlutterLocalizationPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterLocalizationPluginCApi"));
|
||||
FlutterWebRTCPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterWebRTCPlugin"));
|
||||
LiveKitPluginRegisterWithRegistrar(
|
||||
|
@@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||
connectivity_plus
|
||||
emoji_picker_flutter
|
||||
file_selector_windows
|
||||
flutter_localization
|
||||
flutter_webrtc
|
||||
livekit_client
|
||||
permission_handler_windows
|
||||
|
Reference in New Issue
Block a user