Added authorization page and page with a list of organizations

This commit is contained in:
max 2025-07-17 10:09:19 +03:00
parent 2a26c396bd
commit c21a43190f
9 changed files with 501 additions and 1 deletions

View File

@ -0,0 +1,6 @@
abstract class Repository<T> {
Future<List<T>> load();
Future<T> add(T organization);
Future<void> update(T organization);
Future<void> delete(String? id);
}

View File

@ -0,0 +1,50 @@
import 'package:instruction_app/data/interface/IRepository.dart';
import 'package:instruction_app/models/organization.dart';
class LocalOrganizationRepository implements Repository<Organization> {
final List<Organization> _organizations = [
Organization(
id: '1',
title: 'Про-Эксперт'
),
Organization(
id: '2',
title:'Лидер'
),
Organization(
id: '3',
title: 'Авангард'
),
Organization(
id: '4',
title: 'Галактика'
)
];
@override
Future<List<Organization>> load() async {
return List.of(_organizations);
}
@override
Future<Organization> add(Organization organization) async {
// изменить id
_organizations.add(organization);
return organization;
}
@override
Future<void> update(Organization organization) async {
var index = _organizations.indexWhere((item) => item.id == organization.id);
if (index != -1) {
_organizations[index] = organization;
}
}
@override
Future<void> delete(String? id) async {
if (id == null)
return;
_organizations.removeWhere((item) => item.id == id);
}
}

View File

@ -1,6 +1,12 @@
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:instruction_app/data/interface/IRepository.dart';
import 'package:instruction_app/data/organization_repository.dart';
import 'package:instruction_app/models/organization.dart';
import 'package:instruction_app/providers/organization_ptovider.dart';
import 'package:instruction_app/screens/auth/login_page.dart';
import 'package:instruction_app/screens/organization_page/list_organization_page.dart';
import 'package:provider/provider.dart';
import 'data/instruction_repository.dart';
import 'providers/instruction_provider.dart';
@ -11,10 +17,13 @@ void main() {
final InstructionRepository repository = LocalInstructionRepository();
//LocalInstructionRepository();
// final Repository<Organization> organizationRepository = LocalOrganizationRepository();
runApp(
// "Предоставляем" наш провайдер всему дереву виджетов
ChangeNotifierProvider(
create: (context) => InstructionProvider(repository),
// create: (context) => OrganizationProvider(organizationRepository),
child: const MyApp(),
),
);
@ -31,7 +40,7 @@ class MyApp extends StatelessWidget {
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const InstructionListPage(),
home: const AuthorizationPage(),
);
}
}

View File

@ -0,0 +1,30 @@
class Organization {
final String? id;
final String title;
Organization({
this.id,
required this.title
});
Organization copyWith(String? id, String? title) {
return Organization(
id: id ?? this.id,
title: title ?? this.title
);
}
factory Organization.fromJson(Map<String, dynamic> json) {
return Organization(
id: json['id'],
title: json['title'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'title': title,
};
}
}

View File

@ -0,0 +1,45 @@
import 'package:flutter/material.dart';
import 'package:instruction_app/data/interface/IRepository.dart';
import 'package:instruction_app/models/organization.dart';
class OrganizationProvider extends ChangeNotifier {
final Repository<Organization> _repository;
OrganizationProvider(this._repository);
List<Organization> _organizations = [];
List<Organization> get organizations => _organizations;
bool _isLoading = false;
bool get isLoading => _isLoading;
Future<void> loadOrganizations() async {
setLoading(false);
_organizations = await _repository.load();
setLoading(true);
}
Future<void> addOrganization(String title) async {
final newOrganization = Organization(
title: title
);
await _repository.add(newOrganization);
await loadOrganizations();
}
Future<void> updateOrganization(Organization organization) async {
await _repository.update(organization);
await loadOrganizations();
}
Future<void> deleteOrganization(String id) async {
await _repository.delete(id);
_organizations.removeWhere((item) => item.id == id);
notifyListeners();
}
void setLoading(bool value){
_isLoading = value;
notifyListeners();
}
}

View File

@ -0,0 +1,88 @@
import 'package:flutter/material.dart';
class AuthorizationPage extends StatefulWidget {
const AuthorizationPage({super.key});
@override
State<AuthorizationPage> createState() => _AuthorizationPage();
}
class _AuthorizationPage extends State<AuthorizationPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Авторизация"),
),
body: Padding(
padding: EdgeInsets.all(80),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
FormWidget(),
SizedBox(height: 25,),
TextButton(
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(Colors.blue),
foregroundColor: WidgetStateProperty.all(Colors.white),
),
onPressed: () {},
child: Text("Войти")
)
],
)
)
);
}
}
class FormWidget extends StatefulWidget {
const FormWidget({super.key});
@override
State<FormWidget> createState() => _FormWidget();
}
class _FormWidget extends State<FormWidget> {
final borederRadius = BorderRadius.all(Radius.circular(10));
bool passwordVisible = false;
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
decoration: InputDecoration(
prefixIcon: Icon(Icons.account_circle),
border: OutlineInputBorder(
borderRadius: borederRadius
),
labelText: 'Логин'
)
),
SizedBox(height: 25,),
TextField(
obscureText: !passwordVisible,
decoration: InputDecoration(
prefixIcon: Icon(Icons.lock),
border: OutlineInputBorder(
borderRadius: borederRadius
),
labelText: 'Пароль',
suffixIcon: IconButton(
icon: Icon(
passwordVisible ? Icons.visibility : Icons.visibility_off
),
onPressed: () {
setState(() {
passwordVisible = !passwordVisible;
});
}
)
)
),
],
);
}
}

View File

@ -0,0 +1,88 @@
import 'package:flutter/material.dart';
import 'package:instruction_app/models/organization.dart';
import 'package:instruction_app/providers/organization_ptovider.dart';
import 'package:instruction_app/screens/organization_page/edit_organization_page.dart';
import 'package:provider/provider.dart';
class OrganizationDetailPage extends StatefulWidget {
final Organization initialOrganization;
const OrganizationDetailPage({super.key, required this.initialOrganization});
@override
State<OrganizationDetailPage> createState() => _OrganizationDetailPage();
}
class _OrganizationDetailPage extends State<OrganizationDetailPage>{
late Organization editOrganization = widget.initialOrganization;
void _deleteOrganization(BuildContext context) {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Подтверждение операции'),
content: Text("Вы уверены, что хотите удалить организацию \"${editOrganization.title}\"?"),
actions: [
TextButton(onPressed: () => Navigator.of(ctx).pop(), child: const Text('Отмена')),
TextButton(
onPressed: () {
context.read<OrganizationProvider>().deleteOrganization(editOrganization.id!);
Navigator.of(ctx).pop();
Navigator.of(context).pop();
},
child: const Text('Удалить', style: TextStyle(color: Colors.red)),
),
],
)
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(editOrganization.title, overflow: TextOverflow.ellipsis),
actions: [
IconButton(
icon: const Icon(Icons.edit),
tooltip: 'Редактировать',
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(builder: (ctx) => OrganizationAddPage(initialOrganization: editOrganization)))
.then((value) => {
setState(() {
if (value != null && value is Organization)
editOrganization = value;
})
}),
),
IconButton(
icon: const Icon(Icons.delete),
tooltip: 'Удалить',
onPressed: () => _deleteOrganization(context),
),
]
),
body: Padding(
padding: const EdgeInsets.all(16),
child: ListView(
children: [
Text('Содержание:',
style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.all(12),
height: 200,
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.05),
borderRadius: BorderRadius.circular(8),
),
child: const Text('Описание ...',
style: const TextStyle(fontSize: 16, height: 1.5)
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,97 @@
import 'package:flutter/material.dart';
import 'package:instruction_app/models/organization.dart';
import 'package:instruction_app/providers/organization_ptovider.dart';
import 'package:provider/provider.dart';
class OrganizationAddPage extends StatefulWidget {
final Organization? initialOrganization;
const OrganizationAddPage({super.key, this.initialOrganization});
@override
State<OrganizationAddPage> createState() => _OrganizationAddPage();
}
class _OrganizationAddPage extends State<OrganizationAddPage> {
final _formKey = GlobalKey<FormState>();
late TextEditingController _titleController;
bool get _isEditing => widget.initialOrganization != null;
@override
void initState() {
super.initState();
_titleController = TextEditingController(text: widget.initialOrganization?.title ?? '');
}
@override
void dispose() {
_titleController.dispose();
super.dispose();
}
Future<void> _saveForm() async {
if(_formKey.currentState!.validate()) {
final provider = context.read<OrganizationProvider>();
final title = _titleController.text;
final updateOrganization = new Organization(
id: widget.initialOrganization?.id,
title: title
);
// showDialog(
// context: context,
// barrierDismissible: false,
// builder: (context) => Center(child: CircularProgressIndicator())
// );
try {
if(_isEditing) {
await provider.updateOrganization(updateOrganization);
} else {
await provider.addOrganization(title);
}
if (mounted) {
Navigator.of(context).pop(updateOrganization);
}
} catch(exeption) {
if(mounted) {
// Navigator.of(context).pop(updateOrganization);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Ошибка сохранения: $exeption')),
);
}
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_isEditing ? 'Редактировать' : 'Добавление организации'),
actions: [
IconButton(
tooltip: 'Сохранить',
icon: const Icon(Icons.save),
onPressed: _saveForm
)],
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: ListView(
children: [
TextFormField(
controller: _titleController,
decoration: const InputDecoration(labelText: 'Название', border: OutlineInputBorder()),
validator: (v) => v == null || v.isEmpty ? 'Название не может быть пустым' : null,
),
],
)
)
)
);
}
}

View File

@ -0,0 +1,87 @@
import 'package:flutter/material.dart';
import 'package:instruction_app/providers/organization_ptovider.dart';
import 'package:instruction_app/screens/organization_page/edit_organization_page.dart';
import 'package:instruction_app/screens/organization_page/detail_organization_page.dart';
import 'package:provider/provider.dart';
class OrganizationListPage extends StatefulWidget {
const OrganizationListPage({super.key});
@override
State<OrganizationListPage> createState() => _OrganizationListPage();
}
class _OrganizationListPage extends State<OrganizationListPage>{
@override
void initState() {
super.initState();
Future.microtask(() =>
Provider.of<OrganizationProvider>(context, listen: false)
.loadOrganizations());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Список организаций"),
),
body: Consumer<OrganizationProvider>(
builder: (context, provider, child) {
return Column(
children: [
provider.isLoading ?
_widgetOrganizationList(provider) :
const Expanded(child: Center(child: CircularProgressIndicator()))
],
);
}
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// возможно поменять
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => const OrganizationAddPage()),
);
},
tooltip: "Добавить организацию",
child: const Icon(Icons.add),
),
);
}
Widget _widgetOrganizationList(OrganizationProvider provider){
if (!provider.isLoading) {
return const Expanded(
child: Center(
child: Text(
'Организации не найдены',
textAlign: TextAlign.center,
),
),
);
}
return Expanded(
child: ListView.builder(
itemCount: provider.organizations.length,
itemBuilder: (context, index) {
final organization = provider.organizations[index];
return Card(
margin: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8
),
child: ListTile(
title: Text(organization.title),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (ctx) => OrganizationDetailPage(initialOrganization: organization),
));
},
),
);
}
),
);
}
}