feat: implement spine architecture and main pages
This commit is contained in:
parent
83e963dc55
commit
8e44718def
35
lib/logic/appsettingscubit.dart
Normal file
35
lib/logic/appsettingscubit.dart
Normal file
|
@ -0,0 +1,35 @@
|
|||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:selfprivacy/logic/config.dart';
|
||||
|
||||
export 'package:provider/provider.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
part 'appsettingsstate.dart';
|
||||
|
||||
class AppSettingsCubit extends Cubit<AppSettingsState> {
|
||||
AppSettingsCubit({
|
||||
required final String key,
|
||||
}) : super(
|
||||
AppSettingsState(
|
||||
key: key,
|
||||
),
|
||||
);
|
||||
|
||||
Box box = Hive.box(BNames.appSettingsBox);
|
||||
|
||||
void load() {
|
||||
final String? key = box.get(BNames.serverKey);
|
||||
emit(
|
||||
state.copyWith(
|
||||
key: key,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void updateServerKey({required final String key}) {
|
||||
box.put(BNames.serverKey, key);
|
||||
emit(state.copyWith(key: key));
|
||||
}
|
||||
}
|
19
lib/logic/appsettingsstate.dart
Normal file
19
lib/logic/appsettingsstate.dart
Normal file
|
@ -0,0 +1,19 @@
|
|||
part of 'appsettingscubit.dart';
|
||||
|
||||
class AppSettingsState extends Equatable {
|
||||
const AppSettingsState({
|
||||
required this.key,
|
||||
});
|
||||
|
||||
final String key;
|
||||
|
||||
AppSettingsState copyWith({
|
||||
final String? key,
|
||||
}) =>
|
||||
AppSettingsState(
|
||||
key: key ?? this.key,
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object> get props => [key];
|
||||
}
|
118
lib/logic/config.dart
Normal file
118
lib/logic/config.dart
Normal file
|
@ -0,0 +1,118 @@
|
|||
import 'package:get_it/get_it.dart';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:selfprivacy/logic/appsettingscubit.dart';
|
||||
|
||||
final GetIt getIt = GetIt.instance;
|
||||
|
||||
Future<void> getItSetup() async {
|
||||
getIt.registerSingleton<NavigationService>(NavigationService());
|
||||
|
||||
getIt.registerSingleton<TimerModel>(TimerModel());
|
||||
getIt.registerSingleton<ApiConfigModel>(ApiConfigModel()..init());
|
||||
|
||||
await getIt.allReady();
|
||||
}
|
||||
|
||||
class ApiConfigModel {
|
||||
final Box _box = Hive.box(BNames.serverInstallationBox);
|
||||
|
||||
String? get serverKey => _serverKey;
|
||||
String? _serverKey;
|
||||
|
||||
Future<void> storeServerProviderKey(final String value) async {
|
||||
await _box.put(BNames.serverKey, value);
|
||||
_serverKey = value;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_serverKey = null;
|
||||
}
|
||||
|
||||
void init() {
|
||||
_serverKey = _box.get(BNames.serverKey);
|
||||
}
|
||||
}
|
||||
|
||||
class HiveConfig {
|
||||
static Future<void> init() async {
|
||||
await Hive.initFlutter();
|
||||
|
||||
await Hive.openBox(BNames.appSettingsBox);
|
||||
|
||||
final HiveAesCipher cipher = HiveAesCipher(
|
||||
await getEncryptedKey(BNames.serverInstallationEncryptionKey),
|
||||
);
|
||||
|
||||
await Hive.openBox(BNames.serverInstallationBox, encryptionCipher: cipher);
|
||||
}
|
||||
|
||||
static Future<Uint8List> getEncryptedKey(final String encKey) async {
|
||||
const FlutterSecureStorage secureStorage = FlutterSecureStorage();
|
||||
final bool hasEncryptionKey = await secureStorage.containsKey(key: encKey);
|
||||
if (!hasEncryptionKey) {
|
||||
final List<int> key = Hive.generateSecureKey();
|
||||
await secureStorage.write(key: encKey, value: base64UrlEncode(key));
|
||||
}
|
||||
|
||||
final String? string = await secureStorage.read(key: encKey);
|
||||
return base64Url.decode(string!);
|
||||
}
|
||||
}
|
||||
|
||||
class BNames {
|
||||
static String appSettingsBox = 'appSettingsBox';
|
||||
static String serverInstallationEncryptionKey = 'key';
|
||||
static String serverInstallationBox = 'appConfig';
|
||||
static String rootKeys = 'rootKeys';
|
||||
static String serverKey = 'serverKey';
|
||||
}
|
||||
|
||||
class NavigationService {
|
||||
final GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey =
|
||||
GlobalKey<ScaffoldMessengerState>();
|
||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
NavigatorState? get navigator => navigatorKey.currentState;
|
||||
|
||||
void showPopUpDialog(final AlertDialog dialog) {
|
||||
final BuildContext context = navigatorKey.currentState!.overlay!.context;
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (final _) => dialog,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TimerModel extends ChangeNotifier {
|
||||
DateTime _time = DateTime.now();
|
||||
|
||||
DateTime get time => _time;
|
||||
|
||||
void restart() {
|
||||
_time = DateTime.now();
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
class BlocAndProviderConfig extends StatelessWidget {
|
||||
const BlocAndProviderConfig({super.key, this.child});
|
||||
|
||||
final Widget? child;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => MultiProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (final _) => AppSettingsCubit(key: '')..load(),
|
||||
),
|
||||
],
|
||||
child: child,
|
||||
);
|
||||
}
|
|
@ -1 +1,69 @@
|
|||
void main() async {}
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:selfprivacy/logic/appsettingscubit.dart';
|
||||
import 'package:selfprivacy/logic/config.dart';
|
||||
import 'package:selfprivacy/ui/root.dart';
|
||||
import 'package:wakelock/wakelock.dart';
|
||||
import 'package:timezone/data/latest.dart' as tz;
|
||||
|
||||
class SimpleBlocObserver extends BlocObserver {
|
||||
SimpleBlocObserver();
|
||||
}
|
||||
|
||||
void main() async {
|
||||
await HiveConfig.init();
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||
|
||||
try {
|
||||
await Wakelock.enable();
|
||||
} on PlatformException catch (e) {
|
||||
print(e);
|
||||
}
|
||||
|
||||
await getItSetup();
|
||||
tz.initializeTimeZones();
|
||||
|
||||
BlocOverrides.runZoned(
|
||||
() => runApp(const MyApp()),
|
||||
blocObserver: SimpleBlocObserver(),
|
||||
);
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) =>
|
||||
AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle.light, // Manually changing appbar color
|
||||
child: BlocAndProviderConfig(
|
||||
child: BlocBuilder<AppSettingsCubit, AppSettingsState>(
|
||||
builder: (
|
||||
final BuildContext context,
|
||||
final AppSettingsState appSettings,
|
||||
) =>
|
||||
MaterialApp(
|
||||
scaffoldMessengerKey:
|
||||
getIt.get<NavigationService>().scaffoldMessengerKey,
|
||||
navigatorKey: getIt.get<NavigationService>().navigatorKey,
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'Admin Panel',
|
||||
home: const RootPage(),
|
||||
builder: (final BuildContext context, final Widget? widget) {
|
||||
Widget error = const Text('...rendering error...');
|
||||
if (widget is Scaffold || widget is Navigator) {
|
||||
error = Scaffold(body: Center(child: error));
|
||||
}
|
||||
ErrorWidget.builder =
|
||||
(final FlutterErrorDetails errorDetails) => error;
|
||||
return widget!;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
13
lib/models/courier.dart
Normal file
13
lib/models/courier.dart
Normal file
|
@ -0,0 +1,13 @@
|
|||
class Courier {
|
||||
Courier({
|
||||
required this.name,
|
||||
required this.surname,
|
||||
required this.id,
|
||||
required this.phone,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String surname;
|
||||
final String phone;
|
||||
final int id;
|
||||
}
|
13
lib/models/item.dart
Normal file
13
lib/models/item.dart
Normal file
|
@ -0,0 +1,13 @@
|
|||
class Item {
|
||||
Item({
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.id,
|
||||
required this.price,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String description;
|
||||
final int price;
|
||||
final int id;
|
||||
}
|
30
lib/models/order.dart
Normal file
30
lib/models/order.dart
Normal file
|
@ -0,0 +1,30 @@
|
|||
import 'package:selfprivacy/models/item.dart';
|
||||
|
||||
class Order {
|
||||
Order({
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.id,
|
||||
required this.customerName,
|
||||
required this.customerPhone,
|
||||
required this.orderDate,
|
||||
required this.deliveryDate,
|
||||
required this.address,
|
||||
required this.status,
|
||||
required this.courierId,
|
||||
required this.items,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String description;
|
||||
final String customerName;
|
||||
final String customerPhone;
|
||||
final String orderDate;
|
||||
final String deliveryDate;
|
||||
final String address;
|
||||
final String status;
|
||||
final int id;
|
||||
final int courierId;
|
||||
|
||||
final List<Item> items;
|
||||
}
|
57
lib/ui/brandtabbar.dart
Normal file
57
lib/ui/brandtabbar.dart
Normal file
|
@ -0,0 +1,57 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class BrandTabBar extends StatefulWidget {
|
||||
const BrandTabBar({super.key, this.controller});
|
||||
|
||||
final TabController? controller;
|
||||
@override
|
||||
State<BrandTabBar> createState() => _BrandTabBarState();
|
||||
}
|
||||
|
||||
class _BrandTabBarState extends State<BrandTabBar> {
|
||||
int? currentIndex;
|
||||
@override
|
||||
void initState() {
|
||||
currentIndex = widget.controller!.index;
|
||||
widget.controller!.addListener(_listener);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void _listener() {
|
||||
if (currentIndex != widget.controller!.index) {
|
||||
setState(() {
|
||||
currentIndex = widget.controller!.index;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.controller ?? widget.controller!.removeListener(_listener);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => NavigationBar(
|
||||
destinations: [
|
||||
_getIconButton('Товары', Icons.add_box_outlined, 0),
|
||||
_getIconButton('Курьеры', Icons.person_pin_outlined, 1),
|
||||
_getIconButton('Заказы', Icons.confirmation_num_outlined, 2),
|
||||
],
|
||||
onDestinationSelected: (final index) {
|
||||
widget.controller!.animateTo(index);
|
||||
},
|
||||
selectedIndex: currentIndex ?? 0,
|
||||
labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected,
|
||||
);
|
||||
|
||||
NavigationDestination _getIconButton(
|
||||
final String label,
|
||||
final IconData iconData,
|
||||
final int index,
|
||||
) =>
|
||||
NavigationDestination(
|
||||
icon: Icon(iconData),
|
||||
label: label,
|
||||
);
|
||||
}
|
44
lib/ui/courier.dart
Normal file
44
lib/ui/courier.dart
Normal file
|
@ -0,0 +1,44 @@
|
|||
part of 'courierspage.dart';
|
||||
|
||||
class _Courier extends StatelessWidget {
|
||||
const _Courier({
|
||||
required this.courier,
|
||||
});
|
||||
|
||||
final Courier courier;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (final BuildContext context) => CourierDetails(courier),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
height: 48,
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 17,
|
||||
height: 17,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color.fromRGBO(
|
||||
133,
|
||||
133,
|
||||
200,
|
||||
100,
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
Flexible(
|
||||
child: Text(courier.title),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
88
lib/ui/courierspage.dart
Normal file
88
lib/ui/courierspage.dart
Normal file
|
@ -0,0 +1,88 @@
|
|||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/models/courier.dart';
|
||||
|
||||
part 'courier.dart';
|
||||
|
||||
class CouriersPage extends StatelessWidget {
|
||||
const CouriersPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
final Widget child = BlocBuilder<CouriersCubit, CouriersState>(
|
||||
builder: (final BuildContext context, final CouriersState state) {
|
||||
final List<Courier> couriers = state.couriers;
|
||||
|
||||
if (couriers.isEmpty) {
|
||||
if (state.isLoading) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
context.read<CouriersCubit>().refresh();
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
minHeight: 40,
|
||||
minWidth: double.infinity,
|
||||
),
|
||||
child: OutlinedButton(
|
||||
onPressed: () {
|
||||
context.read<CouriersCubit>().refresh();
|
||||
},
|
||||
child: Text(
|
||||
'Обновить',
|
||||
style: Theme.of(context).textTheme.button?.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
context.read<CouriersCubit>().refresh();
|
||||
},
|
||||
child: ListView.builder(
|
||||
itemCount: couriers.length,
|
||||
itemBuilder: (final BuildContext context, final int index) =>
|
||||
_Courier(
|
||||
courier: couriers[index],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(52),
|
||||
child: AppBar(
|
||||
title: const Padding(
|
||||
padding: EdgeInsets.only(top: 4.0),
|
||||
child: Text('Товары'),
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
),
|
||||
),
|
||||
body: child,
|
||||
);
|
||||
}
|
||||
}
|
44
lib/ui/item.dart
Normal file
44
lib/ui/item.dart
Normal file
|
@ -0,0 +1,44 @@
|
|||
part of 'itemspage.dart';
|
||||
|
||||
class _Item extends StatelessWidget {
|
||||
const _Item({
|
||||
required this.item,
|
||||
});
|
||||
|
||||
final Item item;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (final BuildContext context) => ItemDetails(item),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
height: 48,
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 17,
|
||||
height: 17,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color.fromRGBO(
|
||||
133,
|
||||
200,
|
||||
133,
|
||||
100,
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
Flexible(
|
||||
child: Text(item.title),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
87
lib/ui/itemspage.dart
Normal file
87
lib/ui/itemspage.dart
Normal file
|
@ -0,0 +1,87 @@
|
|||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/models/item.dart';
|
||||
|
||||
part 'item.dart';
|
||||
|
||||
class ItemsPage extends StatelessWidget {
|
||||
const ItemsPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
final Widget child = BlocBuilder<ItemsCubit, ItemsState>(
|
||||
builder: (final BuildContext context, final ItemsState state) {
|
||||
final List<Item> users = state.users;
|
||||
|
||||
if (users.isEmpty) {
|
||||
if (state.isLoading) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
context.read<ItemsCubit>().refresh();
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
minHeight: 40,
|
||||
minWidth: double.infinity,
|
||||
),
|
||||
child: OutlinedButton(
|
||||
onPressed: () {
|
||||
context.read<ItemsCubit>().refresh();
|
||||
},
|
||||
child: Text(
|
||||
'Обновить',
|
||||
style: Theme.of(context).textTheme.button?.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
context.read<ItemsCubit>().refresh();
|
||||
},
|
||||
child: ListView.builder(
|
||||
itemCount: users.length,
|
||||
itemBuilder: (final BuildContext context, final int index) => _Item(
|
||||
item: users[index],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(52),
|
||||
child: AppBar(
|
||||
title: const Padding(
|
||||
padding: EdgeInsets.only(top: 4.0),
|
||||
child: Text('Товары'),
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
),
|
||||
),
|
||||
body: child,
|
||||
);
|
||||
}
|
||||
}
|
44
lib/ui/order.dart
Normal file
44
lib/ui/order.dart
Normal file
|
@ -0,0 +1,44 @@
|
|||
part of 'orderspage.dart';
|
||||
|
||||
class _Order extends StatelessWidget {
|
||||
const _Order({
|
||||
required this.order,
|
||||
});
|
||||
|
||||
final Order order;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (final BuildContext context) => OrderDetails(order),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
height: 48,
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 17,
|
||||
height: 17,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color.fromRGBO(
|
||||
133,
|
||||
200,
|
||||
133,
|
||||
100,
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
Flexible(
|
||||
child: Text(order.title),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
88
lib/ui/orderspage.dart
Normal file
88
lib/ui/orderspage.dart
Normal file
|
@ -0,0 +1,88 @@
|
|||
import 'package:cubit_form/cubit_form.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/models/order.dart';
|
||||
|
||||
part 'order.dart';
|
||||
|
||||
class OrdersPage extends StatelessWidget {
|
||||
const OrdersPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
final Widget child = BlocBuilder<OrdersCubit, OrdersState>(
|
||||
builder: (final BuildContext context, final OrdersState state) {
|
||||
final List<Order> orders = state.orders;
|
||||
|
||||
if (orders.isEmpty) {
|
||||
if (state.isLoading) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
context.read<OrdersCubit>().refresh();
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
minHeight: 40,
|
||||
minWidth: double.infinity,
|
||||
),
|
||||
child: OutlinedButton(
|
||||
onPressed: () {
|
||||
context.read<OrdersCubit>().refresh();
|
||||
},
|
||||
child: Text(
|
||||
'Обновить',
|
||||
style: Theme.of(context).textTheme.button?.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
context.read<OrdersCubit>().refresh();
|
||||
},
|
||||
child: ListView.builder(
|
||||
itemCount: orders.length,
|
||||
itemBuilder: (final BuildContext context, final int index) =>
|
||||
_Order(
|
||||
order: orders[index],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(52),
|
||||
child: AppBar(
|
||||
title: const Padding(
|
||||
padding: EdgeInsets.only(top: 4.0),
|
||||
child: Text('Товары'),
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
),
|
||||
),
|
||||
body: child,
|
||||
);
|
||||
}
|
||||
}
|
67
lib/ui/root.dart
Normal file
67
lib/ui/root.dart
Normal file
|
@ -0,0 +1,67 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/logic/appsettingscubit.dart';
|
||||
import 'package:selfprivacy/ui/brandtabbar.dart';
|
||||
import 'package:selfprivacy/ui/courierspage.dart';
|
||||
import 'package:selfprivacy/ui/itemspage.dart';
|
||||
import 'package:selfprivacy/ui/orderspage.dart';
|
||||
|
||||
class RootPage extends StatefulWidget {
|
||||
const RootPage({super.key});
|
||||
|
||||
@override
|
||||
State<RootPage> createState() => _RootPageState();
|
||||
}
|
||||
|
||||
class _RootPageState extends State<RootPage> with TickerProviderStateMixin {
|
||||
late TabController tabController;
|
||||
|
||||
late final AnimationController _controller = AnimationController(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
tabController = TabController(length: 3, vsync: this);
|
||||
tabController.addListener(() {
|
||||
setState(() {
|
||||
tabController.index == 2
|
||||
? _controller.forward()
|
||||
: _controller.reverse();
|
||||
});
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
tabController.dispose();
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => SafeArea(
|
||||
child: Provider<ChangeTab>(
|
||||
create: (final _) => ChangeTab(tabController.animateTo),
|
||||
child: Scaffold(
|
||||
body: TabBarView(
|
||||
controller: tabController,
|
||||
children: const [
|
||||
ItemsPage(),
|
||||
CouriersPage(),
|
||||
OrdersPage(),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: BrandTabBar(
|
||||
controller: tabController,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class ChangeTab {
|
||||
ChangeTab(this.onPress);
|
||||
final ValueChanged<int> onPress;
|
||||
}
|
25
pubspec.yaml
25
pubspec.yaml
|
@ -64,28 +64,3 @@ flutter_icons:
|
|||
ios: true
|
||||
image_path_android: "assets/images/icon/logo_android.png"
|
||||
image_path_ios: "assets/images/icon/logo_ios.png"
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- assets/images/
|
||||
- assets/images/onboarding/
|
||||
- assets/images/logos/
|
||||
- assets/images/gifs/
|
||||
- assets/translations/
|
||||
- assets/markdown/
|
||||
fonts:
|
||||
- family: BrandIcons
|
||||
fonts:
|
||||
- asset: assets/fonts/BrandIcons.ttf
|
||||
- family: Inter
|
||||
fonts:
|
||||
- asset: assets/fonts/Inter-Regular.ttf
|
||||
- asset: assets/fonts/Inter-Medium.ttf
|
||||
weight: 500
|
||||
- asset: assets/fonts/Inter-SemiBold.ttf
|
||||
weight: 600
|
||||
- asset: assets/fonts/Inter-Bold.ttf
|
||||
weight: 700
|
||||
- asset: assets/fonts/Inter-ExtraBold.ttf
|
||||
weight: 800
|
||||
|
|
Loading…
Reference in a new issue