chore(group): fix delay show group when switch and convert to relative import

This commit is contained in:
Tran Anh Tuan
2025-10-23 11:08:03 +07:00
parent cfebf74515
commit 9bff11a0b1
44 changed files with 357 additions and 380 deletions

View File

@@ -2,11 +2,11 @@
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import '../../../product/extension/context_extension.dart';
import '../../../bloc/group_detail_bloc.dart';
import '../../../product/constant/icon/icon_constants.dart';
import '../../../product/services/language_services.dart';
import '../../devices/device_model.dart';
import 'group_detail_model.dart';

View File

@@ -1,8 +1,8 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:async';
import 'package:flutter/material.dart';
import '../../../bloc/group_detail_bloc.dart';
import '../../../product/shared/shared_loading_animation.dart';
import 'group_detail_model.dart';
@@ -14,7 +14,6 @@ import '../../../product/services/api_services.dart';
import '../../../product/services/language_services.dart';
import '../../../product/utils/device_utils.dart';
import '../../../product/utils/response_status_utils.dart';
import 'add_device_to_group_dialog.dart';
class DetailGroupScreen extends StatefulWidget {

View File

@@ -1,187 +1,125 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../../product/constant/enums/app_route_enums.dart';
import '../../../product/shared/shared_component_loading_animation.dart';
import '../../../product/shared/shared_loading_animation.dart';
import 'groups_model.dart';
import 'groups_widget.dart';
import '../../../bloc/inter_family_bloc.dart';
import '../inter_family_widget.dart';
import '../../../product/base/bloc/base_bloc.dart';
import '../../../product/constant/app/app_constants.dart';
import '../../../product/constant/icon/icon_constants.dart';
import '../../../product/extension/context_extension.dart';
import '../../../product/services/language_services.dart';
import 'groups_widget.dart';
class GroupsScreen extends StatefulWidget {
const GroupsScreen({super.key, required this.role});
/// Stateless widget that renders a provided list of groups. The parent
/// screen owns fetching/updating the list; this widget only displays it and
/// forwards actions to the provided [InterFamilyBloc].
class GroupsScreen extends StatelessWidget {
const GroupsScreen(
{super.key,
required this.role,
required this.groups,
required this.interFamilyBloc});
final String role;
@override
State<GroupsScreen> createState() => _GroupsScreenState();
}
class _GroupsScreenState extends State<GroupsScreen> {
late InterFamilyBloc interFamilyBloc;
Timer? getAllGroupsTimer;
@override
void initState() {
super.initState();
interFamilyBloc = BlocProvider.of(context);
const duration = Duration(seconds: 5);
getAllGroupsTimer = Timer.periodic(
duration,
(Timer t) => interFamilyBloc.getAllGroup(context, widget.role),
);
}
@override
void dispose() {
getAllGroupsTimer?.cancel();
super.dispose();
}
final List<Group> groups;
final InterFamilyBloc interFamilyBloc;
@override
Widget build(BuildContext context) {
if (widget.role == ApplicationConstants.OWNER_GROUP ||
widget.role == ApplicationConstants.PARTICIPANT_GROUP) {
return StreamBuilder<List<Group>>(
stream: interFamilyBloc.streamCurrentGroups,
builder: (context, groupsSnapshot) {
if (groupsSnapshot.data == null) {
interFamilyBloc.getAllGroup(context,widget.role);
return const SharedLoadingAnimation();
} else if (groupsSnapshot.data!.isEmpty) {
return Center(
child: Text(appLocalization(context).dont_have_group),
);
} else {
return Scaffold(
body: groupsSnapshot.data?.isEmpty ?? true
? const SharedComponentLoadingAnimation()
: ListView.builder(
itemCount: groupsSnapshot.data!.length,
itemBuilder: (context, index) {
return ListTile(
onTap: () {
context.pushNamed(AppRoutes.GROUP_DETAIL.name,
pathParameters: {
"groupId": groupsSnapshot.data![index].id!
},
extra: widget.role);
},
leading: IconConstants.instance
.getMaterialIcon(Icons.diversity_2),
title: Text(
groupsSnapshot.data![index].name ?? '',
style: const TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text(
groupsSnapshot.data![index].description ?? ""),
trailing:
widget.role == ApplicationConstants.OWNER_GROUP
? PopupMenuButton(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(8.0),
bottomRight: Radius.circular(8.0),
topLeft: Radius.circular(8.0),
topRight: Radius.circular(8.0),
),
),
itemBuilder: (ctx) => [
_buildPopupMenuItem(
groupsSnapshot.data![index],
context,
appLocalization(context)
.share_group_title,
Icons.share,
4),
_buildPopupMenuItem(
groupsSnapshot.data![index],
context,
appLocalization(context)
.change_group_infomation_title,
Icons.settings_backup_restore,
2),
_buildPopupMenuItem(
groupsSnapshot.data![index],
context,
appLocalization(context)
.delete_group_title,
Icons.delete_forever_rounded,
3),
],
icon: const Icon(Icons.more_horiz),
)
: const SizedBox.shrink(),
);
},
),
);
}
},
);
} else {
if (role != ApplicationConstants.OWNER_GROUP &&
role != ApplicationConstants.PARTICIPANT_GROUP) {
return const SizedBox.shrink();
}
if (groups.isEmpty) {
return Center(child: Text(appLocalization(context).dont_have_group));
}
return ListView.builder(
itemCount: groups.length,
itemBuilder: (context, index) {
final group = groups[index];
return ListTile(
onTap: () => context.pushNamed(AppRoutes.GROUP_DETAIL.name,
pathParameters: {"groupId": group.id!}, extra: role),
leading: IconConstants.instance.getMaterialIcon(Icons.diversity_2),
title: Text(group.name ?? '',
style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text(group.description ?? ''),
trailing: role == ApplicationConstants.OWNER_GROUP
? _ownerPopupMenu(group, context)
: null,
);
},
);
}
PopupMenuItem _buildPopupMenuItem(Group group, BuildContext context,
String title, IconData iconData, int position) {
return PopupMenuItem(
Widget _ownerPopupMenu(Group group, BuildContext context) {
return PopupMenuButton<int>(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0))),
itemBuilder: (ctx) => [
_buildPopupMenuItem(group, context,
appLocalization(context).share_group_title, Icons.share, 4),
_buildPopupMenuItem(
group,
context,
appLocalization(context).change_group_infomation_title,
Icons.settings_backup_restore,
2),
_buildPopupMenuItem(
group,
context,
appLocalization(context).delete_group_title,
Icons.delete_forever_rounded,
3),
],
icon: const Icon(Icons.more_horiz),
);
}
PopupMenuItem<int> _buildPopupMenuItem(Group group, BuildContext context,
String title, IconData iconData, int value) {
return PopupMenuItem<int>(
value: value,
child: Row(children: [
Icon(iconData, color: Colors.black),
const SizedBox(width: 10),
Text(title)
]),
onTap: () {
if (title == appLocalization(context).share_group_title) {
Future.delayed(context.lowDuration, () {
Future.delayed(context.lowDuration, () {
if (title == appLocalization(context).share_group_title) {
shareGroup(context, group);
});
} else if (title ==
appLocalization(context).change_group_infomation_title) {
Future.delayed(context.lowDuration, () {
} else if (title ==
appLocalization(context).change_group_infomation_title) {
createOrJoinGroupDialog(
context,
interFamilyBloc,
widget.role,
appLocalization(context).change_group_infomation_content,
appLocalization(context).group_name_title,
group.name!,
false,
group.id!,
appLocalization(context).description_group,
group.description ?? "");
});
} else if (title == appLocalization(context).delete_group_title) {
Future.delayed(context.lowDuration, () {
context,
interFamilyBloc,
role,
appLocalization(context).change_group_infomation_content,
appLocalization(context).group_name_title,
group.name ?? '',
false,
group.id ?? '',
appLocalization(context).description_group,
group.description ?? '',
);
} else if (title == appLocalization(context).delete_group_title) {
showActionDialog(
context,
widget.role,
interFamilyBloc,
appLocalization(context).delete_group_title,
appLocalization(context).delete_group_content,
group);
});
} else {}
context,
role,
interFamilyBloc,
appLocalization(context).delete_group_title,
appLocalization(context).delete_group_content,
group,
);
}
});
},
value: position,
child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(
iconData,
color: Colors.black,
),
const SizedBox(width: 10),
Text(title),
],
),
);
}
}

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'groups/groups_screen.dart';
import 'groups/groups_model.dart';
import '../../bloc/inter_family_bloc.dart';
import 'inter_family_widget.dart';
import '../../product/base/bloc/base_bloc.dart';
@@ -25,22 +26,31 @@ class _InterFamilyScreenState extends State<InterFamilyScreen> {
void initState() {
super.initState();
interFamilyBloc = BlocProvider.of(context);
// fetch initial groups for the default selected tab
WidgetsBinding.instance.addPostFrameCallback((_) {
interFamilyBloc.getAllGroup(
context,
_selectedIndex == 0
? ApplicationConstants.OWNER_GROUP
: ApplicationConstants.PARTICIPANT_GROUP);
});
}
final _widgetOptions = <Widget>[
BlocProvider(
blocBuilder: () => InterFamilyBloc(),
child: const GroupsScreen(
role: ApplicationConstants.OWNER_GROUP,
),
),
BlocProvider(
blocBuilder: () => InterFamilyBloc(),
child: const GroupsScreen(
role: ApplicationConstants.PARTICIPANT_GROUP,
),
),
];
List<Group> ownerGroups = [];
List<Group> participantGroups = [];
List<Widget> get _widgetOptions => [
GroupsScreen(
role: ApplicationConstants.OWNER_GROUP,
groups: ownerGroups,
interFamilyBloc: interFamilyBloc,
),
GroupsScreen(
role: ApplicationConstants.PARTICIPANT_GROUP,
groups: participantGroups,
interFamilyBloc: interFamilyBloc,
),
];
@override
Widget build(BuildContext context) {
@@ -49,96 +59,109 @@ class _InterFamilyScreenState extends State<InterFamilyScreen> {
stream: interFamilyBloc.streamSelectedScreen,
initialData: _selectedIndex,
builder: (context, selectSnapshot) {
return Scaffold(
appBar: AppBar(
actions: [
ElevatedButton(
onPressed: () {
if (selectSnapshot.data == 0) {
createOrJoinGroupDialog(
context,
interFamilyBloc,
selectSnapshot.data! == 0
? ApplicationConstants.OWNER_GROUP
: ApplicationConstants.PARTICIPANT_GROUP,
appLocalization(context).add_new_group,
appLocalization(context).group_name_title,
"",
false,
"",
"",
"");
} else {
createOrJoinGroupDialog(
context,
interFamilyBloc,
selectSnapshot.data! == 0
? ApplicationConstants.OWNER_GROUP
: ApplicationConstants.PARTICIPANT_GROUP,
appLocalization(context).join_group,
appLocalization(context).group_id_title,
'',
true,
"",
appLocalization(context).group_name_title,
"");
}
},
style: ElevatedButton.styleFrom(
shape: const CircleBorder(),
// subscribe to groups stream and update local lists so child widgets render instantly
return StreamBuilder<List<Group>>(
stream: interFamilyBloc.streamCurrentGroups,
builder: (context, groupsSnapshot) {
if (groupsSnapshot.hasData) {
final all = groupsSnapshot.data!;
ownerGroups = all
.where((g) => g.isOwner == true && g.visibility == 'PUBLIC')
.toList();
participantGroups = all
.where((g) => g.isOwner == null && g.visibility == 'PUBLIC')
.toList();
}
// build UI below
return Scaffold(
appBar: AppBar(
actions: [
ElevatedButton(
onPressed: () {
if (selectSnapshot.data == 0) {
createOrJoinGroupDialog(
context,
interFamilyBloc,
ApplicationConstants.OWNER_GROUP,
appLocalization(context).add_new_group,
appLocalization(context).group_name_title,
"",
false,
"",
"",
"",
);
} else {
createOrJoinGroupDialog(
context,
interFamilyBloc,
ApplicationConstants.PARTICIPANT_GROUP,
appLocalization(context).join_group,
appLocalization(context).group_id_title,
'',
true,
"",
appLocalization(context).group_name_title,
"",
);
}
},
style:
ElevatedButton.styleFrom(shape: const CircleBorder()),
child: IconConstants.instance.getMaterialIcon(Icons.add),
),
],
leading: Builder(
builder: (context) {
return IconButton(
onPressed: () {
Scaffold.of(context).openDrawer();
},
icon: const Icon(Icons.menu),
);
},
),
title: StreamBuilder<String>(
stream: interFamilyBloc.streamTitleScreen,
initialData: title,
builder: (context, titleSnapshot) {
return Center(
child: Text(titleSnapshot.data ?? title),
);
},
),
child: IconConstants.instance.getMaterialIcon(Icons.add),
),
],
leading: Builder(
builder: (context) {
return IconButton(
onPressed: () {
Scaffold.of(context).openDrawer();
},
icon: const Icon(Icons.menu),
);
},
),
title: StreamBuilder<String>(
stream: interFamilyBloc.streamTitleScreen,
initialData: title,
builder: (context, titleSnapshot) {
return Center(
child: Text(titleSnapshot.data ?? title),
);
},
),
),
drawer: Drawer(
width: context.dynamicWidth(0.6),
child: ListView(
padding: EdgeInsets.zero,
children: [
ListTile(
title: Text(appLocalization(context).my_group_title),
selected: _selectedIndex == 0,
onTap: () {
_onItemTapped(0);
title = appLocalization(context).my_group_title;
interFamilyBloc.sinkTitleScreen.add(title);
Navigator.pop(context);
},
drawer: Drawer(
width: context.dynamicWidth(0.6),
child: ListView(
padding: EdgeInsets.zero,
children: [
ListTile(
title: Text(appLocalization(context).my_group_title),
selected: _selectedIndex == 0,
onTap: () {
_onItemTapped(0);
title = appLocalization(context).my_group_title;
interFamilyBloc.sinkTitleScreen.add(title);
Navigator.pop(context);
},
),
ListTile(
title: Text(appLocalization(context).invite_group),
selected: _selectedIndex == 1,
onTap: () {
_onItemTapped(1);
title = appLocalization(context).invite_group;
interFamilyBloc.sinkTitleScreen.add(title);
Navigator.pop(context);
},
),
],
),
ListTile(
title: Text(appLocalization(context).invite_group),
selected: _selectedIndex == 1,
onTap: () {
_onItemTapped(1);
title = appLocalization(context).invite_group;
interFamilyBloc.sinkTitleScreen.add(title);
Navigator.pop(context);
},
),
],
),
),
body: _widgetOptions[selectSnapshot.data ?? _selectedIndex],
),
body: _widgetOptions[selectSnapshot.data ?? _selectedIndex],
);
},
);
},
);
@@ -159,5 +182,11 @@ class _InterFamilyScreenState extends State<InterFamilyScreen> {
interFamilyBloc.sinkSelectedScreen.add(_selectedIndex);
isLoading = false;
interFamilyBloc.sinkIsLoading.add(isLoading);
// fetch groups for the selected tab immediately
interFamilyBloc.getAllGroup(
context,
_selectedIndex == 0
? ApplicationConstants.OWNER_GROUP
: ApplicationConstants.PARTICIPANT_GROUP);
}
}