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
|
ios: true
|
||||||
image_path_android: "assets/images/icon/logo_android.png"
|
image_path_android: "assets/images/icon/logo_android.png"
|
||||||
image_path_ios: "assets/images/icon/logo_ios.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