Complete refactoring SFM App Source Code
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'device_notification_settings_model.dart';
|
||||
import '../../../product/base/bloc/base_bloc.dart';
|
||||
|
||||
class DeviceNotificationSettingsBloc extends BlocBase {
|
||||
final listNotifications =
|
||||
StreamController<List<DeviceNotificationSettings>>.broadcast();
|
||||
StreamSink<List<DeviceNotificationSettings>> get sinkListNotifications =>
|
||||
listNotifications.sink;
|
||||
Stream<List<DeviceNotificationSettings>> get streamListNotifications =>
|
||||
listNotifications.stream;
|
||||
|
||||
final deviceThingID = StreamController<String>.broadcast();
|
||||
StreamSink<String> get sinkDeviceThingID => deviceThingID.sink;
|
||||
Stream<String> get streamDeviceThingID => deviceThingID.stream;
|
||||
|
||||
final deviceNotificationSettingMap =
|
||||
StreamController<Map<String, int>>.broadcast();
|
||||
StreamSink<Map<String, int>> get sinkDeviceNotificationSettingMap =>
|
||||
deviceNotificationSettingMap.sink;
|
||||
Stream<Map<String, int>> get streamDeviceNotificationSettingMap =>
|
||||
deviceNotificationSettingMap.stream;
|
||||
|
||||
final isDataChange = StreamController<bool>.broadcast();
|
||||
StreamSink<bool> get sinkIsDataChange => isDataChange.sink;
|
||||
Stream<bool> get streamIsDataChange => isDataChange.stream;
|
||||
|
||||
final iconChange = StreamController<IconData>.broadcast();
|
||||
StreamSink<IconData> get sinkIconChange => iconChange.sink;
|
||||
Stream<IconData> get streamIconChange => iconChange.stream;
|
||||
|
||||
final messageChange = StreamController<String>.broadcast();
|
||||
StreamSink<String> get sinkMessageChange => messageChange.sink;
|
||||
Stream<String> get streaMmessageChange => messageChange.stream;
|
||||
|
||||
|
||||
@override
|
||||
void dispose() {}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
class DeviceNotificationSettings {
|
||||
String? id;
|
||||
String? userId;
|
||||
String? thingId;
|
||||
DeviceAliasSettings? settings;
|
||||
DateTime? updatedAt;
|
||||
|
||||
DeviceNotificationSettings({
|
||||
this.id,
|
||||
this.userId,
|
||||
this.thingId,
|
||||
this.settings,
|
||||
this.updatedAt,
|
||||
});
|
||||
|
||||
DeviceNotificationSettings.fromJson(Map<String, dynamic> json) {
|
||||
id = json["id"];
|
||||
userId = json["user_id"];
|
||||
thingId = json["thing_id"];
|
||||
settings = json["settings"] != null
|
||||
? DeviceAliasSettings.fromJson(json["settings"])
|
||||
: null;
|
||||
updatedAt = DateTime.parse(json["updated_at"]);
|
||||
}
|
||||
|
||||
static List<DeviceNotificationSettings> mapToList(
|
||||
Map<String, DeviceNotificationSettings> map) {
|
||||
return map.values.toList();
|
||||
}
|
||||
|
||||
static Map<String, DeviceNotificationSettings> mapFromJson(
|
||||
Map<String, dynamic> json) {
|
||||
final Map<String, DeviceNotificationSettings> devices = {};
|
||||
json.forEach((key, value) {
|
||||
devices[key] = DeviceNotificationSettings.fromJson(value);
|
||||
});
|
||||
return devices;
|
||||
}
|
||||
}
|
||||
|
||||
class DeviceAliasSettings {
|
||||
String? alias;
|
||||
Map<String, int>? notifiSettings;
|
||||
|
||||
DeviceAliasSettings({
|
||||
this.alias,
|
||||
this.notifiSettings,
|
||||
});
|
||||
|
||||
DeviceAliasSettings.fromJson(Map<String, dynamic> json) {
|
||||
alias = json['alias'];
|
||||
if (json['notifi_settings'] != null) {
|
||||
notifiSettings = Map<String, int>.from(json['notifi_settings']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'dart:convert';
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../product/shared/shared_snack_bar.dart';
|
||||
import 'device_notification_settings_bloc.dart';
|
||||
import 'device_notification_settings_model.dart';
|
||||
import '../../../product/base/bloc/base_bloc.dart';
|
||||
import '../../../product/extention/context_extention.dart';
|
||||
import '../../../product/services/api_services.dart';
|
||||
import '../../../product/services/language_services.dart';
|
||||
|
||||
class DeviceNotificationSettingsScreen extends StatefulWidget {
|
||||
const DeviceNotificationSettingsScreen({super.key});
|
||||
|
||||
@override
|
||||
State<DeviceNotificationSettingsScreen> createState() =>
|
||||
_DeviceNotificationSettingsScreenState();
|
||||
}
|
||||
|
||||
class _DeviceNotificationSettingsScreenState
|
||||
extends State<DeviceNotificationSettingsScreen> {
|
||||
late DeviceNotificationSettingsBloc deviceNotificationSettingsBloc;
|
||||
String thingID = "";
|
||||
List<DeviceNotificationSettings> deviceNotifications = [];
|
||||
APIServices apiServices = APIServices();
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
deviceNotificationSettingsBloc = BlocProvider.of(context);
|
||||
getNotificationSetting();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StreamBuilder<List<DeviceNotificationSettings>>(
|
||||
stream: deviceNotificationSettingsBloc.streamListNotifications,
|
||||
initialData: deviceNotifications,
|
||||
builder: (context, notificationSnapshot) {
|
||||
return StreamBuilder<String>(
|
||||
stream: deviceNotificationSettingsBloc.streamDeviceThingID,
|
||||
initialData: thingID,
|
||||
builder: (context, deviceThingIdSnapshot) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: Text(
|
||||
appLocalization(context).profile_setting,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
body: deviceNotifications.isEmpty
|
||||
? Center(
|
||||
child: CircularProgressIndicator(
|
||||
value: context.mediumValue,
|
||||
),
|
||||
)
|
||||
: Padding(
|
||||
padding: context.paddingLow,
|
||||
child: Column(
|
||||
children: [
|
||||
DropdownButtonFormField2<
|
||||
DeviceNotificationSettings>(
|
||||
isExpanded: true,
|
||||
decoration: InputDecoration(
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(vertical: 10),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
),
|
||||
hint: Text(
|
||||
appLocalization(context)
|
||||
.choose_device_dropdownButton,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
iconStyleData: const IconStyleData(
|
||||
icon: Icon(
|
||||
Icons.arrow_drop_down,
|
||||
),
|
||||
iconSize: 24,
|
||||
),
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
),
|
||||
items: deviceNotifications
|
||||
.map((e) => DropdownMenuItem<
|
||||
DeviceNotificationSettings>(
|
||||
value: e,
|
||||
child: Text(e.settings!.alias!),
|
||||
))
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
thingID = value!.thingId ?? "";
|
||||
deviceNotificationSettingsBloc
|
||||
.sinkDeviceThingID
|
||||
.add(thingID);
|
||||
},
|
||||
),
|
||||
if (deviceThingIdSnapshot.data! != "")
|
||||
listNotificationSetting(
|
||||
deviceThingIdSnapshot.data ?? thingID,
|
||||
deviceNotifications)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void getNotificationSetting() async {
|
||||
String? response = await apiServices.getAllSettingsNotificationOfDevices();
|
||||
final data = jsonDecode(response);
|
||||
final result = data['data'];
|
||||
// log("Data ${DeviceNotificationSettings.mapFromJson(jsonDecode(data)).values.toList()}");
|
||||
deviceNotifications =
|
||||
DeviceNotificationSettings.mapFromJson(result).values.toList();
|
||||
deviceNotificationSettingsBloc.sinkListNotifications
|
||||
.add(deviceNotifications);
|
||||
}
|
||||
|
||||
Widget listNotificationSetting(
|
||||
String thingID, List<DeviceNotificationSettings> devices) {
|
||||
DeviceNotificationSettings chooseDevice = DeviceNotificationSettings();
|
||||
|
||||
for (var device in devices) {
|
||||
if (device.thingId == thingID) {
|
||||
chooseDevice = device;
|
||||
break;
|
||||
}
|
||||
}
|
||||
IconData selectIcon = Icons.unpublished_outlined;
|
||||
String selectMessage = appLocalization(context)
|
||||
.change_profile_device_notification_deselect_all;
|
||||
bool isChange = false;
|
||||
bool isDataChange = false;
|
||||
// Lấy notifiSettings
|
||||
final notifiSettings = chooseDevice.settings?.notifiSettings ?? {};
|
||||
deviceNotificationSettingsBloc.sinkDeviceNotificationSettingMap
|
||||
.add(notifiSettings);
|
||||
return SingleChildScrollView(
|
||||
child: StreamBuilder<Map<String, int>>(
|
||||
stream:
|
||||
deviceNotificationSettingsBloc.streamDeviceNotificationSettingMap,
|
||||
initialData: notifiSettings,
|
||||
builder: (context, deviceNotificationSettingSnapshot) {
|
||||
return StreamBuilder<bool>(
|
||||
stream: deviceNotificationSettingsBloc.streamIsDataChange,
|
||||
initialData: isDataChange,
|
||||
builder: (context, isDataChangeSnapshot) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
changeIconAndMessage(
|
||||
isChange,
|
||||
selectIcon,
|
||||
selectMessage,
|
||||
notifiSettings,
|
||||
isDataChange);
|
||||
isChange = !isChange;
|
||||
},
|
||||
icon: StreamBuilder<IconData>(
|
||||
stream: deviceNotificationSettingsBloc
|
||||
.streamIconChange,
|
||||
initialData: selectIcon,
|
||||
builder: (context, iconSnapshot) {
|
||||
return Icon(
|
||||
iconSnapshot.data ?? selectIcon);
|
||||
}),
|
||||
label: StreamBuilder<String>(
|
||||
stream: deviceNotificationSettingsBloc
|
||||
.streaMmessageChange,
|
||||
builder: (context, messageSnapshot) {
|
||||
return Text(
|
||||
messageSnapshot.data ?? selectMessage);
|
||||
})),
|
||||
if (isDataChangeSnapshot.data ?? isDataChange)
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
updateDeviceNotification(
|
||||
thingID, notifiSettings, isDataChange);
|
||||
},
|
||||
icon: const Icon(Icons.done))
|
||||
],
|
||||
),
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount:
|
||||
deviceNotificationSettingSnapshot.data?.length ??
|
||||
notifiSettings.length,
|
||||
itemBuilder: (context, index) {
|
||||
final key = deviceNotificationSettingSnapshot
|
||||
.data!.keys
|
||||
.elementAt(index);
|
||||
final value =
|
||||
deviceNotificationSettingSnapshot.data![key];
|
||||
return CheckboxListTile(
|
||||
value: value == 1,
|
||||
onChanged: (newValue) {
|
||||
notifiSettings[key] = newValue == true ? 1 : 0;
|
||||
deviceNotificationSettingsBloc
|
||||
.sinkDeviceNotificationSettingMap
|
||||
.add(notifiSettings);
|
||||
isDataChange = true;
|
||||
deviceNotificationSettingsBloc.sinkIsDataChange
|
||||
.add(isDataChange);
|
||||
},
|
||||
title: Text(getNotificationEvent(key)),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (isDataChangeSnapshot.data ?? isDataChange)
|
||||
Center(
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor:
|
||||
MaterialStateProperty.all<Color>(
|
||||
Colors.green),
|
||||
foregroundColor:
|
||||
MaterialStateProperty.all<Color>(
|
||||
Colors.white)),
|
||||
onPressed: () async {
|
||||
updateDeviceNotification(
|
||||
thingID, notifiSettings, isDataChange);
|
||||
},
|
||||
child: Text(appLocalization(context)
|
||||
.update_button_content)),
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
void changeIconAndMessage(bool isChange, IconData icon, String message,
|
||||
Map<String, int> notifiSettings, bool isDataChange) {
|
||||
if (isChange == false) {
|
||||
icon = Icons.published_with_changes_outlined;
|
||||
message = appLocalization(context)
|
||||
.change_profile_device_notification_select_all;
|
||||
notifiSettings.updateAll((key, value) => 0);
|
||||
} else {
|
||||
icon = Icons.unpublished_outlined;
|
||||
message = appLocalization(context)
|
||||
.change_profile_device_notification_deselect_all;
|
||||
notifiSettings.updateAll((key, value) => 1);
|
||||
}
|
||||
isDataChange = true;
|
||||
deviceNotificationSettingsBloc.sinkIsDataChange.add(isDataChange);
|
||||
deviceNotificationSettingsBloc.sinkDeviceNotificationSettingMap
|
||||
.add(notifiSettings);
|
||||
deviceNotificationSettingsBloc.sinkIconChange.add(icon);
|
||||
deviceNotificationSettingsBloc.sinkMessageChange.add(message);
|
||||
}
|
||||
|
||||
String getNotificationEvent(String eventCode) {
|
||||
String event = "";
|
||||
if (eventCode == "001") {
|
||||
event = "Thiết bị mất kết nối";
|
||||
} else if (eventCode == "002") {
|
||||
event = "Hoạt động của thiết bị";
|
||||
} else if (eventCode == "003") {
|
||||
event = "Cảnh báo của thiết bị";
|
||||
} else if (eventCode == "004") {
|
||||
event = "Thiết bị lỗi";
|
||||
} else if (eventCode == "005") {
|
||||
event = "Thiết bị bình thường";
|
||||
} else if (eventCode == "006") {
|
||||
event = "Thiết bị yếu pin";
|
||||
} else if (eventCode == "101") {
|
||||
event = "Người dùng tham gia nhóm";
|
||||
} else if (eventCode == "102") {
|
||||
event = "Người dùng rời nhóm";
|
||||
} else if (eventCode == "103") {
|
||||
event = "Thiết bị tham gia nhóm";
|
||||
} else if (eventCode == "104") {
|
||||
event = "Thiết bị bị xóa khỏi nhóm";
|
||||
} else {
|
||||
event = "Chưa xác định";
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
void updateDeviceNotification(String thingID, Map<String, int> notifiSettings,
|
||||
bool isDataChange) async {
|
||||
int statusCode = await apiServices.updateDeviceNotificationSettings(
|
||||
thingID, notifiSettings);
|
||||
if (statusCode == 200) {
|
||||
showNoIconTopSnackBar(
|
||||
context,
|
||||
appLocalization(context).notification_update_device_settings_success,
|
||||
Colors.green,
|
||||
Colors.white);
|
||||
} else {
|
||||
showNoIconTopSnackBar(
|
||||
context,
|
||||
appLocalization(context).notification_update_device_settings_failed,
|
||||
Colors.red,
|
||||
Colors.white);
|
||||
}
|
||||
isDataChange = false;
|
||||
deviceNotificationSettingsBloc.sinkIsDataChange.add(isDataChange);
|
||||
}
|
||||
}
|
||||
37
lib/feature/settings/profile/profile_model.dart
Normal file
37
lib/feature/settings/profile/profile_model.dart
Normal file
@@ -0,0 +1,37 @@
|
||||
class User {
|
||||
String? id;
|
||||
String? username;
|
||||
String? role;
|
||||
String? name;
|
||||
String? email;
|
||||
String? phone;
|
||||
String? address;
|
||||
String? notes;
|
||||
String? latitude;
|
||||
String? longitude;
|
||||
|
||||
User(
|
||||
{this.address,
|
||||
this.email,
|
||||
this.id,
|
||||
this.name,
|
||||
this.notes,
|
||||
this.phone,
|
||||
this.role,
|
||||
this.username,
|
||||
this.latitude,
|
||||
this.longitude});
|
||||
|
||||
User.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
username = json['username'];
|
||||
role = json['role'];
|
||||
name = json['name'];
|
||||
email = json['email'];
|
||||
phone = json['phone'];
|
||||
address = json['address'];
|
||||
notes = json['notes'];
|
||||
latitude = json['latitude'];
|
||||
longitude = json['longitude'];
|
||||
}
|
||||
}
|
||||
440
lib/feature/settings/profile/profile_screen.dart
Normal file
440
lib/feature/settings/profile/profile_screen.dart
Normal file
@@ -0,0 +1,440 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../product/shared/shared_snack_bar.dart';
|
||||
import '../../../product/constant/icon/icon_constants.dart';
|
||||
import '../../../product/services/api_services.dart';
|
||||
import '../settings_bloc.dart';
|
||||
import '../../../product/shared/shared_input_decoration.dart';
|
||||
import '../../../product/extention/context_extention.dart';
|
||||
import '../../../product/services/language_services.dart';
|
||||
|
||||
import 'profile_model.dart';
|
||||
|
||||
changeUserInfomation(
|
||||
BuildContext context, User user, SettingsBloc settingsBloc) {
|
||||
final formKey = GlobalKey<FormState>();
|
||||
String username = user.name ?? "";
|
||||
String email = user.email ?? "";
|
||||
String tel = user.phone ?? "";
|
||||
String address = user.address ?? "";
|
||||
bool isChange = false;
|
||||
APIServices apiServices = APIServices();
|
||||
showModalBottomSheet(
|
||||
isScrollControlled: true,
|
||||
useSafeArea: true,
|
||||
context: context,
|
||||
builder: (modalBottomSheetContext) {
|
||||
return StreamBuilder<bool>(
|
||||
stream: settingsBloc.streamIsChangeProfileInfomation,
|
||||
initialData: isChange,
|
||||
builder: (context, isChangeSnapshot) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(appLocalization(context).change_profile_title),
|
||||
centerTitle: true,
|
||||
actions: [
|
||||
isChangeSnapshot.data ?? isChange
|
||||
? IconButton(
|
||||
onPressed: () async {
|
||||
if (formKey.currentState!.validate()) {
|
||||
formKey.currentState!.save();
|
||||
String latitude = user.latitude ?? "";
|
||||
String longitude = user.longitude ?? "";
|
||||
Map<String, dynamic> body = {
|
||||
"name": username,
|
||||
"email": email,
|
||||
"phone": tel,
|
||||
"address": address,
|
||||
"latitude": latitude,
|
||||
"longitude": longitude
|
||||
};
|
||||
int statusCode =
|
||||
await apiServices.updateUserProfile(body);
|
||||
if (statusCode == 200) {
|
||||
showNoIconTopSnackBar(
|
||||
modalBottomSheetContext,
|
||||
appLocalization(context)
|
||||
.notification_update_profile_success,
|
||||
Colors.green,
|
||||
Colors.white);
|
||||
} else {
|
||||
showNoIconTopSnackBar(
|
||||
modalBottomSheetContext,
|
||||
appLocalization(context)
|
||||
.notification_update_profile_failed,
|
||||
Colors.redAccent,
|
||||
Colors.white);
|
||||
}
|
||||
Navigator.pop(modalBottomSheetContext);
|
||||
}
|
||||
},
|
||||
icon:
|
||||
IconConstants.instance.getMaterialIcon(Icons.check),
|
||||
)
|
||||
: const SizedBox.shrink()
|
||||
],
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Form(
|
||||
key: formKey,
|
||||
child: Padding(
|
||||
padding: context.paddingLow,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
appLocalization(context).change_profile_username,
|
||||
style: context.titleMediumTextStyle,
|
||||
),
|
||||
Padding(
|
||||
padding: context.paddingLowVertical,
|
||||
child: TextFormField(
|
||||
initialValue: username,
|
||||
textInputAction: TextInputAction.next,
|
||||
validator: (usernameValue) {
|
||||
if (usernameValue == "null" ||
|
||||
usernameValue!.isEmpty) {
|
||||
return appLocalization(modalBottomSheetContext)
|
||||
.login_account_not_empty;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onChanged: (value) {
|
||||
isChange = true;
|
||||
settingsBloc.sinkIsChangeProfileInfomation
|
||||
.add(isChange);
|
||||
},
|
||||
onSaved: (newUsername) {
|
||||
username = newUsername!;
|
||||
},
|
||||
decoration: borderRadiusTopLeftAndBottomRight(
|
||||
modalBottomSheetContext,
|
||||
appLocalization(modalBottomSheetContext)
|
||||
.change_profile_username_hint),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
appLocalization(context).change_profile_email,
|
||||
style: context.titleMediumTextStyle,
|
||||
),
|
||||
Padding(
|
||||
padding: context.paddingLowVertical,
|
||||
child: TextFormField(
|
||||
initialValue: email,
|
||||
textInputAction: TextInputAction.next,
|
||||
validator: (emailValue) {
|
||||
if (emailValue == "null" || emailValue!.isEmpty) {
|
||||
return appLocalization(modalBottomSheetContext)
|
||||
.change_profile_email_not_empty;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onChanged: (value) {
|
||||
isChange = true;
|
||||
settingsBloc.sinkIsChangeProfileInfomation
|
||||
.add(isChange);
|
||||
},
|
||||
onSaved: (newEmail) {
|
||||
email = newEmail!;
|
||||
},
|
||||
decoration: borderRadiusTopLeftAndBottomRight(
|
||||
modalBottomSheetContext,
|
||||
appLocalization(modalBottomSheetContext)
|
||||
.change_profile_email_hint),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
appLocalization(context).change_profile_tel,
|
||||
style: context.titleMediumTextStyle,
|
||||
),
|
||||
Padding(
|
||||
padding: context.paddingLowVertical,
|
||||
child: TextFormField(
|
||||
initialValue: tel,
|
||||
textInputAction: TextInputAction.next,
|
||||
keyboardType: TextInputType.phone,
|
||||
validator: (telValue) {
|
||||
if (telValue == "null" || telValue!.isEmpty) {
|
||||
return appLocalization(modalBottomSheetContext)
|
||||
.change_profile_tel_not_empty;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onChanged: (value) {
|
||||
isChange = true;
|
||||
settingsBloc.sinkIsChangeProfileInfomation
|
||||
.add(isChange);
|
||||
},
|
||||
onSaved: (newTel) {
|
||||
tel = newTel!;
|
||||
},
|
||||
decoration: borderRadiusTopLeftAndBottomRight(
|
||||
modalBottomSheetContext,
|
||||
appLocalization(modalBottomSheetContext)
|
||||
.change_profile_tel_hint),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
appLocalization(context).change_profile_address,
|
||||
style: context.titleMediumTextStyle,
|
||||
),
|
||||
Padding(
|
||||
padding: context.paddingLowVertical,
|
||||
child: TextFormField(
|
||||
initialValue: address,
|
||||
textInputAction: TextInputAction.done,
|
||||
onSaved: (newAddress) {
|
||||
address = newAddress!;
|
||||
},
|
||||
onChanged: (value) {
|
||||
isChange = true;
|
||||
settingsBloc.sinkIsChangeProfileInfomation
|
||||
.add(isChange);
|
||||
},
|
||||
decoration: borderRadiusTopLeftAndBottomRight(
|
||||
modalBottomSheetContext,
|
||||
appLocalization(modalBottomSheetContext)
|
||||
.change_profile_address_hint),
|
||||
),
|
||||
),
|
||||
SizedBox(height: context.mediumValue),
|
||||
isChangeSnapshot.data ?? isChange
|
||||
? Center(
|
||||
child: TextButton(
|
||||
onPressed: () async {
|
||||
if (formKey.currentState!.validate()) {
|
||||
formKey.currentState!.save();
|
||||
String latitude = user.latitude ?? "";
|
||||
String longitude = user.longitude ?? "";
|
||||
Map<String, dynamic> body = {
|
||||
"name": username,
|
||||
"email": email,
|
||||
"phone": tel,
|
||||
"address": address,
|
||||
"latitude": latitude,
|
||||
"longitude": longitude
|
||||
};
|
||||
int statusCode = await apiServices
|
||||
.updateUserProfile(body);
|
||||
if (statusCode == 200) {
|
||||
showNoIconTopSnackBar(
|
||||
modalBottomSheetContext,
|
||||
appLocalization(context)
|
||||
.notification_update_profile_success,
|
||||
Colors.green,
|
||||
Colors.white);
|
||||
} else {
|
||||
showNoIconTopSnackBar(
|
||||
modalBottomSheetContext,
|
||||
appLocalization(context)
|
||||
.notification_update_profile_failed,
|
||||
Colors.redAccent,
|
||||
Colors.white);
|
||||
}
|
||||
Navigator.pop(modalBottomSheetContext);
|
||||
}
|
||||
},
|
||||
style: const ButtonStyle(
|
||||
backgroundColor:
|
||||
MaterialStatePropertyAll(Colors.blue),
|
||||
foregroundColor:
|
||||
MaterialStatePropertyAll(Colors.white),
|
||||
),
|
||||
child: Text(appLocalization(context)
|
||||
.update_button_content),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
changeUserPassword(BuildContext context, SettingsBloc settingsBloc) {
|
||||
final formKey = GlobalKey<FormState>();
|
||||
String oldPass = "";
|
||||
String newPass = "";
|
||||
APIServices apiServices = APIServices();
|
||||
bool isChange = false;
|
||||
showModalBottomSheet(
|
||||
isScrollControlled: true,
|
||||
useSafeArea: true,
|
||||
context: context,
|
||||
builder: (modalBottomSheetContext) {
|
||||
return StreamBuilder<bool>(
|
||||
stream: settingsBloc.streamIsChangeProfileInfomation,
|
||||
initialData: isChange,
|
||||
builder: (context, isChangeSnapshot) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(appLocalization(context).change_profile_title),
|
||||
centerTitle: true,
|
||||
actions: [
|
||||
isChangeSnapshot.data ?? isChange
|
||||
? IconButton(
|
||||
onPressed: () async {
|
||||
if (formKey.currentState!.validate()) {
|
||||
formKey.currentState!.save();
|
||||
Map<String, dynamic> body = {
|
||||
"password_old": oldPass,
|
||||
"password_new": newPass,
|
||||
};
|
||||
int statusCode =
|
||||
await apiServices.updateUserPassword(body);
|
||||
if (statusCode == 200) {
|
||||
showNoIconTopSnackBar(
|
||||
modalBottomSheetContext,
|
||||
appLocalization(context)
|
||||
.notification_update_password_success,
|
||||
Colors.green,
|
||||
Colors.white);
|
||||
} else {
|
||||
showNoIconTopSnackBar(
|
||||
modalBottomSheetContext,
|
||||
appLocalization(context)
|
||||
.notification_update_password_failed,
|
||||
Colors.redAccent,
|
||||
Colors.white);
|
||||
}
|
||||
Navigator.pop(modalBottomSheetContext);
|
||||
}
|
||||
},
|
||||
icon:
|
||||
IconConstants.instance.getMaterialIcon(Icons.check),
|
||||
)
|
||||
: const SizedBox.shrink()
|
||||
],
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Form(
|
||||
key: formKey,
|
||||
child: Padding(
|
||||
padding: context.paddingLow,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
appLocalization(context).change_profile_old_pass,
|
||||
style: context.titleMediumTextStyle,
|
||||
),
|
||||
Padding(
|
||||
padding: context.paddingLowVertical,
|
||||
child: TextFormField(
|
||||
initialValue: oldPass,
|
||||
textInputAction: TextInputAction.next,
|
||||
validator: (oldPassValue) {
|
||||
if (oldPassValue == "null" ||
|
||||
oldPassValue!.isEmpty) {
|
||||
return appLocalization(modalBottomSheetContext)
|
||||
.change_profile_old_pass_not_empty;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onChanged: (value) {
|
||||
isChange = true;
|
||||
settingsBloc.sinkIsChangeProfileInfomation
|
||||
.add(isChange);
|
||||
},
|
||||
onSaved: (newOldPass) {
|
||||
oldPass = newOldPass!;
|
||||
},
|
||||
decoration: borderRadiusTopLeftAndBottomRight(
|
||||
modalBottomSheetContext,
|
||||
appLocalization(modalBottomSheetContext)
|
||||
.change_profile_old_pass_hint),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
appLocalization(context).change_profile_new_pass,
|
||||
style: context.titleMediumTextStyle,
|
||||
),
|
||||
Padding(
|
||||
padding: context.paddingLowVertical,
|
||||
child: TextFormField(
|
||||
initialValue: newPass,
|
||||
textInputAction: TextInputAction.done,
|
||||
validator: (newPassValue) {
|
||||
if (newPassValue == "null" ||
|
||||
newPassValue!.isEmpty) {
|
||||
return appLocalization(modalBottomSheetContext)
|
||||
.change_profile_new_pass_not_empty;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onChanged: (value) {
|
||||
isChange = true;
|
||||
settingsBloc.sinkIsChangeProfileInfomation
|
||||
.add(isChange);
|
||||
},
|
||||
onSaved: (newnewPass) {
|
||||
newPass = newnewPass!;
|
||||
},
|
||||
decoration: borderRadiusTopLeftAndBottomRight(
|
||||
modalBottomSheetContext,
|
||||
appLocalization(modalBottomSheetContext)
|
||||
.change_profile_new_pass_hint),
|
||||
),
|
||||
),
|
||||
SizedBox(height: context.mediumValue),
|
||||
isChangeSnapshot.data ?? isChange
|
||||
? Center(
|
||||
child: TextButton(
|
||||
onPressed: () async {
|
||||
if (formKey.currentState!.validate()) {
|
||||
formKey.currentState!.save();
|
||||
Map<String, dynamic> body = {
|
||||
"password_old": oldPass,
|
||||
"password_new": newPass,
|
||||
};
|
||||
int statusCode = await apiServices
|
||||
.updateUserPassword(body);
|
||||
if (statusCode == 200) {
|
||||
showNoIconTopSnackBar(
|
||||
modalBottomSheetContext,
|
||||
appLocalization(context)
|
||||
.notification_update_password_success,
|
||||
Colors.green,
|
||||
Colors.white);
|
||||
} else {
|
||||
showNoIconTopSnackBar(
|
||||
modalBottomSheetContext,
|
||||
appLocalization(context)
|
||||
.notification_update_password_failed,
|
||||
Colors.redAccent,
|
||||
Colors.white);
|
||||
}
|
||||
Navigator.pop(modalBottomSheetContext);
|
||||
}
|
||||
},
|
||||
style: const ButtonStyle(
|
||||
backgroundColor:
|
||||
MaterialStatePropertyAll(Colors.blue),
|
||||
foregroundColor:
|
||||
MaterialStatePropertyAll(Colors.white),
|
||||
),
|
||||
child: Text(appLocalization(context)
|
||||
.update_button_content),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink()
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
25
lib/feature/settings/settings_bloc.dart
Normal file
25
lib/feature/settings/settings_bloc.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'profile/profile_model.dart';
|
||||
import '../../product/base/bloc/base_bloc.dart';
|
||||
|
||||
class SettingsBloc extends BlocBase {
|
||||
// Settings Screen
|
||||
final userProfile = StreamController<User>.broadcast();
|
||||
StreamSink<User> get sinkUserProfile => userProfile.sink;
|
||||
Stream<User> get streamUserProfile => userProfile.stream;
|
||||
|
||||
|
||||
// Profile Screen
|
||||
final isChangeProfileInfomation = StreamController<bool>.broadcast();
|
||||
StreamSink<bool> get sinkIsChangeProfileInfomation =>
|
||||
isChangeProfileInfomation.sink;
|
||||
Stream<bool> get streamIsChangeProfileInfomation =>
|
||||
isChangeProfileInfomation.stream;
|
||||
|
||||
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
}
|
||||
}
|
||||
153
lib/feature/settings/settings_screen.dart
Normal file
153
lib/feature/settings/settings_screen.dart
Normal file
@@ -0,0 +1,153 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import '../../product/constant/app/app_constants.dart';
|
||||
import 'profile/profile_screen.dart';
|
||||
import '../../product/constant/icon/icon_constants.dart';
|
||||
import '../../product/extention/context_extention.dart';
|
||||
import '../../product/services/api_services.dart';
|
||||
import 'profile/profile_model.dart';
|
||||
import 'settings_bloc.dart';
|
||||
import '../../product/base/bloc/base_bloc.dart';
|
||||
import '../../product/services/language_services.dart';
|
||||
|
||||
class SettingsScreen extends StatefulWidget {
|
||||
const SettingsScreen({super.key});
|
||||
|
||||
@override
|
||||
State<SettingsScreen> createState() => _SettingsScreenState();
|
||||
}
|
||||
|
||||
class _SettingsScreenState extends State<SettingsScreen> {
|
||||
late SettingsBloc settingsBloc;
|
||||
User user = User();
|
||||
APIServices apiServices = APIServices();
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
settingsBloc = BlocProvider.of(context);
|
||||
getUserProfile();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(appLocalization(context).profile_page_title),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: StreamBuilder<User>(
|
||||
stream: settingsBloc.streamUserProfile,
|
||||
initialData: user,
|
||||
builder: (context, userSnapshot) {
|
||||
return userSnapshot.data?.id == "" || user.id == ""
|
||||
? Center(
|
||||
child: CircularProgressIndicator(
|
||||
value: context.highValue,
|
||||
),
|
||||
)
|
||||
: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 70,
|
||||
child: CircleAvatar(
|
||||
radius: 60,
|
||||
child: CircleAvatar(
|
||||
radius: 50,
|
||||
child: Text(
|
||||
getAvatarContent(
|
||||
userSnapshot.data?.username ?? ""),
|
||||
style: const TextStyle(
|
||||
fontSize: 35, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: context.lowValue),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
userSnapshot.data?.name ?? "User Name",
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w900, fontSize: 26),
|
||||
)
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [Text(userSnapshot.data?.email ?? "Email")],
|
||||
),
|
||||
SizedBox(height: context.mediumValue),
|
||||
cardContent(
|
||||
Icons.account_circle_rounded,
|
||||
appLocalization(context).profile_change_info,
|
||||
),
|
||||
SizedBox(height: context.lowValue),
|
||||
cardContent(
|
||||
Icons.lock_outline,
|
||||
appLocalization(context).profile_change_pass,
|
||||
),
|
||||
SizedBox(height: context.lowValue),
|
||||
cardContent(
|
||||
Icons.settings_outlined,
|
||||
appLocalization(context).profile_setting,
|
||||
),
|
||||
SizedBox(height: context.lowValue),
|
||||
cardContent(
|
||||
Icons.logout_outlined,
|
||||
appLocalization(context).log_out,
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
cardContent(IconData icon, String content) {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
if (icon == Icons.account_circle_rounded) {
|
||||
changeUserInfomation(context, user, settingsBloc);
|
||||
} else if (icon == Icons.lock_outline) {
|
||||
changeUserPassword(context, settingsBloc);
|
||||
} else if (icon == Icons.settings_outlined) {
|
||||
context.push(ApplicationConstants.DEVICE_NOTIFICATIONS_SETTINGS);
|
||||
} else {
|
||||
await apiServices.logOut(context);
|
||||
}
|
||||
},
|
||||
child: Card(
|
||||
margin: const EdgeInsets.only(left: 35, right: 35, bottom: 10),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)),
|
||||
child: ListTile(
|
||||
leading: IconConstants.instance.getMaterialIcon(icon),
|
||||
title: Text(
|
||||
content,
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
trailing: const Icon(
|
||||
Icons.arrow_forward_ios_outlined,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void getUserProfile() async {
|
||||
String data = await apiServices.getUserDetail();
|
||||
user = User.fromJson(jsonDecode(data));
|
||||
settingsBloc.sinkUserProfile.add(user);
|
||||
}
|
||||
|
||||
String getAvatarContent(String username) {
|
||||
String name = "";
|
||||
if (username.isNotEmpty) {
|
||||
name = username[0].toUpperCase();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user