frontendPlayer/lib/presentation/pages/new_license_page.dart
2026-04-10 09:55:19 +03:30

463 lines
15 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../core/constants/app_colors.dart';
import '../../core/constants/app_text_styles.dart';
import '../../data/services/api_service.dart';
import '../widgets/teacher_panel_scaffold.dart';
import 'course_page.dart';
import 'dashboard_page.dart';
import 'license_page.dart';
import 'ticket_page.dart';
import 'transaction_page.dart';
class NewLicensePage extends StatefulWidget {
final int courseId;
final String courseTitle;
const NewLicensePage({
super.key,
required this.courseId,
required this.courseTitle,
});
@override
State<NewLicensePage> createState() => _NewLicensePageState();
}
class _NewLicensePageState extends State<NewLicensePage> {
final TextEditingController _studentPhoneController = TextEditingController();
final TextEditingController _studentNameController = TextEditingController();
final TextEditingController _studentEmailController = TextEditingController();
final List<_DeviceLimitForm> _deviceLimits = <_DeviceLimitForm>[
_DeviceLimitForm(),
];
bool _isSubmitting = false;
String? _errorMessage;
@override
void dispose() {
_studentPhoneController.dispose();
_studentNameController.dispose();
_studentEmailController.dispose();
for (final item in _deviceLimits) {
item.dispose();
}
super.dispose();
}
void _addDeviceLimit() {
setState(() {
_deviceLimits.add(_DeviceLimitForm());
});
}
void _removeDeviceLimit(int index) {
if (_deviceLimits.length <= 1) return;
final removed = _deviceLimits.removeAt(index);
removed.dispose();
setState(() {});
}
Future<void> _submit() async {
final studentPhone = _studentPhoneController.text.trim();
final studentName = _studentNameController.text.trim();
final studentEmail = _studentEmailController.text.trim();
if (!RegExp(r'^09\d{9}$').hasMatch(studentPhone)) {
setState(() {
_errorMessage = 'شماره موبایل دانشجو باید با 09 شروع شود و 11 رقم باشد.';
});
return;
}
if (studentName.length > 255) {
setState(() {
_errorMessage = 'حداکثر طول نام دانشجو 255 کاراکتر است.';
});
return;
}
if (studentEmail.isNotEmpty) {
final emailRegex = RegExp(r'^[^@\s]+@[^@\s]+\.[^@\s]+$');
if (!emailRegex.hasMatch(studentEmail)) {
setState(() {
_errorMessage = 'ایمیل دانشجو معتبر نیست.';
});
return;
}
if (studentEmail.length > 255) {
setState(() {
_errorMessage = 'حداکثر طول ایمیل دانشجو 255 کاراکتر است.';
});
return;
}
}
final payloadDeviceLimits = <Map<String, dynamic>>[];
for (final item in _deviceLimits) {
final limit = int.tryParse(item.limitController.text.trim());
if (limit == null || limit < 1 || limit > 10) {
setState(() {
_errorMessage = 'مقدار limit هر آیتم باید عدد بین 1 تا 10 باشد.';
});
return;
}
payloadDeviceLimits.add({
'os': item.os,
'limit': limit,
});
}
if (payloadDeviceLimits.isEmpty) {
setState(() {
_errorMessage = 'حداقل یک device limit باید وارد شود.';
});
return;
}
setState(() {
_isSubmitting = true;
_errorMessage = null;
});
try {
final apiService = Provider.of<ApiService>(context, listen: false);
final response = await apiService.createCourseLicense(
courseId: widget.courseId,
studentPhone: studentPhone,
studentName: studentName.isEmpty ? null : studentName,
studentEmail: studentEmail.isEmpty ? null : studentEmail,
deviceLimits: payloadDeviceLimits,
);
if (!mounted) return;
final message = (response['message'] ?? 'لایسنس با موفقیت ساخته شد.').toString();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
Navigator.of(context).pop(true);
} catch (e) {
setState(() {
_errorMessage = e.toString();
});
} finally {
if (mounted) {
setState(() {
_isSubmitting = false;
});
}
}
}
@override
Widget build(BuildContext context) {
final isDesktop = MediaQuery.of(context).size.width > 900;
return TeacherPanelScaffold(
selectedMenu: PanelMenu.license,
onDashboardTap: () {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => const DashboardPage()),
);
},
onCourseTap: () {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => const CoursePage()),
);
},
onLicenseTap: () {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => const PanelLicensePage()),
);
},
onTransactionTap: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => const TransactionPage()),
);
},
onTicketTap: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => const TicketPage()),
);
},
child: SingleChildScrollView(
padding: EdgeInsets.all(isDesktop ? 24 : 12),
child: Container(
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.35),
borderRadius: BorderRadius.circular(10),
),
padding: EdgeInsets.all(isDesktop ? 18 : 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InkWell(
onTap: () => Navigator.pop(context),
child: const Icon(Icons.arrow_back, color: AppColors.sidebarBg),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
const Icon(Icons.add_card, size: 18, color: AppColors.sidebarBg),
const SizedBox(width: 6),
Text(
'ساخت لایسنس جدید',
style: AppTextStyles.headlineMedium.copyWith(
color: AppColors.sidebarBg,
fontSize: 20,
),
),
],
),
const SizedBox(height: 8),
Container(
width: double.infinity,
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: const Color(0xFFEFF5FF),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColors.sidebarBg.withValues(alpha: 0.3)),
),
child: Text(
'این لایسنس برای دوره: ${widget.courseTitle} (ID: ${widget.courseId})',
style: AppTextStyles.bodyLarge.copyWith(color: AppColors.sidebarBg),
),
),
const SizedBox(height: 12),
Container(height: 1, color: Colors.black12),
const SizedBox(height: 16),
_buildLabel('شماره موبایل دانشجو *'),
const SizedBox(height: 6),
_buildInput(
_studentPhoneController,
keyboardType: TextInputType.phone,
hint: '09xxxxxxxxx',
),
const SizedBox(height: 12),
_buildLabel('نام دانشجو (اختیاری)'),
const SizedBox(height: 6),
_buildInput(_studentNameController),
const SizedBox(height: 12),
_buildLabel('ایمیل دانشجو (اختیاری)'),
const SizedBox(height: 6),
_buildInput(
_studentEmailController,
keyboardType: TextInputType.emailAddress,
),
const SizedBox(height: 22),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Device Limits *', style: AppTextStyles.headlineMedium),
OutlinedButton.icon(
onPressed: _addDeviceLimit,
icon: const Icon(Icons.add, size: 16),
label: const Text('افزودن آیتم'),
),
],
),
const SizedBox(height: 10),
..._deviceLimits.asMap().entries.map(
(entry) {
final index = entry.key;
final item = entry.value;
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: _buildDeviceLimitRow(index, item, isDesktop),
);
},
),
if (_errorMessage != null) ...[
const SizedBox(height: 10),
Text(
_errorMessage!,
style: AppTextStyles.bodyMedium.copyWith(color: Colors.red),
),
],
const SizedBox(height: 22),
Wrap(
spacing: 10,
runSpacing: 10,
children: [
SizedBox(
width: isDesktop ? 140 : double.infinity,
height: 44,
child: ElevatedButton(
onPressed: _isSubmitting ? null : _submit,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF43C61C),
foregroundColor: Colors.white,
),
child: _isSubmitting
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: const Text('تایید'),
),
),
SizedBox(
width: isDesktop ? 140 : double.infinity,
height: 44,
child: ElevatedButton(
onPressed: _isSubmitting ? null : () => Navigator.pop(context),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFC95757),
foregroundColor: Colors.white,
),
child: const Text('انصراف'),
),
),
],
),
],
),
),
),
);
}
Widget _buildDeviceLimitRow(int index, _DeviceLimitForm item, bool isDesktop) {
final osItems = const [
DropdownMenuItem(value: 'android', child: Text('android')),
DropdownMenuItem(value: 'windows', child: Text('windows')),
DropdownMenuItem(value: 'linux', child: Text('linux')),
DropdownMenuItem(value: 'ios', child: Text('ios')),
];
if (!isDesktop) {
return Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.black12),
),
child: Column(
children: [
DropdownButtonFormField<String>(
initialValue: item.os,
decoration: _inputDecoration('os'),
items: osItems,
onChanged: (v) {
if (v == null) return;
setState(() {
item.os = v;
});
},
),
const SizedBox(height: 8),
TextField(
controller: item.limitController,
keyboardType: TextInputType.number,
decoration: _inputDecoration('limit (1..10)'),
),
const SizedBox(height: 8),
Align(
alignment: Alignment.centerLeft,
child: IconButton(
onPressed: _deviceLimits.length > 1 ? () => _removeDeviceLimit(index) : null,
icon: const Icon(Icons.delete_outline),
),
),
],
),
);
}
return Row(
children: [
Expanded(
flex: 3,
child: DropdownButtonFormField<String>(
initialValue: item.os,
decoration: _inputDecoration('os'),
items: osItems,
onChanged: (v) {
if (v == null) return;
setState(() {
item.os = v;
});
},
),
),
const SizedBox(width: 8),
Expanded(
flex: 2,
child: TextField(
controller: item.limitController,
keyboardType: TextInputType.number,
decoration: _inputDecoration('limit (1..10)'),
),
),
const SizedBox(width: 8),
IconButton(
onPressed: _deviceLimits.length > 1 ? () => _removeDeviceLimit(index) : null,
icon: const Icon(Icons.delete_outline),
),
],
);
}
Widget _buildLabel(String text) {
return Text(text, style: AppTextStyles.bodyLarge);
}
Widget _buildInput(
TextEditingController controller, {
TextInputType? keyboardType,
String? hint,
}) {
return TextField(
controller: controller,
keyboardType: keyboardType,
decoration: _inputDecoration(hint),
);
}
InputDecoration _inputDecoration(String? hint) {
return InputDecoration(
hintText: hint,
isDense: true,
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: AppColors.sidebarBg.withValues(alpha: 0.7)),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: AppColors.sidebarBg.withValues(alpha: 0.7)),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
);
}
}
class _DeviceLimitForm {
String os = 'android';
final TextEditingController limitController;
_DeviceLimitForm({
String limit = '1',
}) : limitController = TextEditingController(text: limit);
void dispose() {
limitController.dispose();
}
}