Added authorization page and page with a list of organizations
This commit is contained in:
parent
2a26c396bd
commit
c21a43190f
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
// lib/main.dart
|
// lib/main.dart
|
||||||
|
|
||||||
import 'package:flutter/material.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 'package:provider/provider.dart';
|
||||||
import 'data/instruction_repository.dart';
|
import 'data/instruction_repository.dart';
|
||||||
import 'providers/instruction_provider.dart';
|
import 'providers/instruction_provider.dart';
|
||||||
|
|
@ -11,10 +17,13 @@ void main() {
|
||||||
final InstructionRepository repository = LocalInstructionRepository();
|
final InstructionRepository repository = LocalInstructionRepository();
|
||||||
//LocalInstructionRepository();
|
//LocalInstructionRepository();
|
||||||
|
|
||||||
|
// final Repository<Organization> organizationRepository = LocalOrganizationRepository();
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
// "Предоставляем" наш провайдер всему дереву виджетов
|
// "Предоставляем" наш провайдер всему дереву виджетов
|
||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create: (context) => InstructionProvider(repository),
|
create: (context) => InstructionProvider(repository),
|
||||||
|
// create: (context) => OrganizationProvider(organizationRepository),
|
||||||
child: const MyApp(),
|
child: const MyApp(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -31,7 +40,7 @@ class MyApp extends StatelessWidget {
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
),
|
),
|
||||||
home: const InstructionListPage(),
|
home: const AuthorizationPage(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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),
|
||||||
|
));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue