update provider and bottom modal sheet

fdroid
Kherel 2020-12-10 21:33:19 +01:00
parent 600df97abf
commit 91aa2b860b
25 changed files with 881 additions and 191 deletions

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart'; import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
@ -14,19 +15,14 @@ class BlocAndProviderConfig extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
var platformBrightness = var platformBrightness =
SchedulerBinding.instance.window.platformBrightness; SchedulerBinding.instance.window.platformBrightness;
var isDark = platformBrightness == Brightness.dark;
// var platformBrightness = Brightness.dark; // var platformBrightness = Brightness.dark;
return MultiProvider( return MultiProvider(
providers: [ providers: [
BlocProvider<AppSettingsCubit>( BlocProvider(create: (_) => AppSettingsCubit(isDarkModeOn: isDark)),
create: (BuildContext context) => AppSettingsCubit( BlocProvider(create: (_) => ServicesCubit()),
isDarkModeOn: platformBrightness == Brightness.dark), BlocProvider(create: (_) => ProvidersCubit()),
), BlocProvider(create: (_) => UsersCubit()),
BlocProvider<ServicesCubit>(
create: (BuildContext context) => ServicesCubit(),
),
BlocProvider<UsersCubit>(
create: (BuildContext context) => UsersCubit(),
),
], ],
child: child, child: child,
); );

View File

@ -30,16 +30,6 @@ class BrandColors {
/// ![](https://www.colorhexa.com/0F8849.png) /// ![](https://www.colorhexa.com/0F8849.png)
static const Color green2 = Color(0xFF0F8849); static const Color green2 = Color(0xFF0F8849);
static const primary = blue;
static const headlineColor = black;
static const inactive = gray2;
static const scaffoldBackground = gray3;
static const inputInactive = gray4;
static const textColor1 = black;
static const textColor2 = gray1;
static const dividerColor = gray5;
static const warning = red;
static get navBackgroundLight => white.withOpacity(0.8); static get navBackgroundLight => white.withOpacity(0.8);
static get navBackgroundDark => black.withOpacity(0.8); static get navBackgroundDark => black.withOpacity(0.8);
@ -56,4 +46,18 @@ class BrandColors {
Color(0xFFEF4E09), Color(0xFFEF4E09),
Color(0xFFEFD135), Color(0xFFEFD135),
]; ];
static const primary = blue;
static const headlineColor = black;
static const inactive = gray2;
static const scaffoldBackground = gray3;
static const inputInactive = gray4;
static const textColor1 = black;
static const textColor2 = gray1;
static const dividerColor = gray5;
static const warning = red;
} }

View File

@ -19,6 +19,26 @@ final ligtTheme = ThemeData(
borderRadius: BorderRadius.all(Radius.circular(4)), borderRadius: BorderRadius.all(Radius.circular(4)),
borderSide: BorderSide(color: BrandColors.blue), borderSide: BorderSide(color: BrandColors.blue),
), ),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(4)),
borderSide: BorderSide(
width: 1,
color: BrandColors.red,
),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(4)),
borderSide: BorderSide(
width: 1,
color: BrandColors.red,
),
),
errorStyle: GoogleFonts.inter(
textStyle: TextStyle(
fontSize: 12,
color: BrandColors.red,
),
),
), ),
textTheme: GoogleFonts.interTextTheme( textTheme: GoogleFonts.interTextTheme(
TextTheme( TextTheme(

View File

@ -0,0 +1,27 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:selfprivacy/logic/models/provider.dart';
import 'package:selfprivacy/logic/models/state_types.dart';
export 'package:selfprivacy/logic/models/state_types.dart';
export 'package:provider/provider.dart';
part 'providers_state.dart';
class ProvidersCubit extends Cubit<ProvidersState> {
ProvidersCubit() : super(ProvidersState(all));
void connect(ProviderModel provider) {
var newState = state.updateElement(provider, StateType.stable);
emit(newState);
}
}
final all = ProviderType.values
.map(
(type) => ProviderModel(
state: StateType.uninitialized,
type: type,
),
)
.toList();

View File

@ -0,0 +1,25 @@
part of 'providers_cubit.dart';
class ProvidersState extends Equatable {
const ProvidersState(this.all);
final List<ProviderModel> all;
ProvidersState updateElement(ProviderModel provider, StateType newState) {
var newList = [...all];
var index = newList.indexOf(provider);
newList[index] = provider.updateState(newState);
return ProvidersState(newList);
}
List<ProviderModel> get connected =>
all.where((service) => service.state != StateType.uninitialized).toList();
List<ProviderModel> get uninitialized =>
all.where((service) => service.state == StateType.uninitialized).toList();
bool get isFullyInitialized => uninitialized.isEmpty;
@override
List<Object> get props => all;
}

View File

@ -1,7 +1,11 @@
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/logic/models/service.dart';
import 'package:selfprivacy/logic/models/state_types.dart';
export 'package:provider/provider.dart'; export 'package:provider/provider.dart';
export 'package:selfprivacy/logic/models/state_types.dart';
part 'services_state.dart'; part 'services_state.dart';
@ -9,7 +13,7 @@ class ServicesCubit extends Cubit<ServicesState> {
ServicesCubit() : super(ServicesState(all)); ServicesCubit() : super(ServicesState(all));
void connect(Service service) { void connect(Service service) {
var newState = state.updateElement(service, ServiceStateType.stable); var newState = state.updateElement(service, StateType.stable);
emit(newState); emit(newState);
} }
} }
@ -17,7 +21,7 @@ class ServicesCubit extends Cubit<ServicesState> {
final all = ServiceTypes.values final all = ServiceTypes.values
.map( .map(
(type) => Service( (type) => Service(
state: ServiceStateType.uninitialized, state: StateType.uninitialized,
type: type, type: type,
), ),
) )

View File

@ -1,12 +1,12 @@
part of 'services_cubit.dart'; part of 'services_cubit.dart';
@immutable @immutable
class ServicesState { class ServicesState extends Equatable{
ServicesState(this.all); ServicesState(this.all);
final List<Service> all; final List<Service> all;
ServicesState updateElement(Service service, ServiceStateType newState) { ServicesState updateElement(Service service, StateType newState) {
var newList = [...all]; var newList = [...all];
var index = newList.indexOf(service); var index = newList.indexOf(service);
newList[index] = service.updateState(newState); newList[index] = service.updateState(newState);
@ -14,10 +14,13 @@ class ServicesState {
} }
List<Service> get connected => all List<Service> get connected => all
.where((service) => service.state != ServiceStateType.uninitialized) .where((service) => service.state != StateType.uninitialized)
.toList(); .toList();
List<Service> get uninitialized => all List<Service> get uninitialized => all
.where((service) => service.state == ServiceStateType.uninitialized) .where((service) => service.state == StateType.uninitialized)
.toList(); .toList();
@override
List<Object> get props => all;
} }

View File

@ -1,9 +1,9 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/logic/models/state_types.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
enum ProviderTypes { enum ProviderType {
server, server,
domain, domain,
backup, backup,
@ -12,10 +12,10 @@ enum ProviderTypes {
class ProviderModel extends Equatable { class ProviderModel extends Equatable {
const ProviderModel({this.state, this.type}); const ProviderModel({this.state, this.type});
final ServiceStateType state; final StateType state;
final ProviderTypes type; final ProviderType type;
ProviderModel updateState(ServiceStateType newState) => ProviderModel( ProviderModel updateState(StateType newState) => ProviderModel(
state: newState, state: newState,
type: type, type: type,
); );
@ -25,14 +25,14 @@ class ProviderModel extends Equatable {
IconData get icon { IconData get icon {
switch (type) { switch (type) {
case ProviderTypes.server: case ProviderType.server:
return BrandIcons.server; return BrandIcons.server;
case ProviderTypes.domain: case ProviderType.domain:
return BrandIcons.globe; return BrandIcons.globe;
break; break;
case ProviderTypes.backup: case ProviderType.backup:
return BrandIcons.save; return BrandIcons.save;
} }
return null; return null;

View File

@ -1,6 +1,6 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:selfprivacy/logic/models/state_types.dart';
enum ServiceStateType { uninitialized, stable, warning }
enum ServiceTypes { enum ServiceTypes {
messanger, messanger,
mail, mail,
@ -12,10 +12,10 @@ enum ServiceTypes {
class Service extends Equatable { class Service extends Equatable {
const Service({this.state, this.type}); const Service({this.state, this.type});
final ServiceStateType state; final StateType state;
final ServiceTypes type; final ServiceTypes type;
Service updateState(ServiceStateType newState) => Service( Service updateState(StateType newState) => Service(
state: newState, state: newState,
type: type, type: type,
); );

View File

@ -0,0 +1 @@
enum StateType { uninitialized, stable, warning }

View File

@ -1,5 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
var navigatorKey = GlobalKey<NavigatorState>();
class BrandModalSheet extends StatelessWidget { class BrandModalSheet extends StatelessWidget {
const BrandModalSheet({ const BrandModalSheet({
Key key, Key key,
@ -10,7 +12,7 @@ class BrandModalSheet extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DraggableScrollableSheet( return DraggableScrollableSheet(
minChildSize: 0.5, minChildSize: 0.95,
initialChildSize: 1, initialChildSize: 1,
maxChildSize: 1, maxChildSize: 1,
builder: (context, scrollController) { builder: (context, scrollController) {
@ -20,20 +22,30 @@ class BrandModalSheet extends StatelessWidget {
child: Container( child: Container(
child: Column( child: Column(
children: [ children: [
Padding( GestureDetector(
padding: EdgeInsets.only(top: 32, bottom: 6), onTap: () => Navigator.of(context).pop(),
behavior: HitTestBehavior.opaque,
child: Container( child: Container(
height: 4, width: double.infinity,
width: 30, child: Center(
decoration: BoxDecoration( child: Padding(
borderRadius: BorderRadius.circular(2), padding: EdgeInsets.only(top: 132, bottom: 6),
color: Color(0xFFE3E3E3).withOpacity(0.65), child: Container(
height: 4,
width: 30,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(2),
color: Color(0xFFE3E3E3).withOpacity(0.65),
),
),
),
), ),
), ),
), ),
Container( Container(
constraints: BoxConstraints( constraints: BoxConstraints(
minHeight: MediaQuery.of(context).size.height - 32 - 4, minHeight: MediaQuery.of(context).size.height - 32 - 4,
maxHeight: MediaQuery.of(context).size.height,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: borderRadius:
@ -41,7 +53,7 @@ class BrandModalSheet extends StatelessWidget {
color: Theme.of(context).scaffoldBackgroundColor, color: Theme.of(context).scaffoldBackgroundColor,
), ),
width: double.infinity, width: double.infinity,
child: child, child: child
), ),
], ],
), ),

View File

@ -17,16 +17,24 @@ class _BrandTabBarState extends State<BrandTabBar> {
@override @override
void initState() { void initState() {
currentIndex = widget.controller.index; currentIndex = widget.controller.index;
widget.controller.addListener(() { widget.controller.addListener(_listener);
if (currentIndex != widget.controller.index) {
setState(() {
currentIndex = widget.controller.index;
});
}
});
super.initState(); super.initState();
} }
_listener() {
if (currentIndex != widget.controller.index) {
setState(() {
currentIndex = widget.controller.index;
});
}
}
@override
void dispose() {
widget.controller ?? widget.controller.removeListener(_listener);
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final paddingBottom = MediaQuery.of(context).padding.bottom; final paddingBottom = MediaQuery.of(context).padding.bottom;

View File

@ -1,24 +1,24 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.dart'; import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/logic/models/state_types.dart';
class IconStatusMaks extends StatelessWidget { class IconStatusMaks extends StatelessWidget {
IconStatusMaks({this.child, this.status}); IconStatusMaks({this.child, this.status});
final Icon child; final Icon child;
final ServiceStateType status; final StateType status;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
List<Color> colors; List<Color> colors;
switch (status) { switch (status) {
case ServiceStateType.uninitialized: case StateType.uninitialized:
colors = BrandColors.uninitializedGradientColors; colors = BrandColors.uninitializedGradientColors;
break; break;
case ServiceStateType.stable: case StateType.stable:
colors = BrandColors.stableGradientColors; colors = BrandColors.stableGradientColors;
break; break;
case ServiceStateType.warning: case StateType.warning:
colors = BrandColors.warningGradientColors; colors = BrandColors.warningGradientColors;
break; break;
} }

View File

@ -0,0 +1,361 @@
import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/config/text_themes.dart';
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
import 'package:selfprivacy/logic/models/provider.dart';
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
import 'package:selfprivacy/ui/components/brand_card/brand_card.dart';
import 'package:selfprivacy/ui/components/brand_modal_sheet/brand_modal_sheet.dart';
import 'package:selfprivacy/ui/components/brand_span_button/brand_span_button.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
class InitializingPage extends StatelessWidget {
const InitializingPage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
var cubit = context.watch<ProvidersCubit>();
var connected = cubit.state.connected;
var uninitialized = cubit.state.uninitialized;
return Scaffold(
body: ListView(
padding: brandPagePadding1,
children: [
BrandText.h4('Начало'),
BrandText.h1('SelfPrivacy'),
SizedBox(
height: 10,
),
RichText(
text: TextSpan(
children: [
TextSpan(
text:
'Для устойчивости и приватности требует много учёток. Полная инструкция на ',
style: body2Style,
),
BrandSpanButton.link(
text: 'selfprivacy.org/start',
urlString: 'https://selfprivacy.org/start',
),
],
),
),
SizedBox(height: 50),
...connected.map((p) => getCard(context, p)).toList(),
...uninitialized.map((p) => getCard(context, p)).toList(),
],
),
);
}
void _showModal(BuildContext context, Widget widget) {
showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (BuildContext context) {
return widget;
},
);
}
Widget getCard(BuildContext context, ProviderModel model) {
var cubit = context.watch<ProvidersCubit>();
if (model.state == StateType.stable) {
return _MockSuccess(type: model.type);
}
switch (model.type) {
case ProviderType.server:
return BrandCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.asset('assets/images/logos/hetzner.png'),
SizedBox(height: 10),
BrandText.h2('1. Подключите сервер Hetzner'),
SizedBox(height: 10),
BrandText.body2(
'Здесь будут жить наши данные и SelfPrivacy-сервисы'),
_MockForm(
hintText: 'Hetzner API Token',
length: 2,
onPressed: () {
var provider = cubit.state.all
.firstWhere((p) => p.type == ProviderType.server);
cubit.connect(provider);
},
),
SizedBox(height: 20),
BrandButton.text(
onPressed: () => _showModal(context, _HowHetzner()),
title: 'Как получить API Token',
),
],
),
);
break;
case ProviderType.domain:
return BrandCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.asset('assets/images/logos/namecheap.png'),
SizedBox(height: 10),
BrandText.h2('2. Настройте домен'),
SizedBox(height: 10),
RichText(
text: TextSpan(
children: [
TextSpan(
text: 'Зарегистрируйте домен в ',
style: body2Style,
),
BrandSpanButton.link(
text: 'NameCheap',
urlString: 'https://www.namecheap.com',
),
TextSpan(
text:
' или у любого другого регистратора. После этого настройте его на DNS-сервер CloudFlare',
style: body2Style,
),
],
),
),
_MockForm(
hintText: 'Домен, например, selfprivacy.org',
submitButtonText: 'Проверить DNS',
length: 2,
onPressed: () {},
),
SizedBox(height: 20),
BrandButton.text(
onPressed: () {},
title: 'Как настроить DNS CloudFlare',
),
SizedBox(height: 10),
Image.asset('assets/images/logos/cloudflare.png'),
SizedBox(height: 10),
BrandText.h2('3. Подключите CloudFlare DNS'),
SizedBox(height: 10),
BrandText.body2('Для управления DNS вашего домена'),
_MockForm(
hintText: 'CloudFlare API Token',
length: 2,
onPressed: () {
var provider = cubit.state.all
.firstWhere((p) => p.type == ProviderType.domain);
cubit.connect(provider);
},
),
SizedBox(height: 20),
BrandButton.text(
onPressed: () {},
title: 'Как получить API Token',
),
],
),
);
break;
case ProviderType.backup:
return BrandCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.asset('assets/images/logos/aws.png'),
SizedBox(height: 10),
BrandText.h2('4. Подключите Amazon AWS для бекапа'),
SizedBox(height: 10),
BrandText.body2(
'IaaS-провайдер, для бесплатного хранения резервных копии ваших данных в зашифрованном виде'),
_MockForm(
hintText: 'Amazon AWS Access Key',
length: 2,
onPressed: () {
var provider = cubit.state.all
.firstWhere((p) => p.type == ProviderType.backup);
cubit.connect(provider);
},
),
SizedBox(height: 20),
BrandButton.text(
onPressed: () {},
title: 'Как получить API Token',
),
],
),
);
}
return null;
}
}
class _HowHetzner extends StatelessWidget {
const _HowHetzner({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return BrandModalSheet(
child: Padding(
padding: brandPagePadding2,
child: Column(
children: [
SizedBox(height: 40),
BrandText.h2('Как получить Hetzner API Token'),
SizedBox(height: 20),
RichText(
text: TextSpan(
children: [
TextSpan(
text: '1 Переходим по ссылке ',
style: body1Style,
),
BrandSpanButton.link(
text: 'hetzner.com/sdfsdfsdfsdf',
urlString: 'https://hetzner.com/sdfsdfsdfsdf',
),
TextSpan(
text: '''
2 Заходим в созданный нами проект. Если такового - нет, значит создаём.
3 Наводим мышкой на боковую панель. Она должна раскрыться, показав нам пункты меню. Нас интересует последний Security (с иконкой ключика).
4 Далее, в верхней части интерфейса видим примерно такой список: SSH Keys, API Tokens, Certificates, Members. Нам нужен API Tokens. Переходим по нему.
5 В правой части интерфейса, нас будет ожидать кнопка Generate API token. Если же вы используете мобильную версию сайта, в нижнем правом углу вы увидите красный плюсик. Нажимаем на эту кнопку.
6 В поле Description, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет.
''',
style: body1Style,
),
],
),
),
],
),
),
);
}
}
class _MockSuccess extends StatelessWidget {
const _MockSuccess({Key key, this.type}) : super(key: key);
final ProviderType type;
@override
Widget build(BuildContext context) {
String text;
switch (type) {
case ProviderType.server:
text = '1. Cервер подключен';
break;
case ProviderType.domain:
text = '2. Домен настроен';
break;
case ProviderType.backup:
text = '3. Резервное копирование настроенно';
break;
}
return BrandCard(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
BrandText.h3(text),
Icon(
Icons.check,
color: BrandColors.green1,
),
],
),
);
}
}
class _MockForm extends StatefulWidget {
const _MockForm({
Key key,
@required this.hintText,
this.submitButtonText = 'Подключить',
@required this.onPressed,
@required this.length,
}) : super(key: key);
final String hintText;
final String submitButtonText;
final int length;
final VoidCallback onPressed;
@override
__MockFormState createState() => __MockFormState();
}
class __MockFormState extends State<_MockForm> {
String text = '';
bool _valid = true;
bool _touched = false;
onPressed() {
if (text.length == widget.length) {
setState(() {
_touched = true;
_valid = true;
widget.onPressed();
});
} else {
setState(() {
_touched = true;
_valid = false;
});
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
SizedBox(height: 20),
TextField(
onChanged: (value) {
if (_touched) {
if (value.length == widget.length) {
setState(() {
_valid = true;
text = value;
});
} else {
setState(() {
_valid = false;
text = value;
});
}
} else {
setState(() {
text = value;
});
}
},
decoration: InputDecoration(
hintText: widget.hintText,
errorText:
_valid ? null : 'Длинна должна быть ${widget.length} символа',
),
),
SizedBox(height: 20),
BrandButton.rised(
onPressed: _valid ? onPressed : null,
title: widget.submitButtonText,
),
],
);
}
}

View File

@ -10,14 +10,14 @@
// import 'package:selfprivacy/ui/pages/rootRoute.dart'; // import 'package:selfprivacy/ui/pages/rootRoute.dart';
// import 'package:selfprivacy/utils/route_transitions/basic.dart'; // import 'package:selfprivacy/utils/route_transitions/basic.dart';
// class OnboardingPage extends StatefulWidget { // class InitializingPage extends StatefulWidget {
// const OnboardingPage({Key key}) : super(key: key); // const InitializingPage({Key key}) : super(key: key);
// @override // @override
// _OnboardingPageState createState() => _OnboardingPageState(); // _InitializingPageState createState() => _InitializingPageState();
// } // }
// class _OnboardingPageState extends State<OnboardingPage> { // class _InitializingPageState extends State<InitializingPage> {
// PageController controller; // PageController controller;
// var currentPage = 0; // var currentPage = 0;
@ -113,9 +113,10 @@
// children: [ // children: [
// Image.asset('assets/images/logos/hetzner.png'), // Image.asset('assets/images/logos/hetzner.png'),
// SizedBox(height: 10), // SizedBox(height: 10),
// Text('1. Подключите сервер Hetzner').h2, // BrandText.h2('1. Подключите сервер Hetzner'),
// SizedBox(height: 10), // SizedBox(height: 10),
// Text('Здесь будут жить наши данные и SelfPrivacy-сервисы').body2, // BrandText.body2(
// 'Здесь будут жить наши данные и SelfPrivacy-сервисы'),
// _MockForm( // _MockForm(
// onPressed: _nextPage, // onPressed: _nextPage,
// hintText: 'Hetzner API Token', // hintText: 'Hetzner API Token',
@ -137,7 +138,7 @@
// children: [ // children: [
// Image.asset('assets/images/logos/namecheap.png'), // Image.asset('assets/images/logos/namecheap.png'),
// SizedBox(height: 10), // SizedBox(height: 10),
// Text('2. Настройте домен ').h2, // BrandText.h2('2. Настройте домен'),
// SizedBox(height: 10), // SizedBox(height: 10),
// RichText( // RichText(
// text: TextSpan( // text: TextSpan(
@ -179,9 +180,9 @@
// children: [ // children: [
// Image.asset('assets/images/logos/cloudflare.png'), // Image.asset('assets/images/logos/cloudflare.png'),
// SizedBox(height: 10), // SizedBox(height: 10),
// Text('3. Подключите CloudFlare DNS').h2, // BrandText.h2('3. Подключите CloudFlare DNS'),
// SizedBox(height: 10), // SizedBox(height: 10),
// Text('Для управления DNS вашего домена').body2, // BrandText.body2('Для управления DNS вашего домена'),
// _MockForm( // _MockForm(
// onPressed: _nextPage, // onPressed: _nextPage,
// hintText: 'CloudFlare API Token', // hintText: 'CloudFlare API Token',
@ -202,10 +203,10 @@
// children: [ // children: [
// Image.asset('assets/images/logos/aws.png'), // Image.asset('assets/images/logos/aws.png'),
// SizedBox(height: 10), // SizedBox(height: 10),
// Text('4. Подключите Amazon AWS для бекапа').h2, // BrandText.h2('4. Подключите Amazon AWS для бекапа'),
// SizedBox(height: 10), // SizedBox(height: 10),
// Text('IaaS-провайдер, для бесплатного хранения резервных копии ваших данных в зашифрованном виде') // BrandText.body2(
// .body2, // 'IaaS-провайдер, для бесплатного хранения резервных копии ваших данных в зашифрованном виде'),
// _MockForm( // _MockForm(
// onPressed: () { // onPressed: () {
// Navigator.of(context) // Navigator.of(context)
@ -254,7 +255,7 @@
// child: Column( // child: Column(
// children: [ // children: [
// SizedBox(height: 40), // SizedBox(height: 40),
// Text('Как получить Hetzner API Token').h2, // BrandText.h2('Как получить Hetzner API Token'),
// SizedBox(height: 20), // SizedBox(height: 20),
// RichText( // RichText(
// text: TextSpan( // text: TextSpan(

View File

@ -1,7 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
import 'package:selfprivacy/logic/models/provider.dart'; import 'package:selfprivacy/logic/models/provider.dart';
import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/logic/models/state_types.dart';
import 'package:selfprivacy/ui/components/brand_card/brand_card.dart'; import 'package:selfprivacy/ui/components/brand_card/brand_card.dart';
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
import 'package:selfprivacy/ui/components/brand_modal_sheet/brand_modal_sheet.dart'; import 'package:selfprivacy/ui/components/brand_modal_sheet/brand_modal_sheet.dart';
@ -20,10 +21,9 @@ class ProvidersPage extends StatefulWidget {
class _ProvidersPageState extends State<ProvidersPage> { class _ProvidersPageState extends State<ProvidersPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cards = ProviderTypes.values final cards = ProviderType.values
.map((type) => _Card( .map((type) =>
provider: _Card(provider: ProviderModel(state: StateType.stable, type: type)))
ProviderModel(state: ServiceStateType.stable, type: type)))
.toList(); .toList();
return Scaffold( return Scaffold(
appBar: PreferredSize( appBar: PreferredSize(
@ -49,16 +49,16 @@ class _Card extends StatelessWidget {
String stableText; String stableText;
switch (provider.type) { switch (provider.type) {
case ProviderTypes.server: case ProviderType.server:
title = 'Сервер'; title = 'Сервер';
stableText = 'В норме'; stableText = 'В норме';
break; break;
case ProviderTypes.domain: case ProviderType.domain:
title = 'Домен'; title = 'Домен';
message = 'example.com'; message = 'example.com';
stableText = 'Домен настроен'; stableText = 'Домен настроен';
break; break;
case ProviderTypes.backup: case ProviderType.backup:
message = '22 янв 2021 14:30'; message = '22 янв 2021 14:30';
title = 'Резервное копирование'; title = 'Резервное копирование';
stableText = 'В норме'; stableText = 'В норме';
@ -91,8 +91,7 @@ class _Card extends StatelessWidget {
BrandText.body2(message), BrandText.body2(message),
SizedBox(height: 10), SizedBox(height: 10),
], ],
if (provider.state == ServiceStateType.stable) if (provider.state == StateType.stable) BrandText.body2(stableText),
BrandText.body2(stableText),
], ],
), ),
), ),
@ -115,75 +114,84 @@ class _ProviderDetails extends StatelessWidget {
String title; String title;
switch (provider.type) { switch (provider.type) {
case ProviderTypes.server: case ProviderType.server:
title = 'Сервер'; title = 'Сервер';
break; break;
case ProviderTypes.domain: case ProviderType.domain:
title = 'Домен'; title = 'Домен';
break; break;
case ProviderTypes.backup: case ProviderType.backup:
title = 'Резервное копирование'; title = 'Резервное копирование';
break; break;
} }
return BrandModalSheet( return BrandModalSheet(
child: Column( child: Navigator(
crossAxisAlignment: CrossAxisAlignment.start, key: navigatorKey,
children: [ initialRoute: '/',
Align( onGenerateRoute: (_) {
alignment: Alignment.centerRight, return materialRoute(
child: Padding( Column(
padding: EdgeInsets.symmetric(
vertical: 4,
horizontal: 2,
),
child: PopupMenuButton<_PopupMenuItemType>(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
onSelected: (_PopupMenuItemType result) {
switch (result) {
case _PopupMenuItemType.setting:
Navigator.of(context)
.pushReplacement(materialRoute(SettingsPage()));
break;
}
},
icon: Icon(Icons.more_vert),
itemBuilder: (BuildContext context) => [
PopupMenuItem<_PopupMenuItemType>(
value: _PopupMenuItemType.setting,
child: Container(
padding: EdgeInsets.only(left: 5),
child: Text('Настройки'),
),
),
],
),
),
),
Padding(
padding: brandPagePadding1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox(height: 13), Align(
IconStatusMaks( alignment: Alignment.centerRight,
status: provider.state, child: Padding(
child: Icon(provider.icon, size: 40, color: Colors.white), padding: EdgeInsets.symmetric(
vertical: 4,
horizontal: 2,
),
child: PopupMenuButton<_PopupMenuItemType>(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
onSelected: (_PopupMenuItemType result) {
switch (result) {
case _PopupMenuItemType.setting:
navigatorKey.currentState
.push(materialRoute(SettingsPage()));
break;
}
},
icon: Icon(Icons.more_vert),
itemBuilder: (BuildContext context) => [
PopupMenuItem<_PopupMenuItemType>(
value: _PopupMenuItemType.setting,
child: Container(
padding: EdgeInsets.only(left: 5),
child: Text('Настройки'),
),
),
],
),
),
), ),
SizedBox(height: 10), Padding(
BrandText.h1(title), padding: brandPagePadding1,
SizedBox(height: 10), child: Column(
BrandText.body1(statusText), crossAxisAlignment: CrossAxisAlignment.start,
SizedBox( children: [
height: 20, SizedBox(height: 13),
), IconStatusMaks(
Text('Статусы сервера и сервис провайдера и т.д.') status: provider.state,
child:
Icon(provider.icon, size: 40, color: Colors.white),
),
SizedBox(height: 10),
BrandText.h1(title),
SizedBox(height: 10),
BrandText.body1(statusText),
SizedBox(
height: 20,
),
Text('Статусы сервера и сервис провайдера и т.д.')
],
),
)
], ],
), ),
) );
], },
), ),
); );
} }

View File

@ -11,57 +11,51 @@ class SettingsPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SafeArea( return ListView(
child: Scaffold( padding: brandPagePadding2,
appBar: PreferredSize( children: [
child: BrandHeader(title: 'Настройки', hasBackButton: true), SizedBox(height: 10),
preferredSize: Size.fromHeight(52), BrandHeader(title: 'Настройки', hasBackButton: true),
BrandDivider(),
SwitcherBlock(
onChange: (_) {},
child: _TextColumn(
title: 'Allow Auto-upgrade',
value: 'Wether to allow automatic packages upgrades',
),
isActive: true,
), ),
body: ListView( SwitcherBlock(
padding: brandPagePadding2, onChange: (_) {},
children: [ child: _TextColumn(
BrandDivider(), title: 'Reboot after upgrade',
SwitcherBlock( value: 'Reboot without prompt after applying updates',
onChange: (_) {}, ),
child: _TextColumn( isActive: false,
title: 'Allow Auto-upgrade',
value: 'Wether to allow automatic packages upgrades',
),
isActive: true,
),
SwitcherBlock(
onChange: (_) {},
child: _TextColumn(
title: 'Reboot after upgrade',
value: 'Reboot without prompt after applying updates',
),
isActive: false,
),
_Button(
onTap: () {},
child: _TextColumn(
title: 'Server Timezone',
value: 'Europe/Kyiv',
),
),
_Button(
onTap: () {},
child: _TextColumn(
title: 'Server Locale',
value: 'Default',
),
),
_Button(
onTap: () {},
child: _TextColumn(
hasWarning: true,
title: 'Factory Reset',
value: 'Restore default settings on your server',
),
)
],
), ),
), _Button(
onTap: () {},
child: _TextColumn(
title: 'Server Timezone',
value: 'Europe/Kyiv',
),
),
_Button(
onTap: () {},
child: _TextColumn(
title: 'Server Locale',
value: 'Default',
),
),
_Button(
onTap: () {},
child: _TextColumn(
hasWarning: true,
title: 'Factory Reset',
value: 'Restore default settings on your server',
),
)
],
); );
} }
} }

View File

@ -1,11 +1,16 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
import 'package:selfprivacy/ui/components/brand_tab_bar/brand_tab_bar.dart'; import 'package:selfprivacy/ui/components/brand_tab_bar/brand_tab_bar.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:selfprivacy/ui/pages/more/more.dart'; import 'package:selfprivacy/ui/pages/more/more.dart';
import 'package:selfprivacy/ui/pages/providers/providers.dart'; import 'package:selfprivacy/ui/pages/providers/providers.dart';
import 'package:selfprivacy/ui/pages/services/services.dart'; import 'package:selfprivacy/ui/pages/services/services.dart';
import 'package:selfprivacy/ui/pages/users/users.dart'; import 'package:selfprivacy/ui/pages/users/users.dart';
import 'initializing/initializing.dart';
class RootPage extends StatefulWidget { class RootPage extends StatefulWidget {
const RootPage({Key key}) : super(key: key); const RootPage({Key key}) : super(key: key);
@ -25,20 +30,23 @@ class _RootPageState extends State<RootPage>
@override @override
void dispose() { void dispose() {
tabController.dispose();
super.dispose(); super.dispose();
tabController.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var isReady =
context.select((ProvidersCubit bloc) => bloc.state.isFullyInitialized);
return SafeArea( return SafeArea(
child: Scaffold( child: Scaffold(
body: TabBarView( body: TabBarView(
controller: tabController, controller: tabController,
children: [ children: [
ProvidersPage(), isReady ? ProvidersPage() : InitializingPage(),
ServicesPage(), isReady ? ServicesPage() : _NotReady(),
UsersPage(), isReady ? UsersPage() : _NotReady(),
MorePage(), MorePage(),
], ],
), ),
@ -49,3 +57,20 @@ class _RootPageState extends State<RootPage>
); );
} }
} }
class _NotReady extends StatelessWidget {
const _NotReady({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BrandText.h3('Not ready'),
BrandText.body2('Finish providers initialization first'),
],
),
);
}
}

View File

@ -93,7 +93,7 @@ class _Card extends StatelessWidget {
SizedBox(height: 10), SizedBox(height: 10),
BrandText.h2(title), BrandText.h2(title),
SizedBox(height: 10), SizedBox(height: 10),
if (service.state == ServiceStateType.uninitialized) ...[ if (service.state == StateType.uninitialized) ...[
BrandText.body1(description), BrandText.body1(description),
SizedBox(height: 10), SizedBox(height: 10),
BrandButton.text( BrandButton.text(
@ -102,7 +102,7 @@ class _Card extends StatelessWidget {
context.read<ServicesCubit>().connect(service); context.read<ServicesCubit>().connect(service);
}) })
], ],
if (service.state == ServiceStateType.stable) if (service.state == StateType.stable)
BrandText.body2('Подключен'), BrandText.body2('Подключен'),
], ],
), ),