forked from SelfPrivacy/selfprivacy.org.app
update provider and bottom modal sheet
parent
600df97abf
commit
91aa2b860b
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.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/users/users_cubit.dart';
|
||||
|
||||
|
@ -14,19 +15,14 @@ class BlocAndProviderConfig extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
var platformBrightness =
|
||||
SchedulerBinding.instance.window.platformBrightness;
|
||||
var isDark = platformBrightness == Brightness.dark;
|
||||
// var platformBrightness = Brightness.dark;
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
BlocProvider<AppSettingsCubit>(
|
||||
create: (BuildContext context) => AppSettingsCubit(
|
||||
isDarkModeOn: platformBrightness == Brightness.dark),
|
||||
),
|
||||
BlocProvider<ServicesCubit>(
|
||||
create: (BuildContext context) => ServicesCubit(),
|
||||
),
|
||||
BlocProvider<UsersCubit>(
|
||||
create: (BuildContext context) => UsersCubit(),
|
||||
),
|
||||
BlocProvider(create: (_) => AppSettingsCubit(isDarkModeOn: isDark)),
|
||||
BlocProvider(create: (_) => ServicesCubit()),
|
||||
BlocProvider(create: (_) => ProvidersCubit()),
|
||||
BlocProvider(create: (_) => UsersCubit()),
|
||||
],
|
||||
child: child,
|
||||
);
|
||||
|
|
|
@ -30,16 +30,6 @@ class BrandColors {
|
|||
/// ![](https://www.colorhexa.com/0F8849.png)
|
||||
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 navBackgroundDark => black.withOpacity(0.8);
|
||||
|
@ -56,4 +46,18 @@ class BrandColors {
|
|||
Color(0xFFEF4E09),
|
||||
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;
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -19,6 +19,26 @@ final ligtTheme = ThemeData(
|
|||
borderRadius: BorderRadius.all(Radius.circular(4)),
|
||||
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(
|
||||
|
|
|
@ -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();
|
|
@ -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;
|
||||
}
|
|
@ -1,7 +1,11 @@
|
|||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:selfprivacy/logic/models/service.dart';
|
||||
import 'package:selfprivacy/logic/models/state_types.dart';
|
||||
|
||||
export 'package:provider/provider.dart';
|
||||
export 'package:selfprivacy/logic/models/state_types.dart';
|
||||
|
||||
part 'services_state.dart';
|
||||
|
||||
|
@ -9,7 +13,7 @@ class ServicesCubit extends Cubit<ServicesState> {
|
|||
ServicesCubit() : super(ServicesState(all));
|
||||
|
||||
void connect(Service service) {
|
||||
var newState = state.updateElement(service, ServiceStateType.stable);
|
||||
var newState = state.updateElement(service, StateType.stable);
|
||||
emit(newState);
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +21,7 @@ class ServicesCubit extends Cubit<ServicesState> {
|
|||
final all = ServiceTypes.values
|
||||
.map(
|
||||
(type) => Service(
|
||||
state: ServiceStateType.uninitialized,
|
||||
state: StateType.uninitialized,
|
||||
type: type,
|
||||
),
|
||||
)
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
part of 'services_cubit.dart';
|
||||
|
||||
@immutable
|
||||
class ServicesState {
|
||||
class ServicesState extends Equatable{
|
||||
ServicesState(this.all);
|
||||
|
||||
final List<Service> all;
|
||||
|
||||
ServicesState updateElement(Service service, ServiceStateType newState) {
|
||||
ServicesState updateElement(Service service, StateType newState) {
|
||||
var newList = [...all];
|
||||
var index = newList.indexOf(service);
|
||||
newList[index] = service.updateState(newState);
|
||||
|
@ -14,10 +14,13 @@ class ServicesState {
|
|||
}
|
||||
|
||||
List<Service> get connected => all
|
||||
.where((service) => service.state != ServiceStateType.uninitialized)
|
||||
.where((service) => service.state != StateType.uninitialized)
|
||||
.toList();
|
||||
|
||||
List<Service> get uninitialized => all
|
||||
.where((service) => service.state == ServiceStateType.uninitialized)
|
||||
.where((service) => service.state == StateType.uninitialized)
|
||||
.toList();
|
||||
|
||||
@override
|
||||
List<Object> get props => all;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import 'package:equatable/equatable.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';
|
||||
|
||||
enum ProviderTypes {
|
||||
enum ProviderType {
|
||||
server,
|
||||
domain,
|
||||
backup,
|
||||
|
@ -12,10 +12,10 @@ enum ProviderTypes {
|
|||
class ProviderModel extends Equatable {
|
||||
const ProviderModel({this.state, this.type});
|
||||
|
||||
final ServiceStateType state;
|
||||
final ProviderTypes type;
|
||||
final StateType state;
|
||||
final ProviderType type;
|
||||
|
||||
ProviderModel updateState(ServiceStateType newState) => ProviderModel(
|
||||
ProviderModel updateState(StateType newState) => ProviderModel(
|
||||
state: newState,
|
||||
type: type,
|
||||
);
|
||||
|
@ -25,14 +25,14 @@ class ProviderModel extends Equatable {
|
|||
|
||||
IconData get icon {
|
||||
switch (type) {
|
||||
case ProviderTypes.server:
|
||||
case ProviderType.server:
|
||||
return BrandIcons.server;
|
||||
|
||||
case ProviderTypes.domain:
|
||||
case ProviderType.domain:
|
||||
return BrandIcons.globe;
|
||||
|
||||
break;
|
||||
case ProviderTypes.backup:
|
||||
case ProviderType.backup:
|
||||
return BrandIcons.save;
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:equatable/equatable.dart';
|
||||
import 'package:selfprivacy/logic/models/state_types.dart';
|
||||
|
||||
enum ServiceStateType { uninitialized, stable, warning }
|
||||
enum ServiceTypes {
|
||||
messanger,
|
||||
mail,
|
||||
|
@ -12,10 +12,10 @@ enum ServiceTypes {
|
|||
class Service extends Equatable {
|
||||
const Service({this.state, this.type});
|
||||
|
||||
final ServiceStateType state;
|
||||
final StateType state;
|
||||
final ServiceTypes type;
|
||||
|
||||
Service updateState(ServiceStateType newState) => Service(
|
||||
Service updateState(StateType newState) => Service(
|
||||
state: newState,
|
||||
type: type,
|
||||
);
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
enum StateType { uninitialized, stable, warning }
|
|
@ -1,5 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
var navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
class BrandModalSheet extends StatelessWidget {
|
||||
const BrandModalSheet({
|
||||
Key key,
|
||||
|
@ -10,7 +12,7 @@ class BrandModalSheet extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DraggableScrollableSheet(
|
||||
minChildSize: 0.5,
|
||||
minChildSize: 0.95,
|
||||
initialChildSize: 1,
|
||||
maxChildSize: 1,
|
||||
builder: (context, scrollController) {
|
||||
|
@ -20,20 +22,30 @@ class BrandModalSheet extends StatelessWidget {
|
|||
child: Container(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 32, bottom: 6),
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
height: 4,
|
||||
width: 30,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
color: Color(0xFFE3E3E3).withOpacity(0.65),
|
||||
width: double.infinity,
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: 132, bottom: 6),
|
||||
child: Container(
|
||||
height: 4,
|
||||
width: 30,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
color: Color(0xFFE3E3E3).withOpacity(0.65),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: MediaQuery.of(context).size.height - 32 - 4,
|
||||
maxHeight: MediaQuery.of(context).size.height,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
|
@ -41,7 +53,7 @@ class BrandModalSheet extends StatelessWidget {
|
|||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
),
|
||||
width: double.infinity,
|
||||
child: child,
|
||||
child: child
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -17,16 +17,24 @@ class _BrandTabBarState extends State<BrandTabBar> {
|
|||
@override
|
||||
void initState() {
|
||||
currentIndex = widget.controller.index;
|
||||
widget.controller.addListener(() {
|
||||
if (currentIndex != widget.controller.index) {
|
||||
setState(() {
|
||||
currentIndex = widget.controller.index;
|
||||
});
|
||||
}
|
||||
});
|
||||
widget.controller.addListener(_listener);
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
final paddingBottom = MediaQuery.of(context).padding.bottom;
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
import 'package:flutter/material.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 {
|
||||
IconStatusMaks({this.child, this.status});
|
||||
final Icon child;
|
||||
|
||||
final ServiceStateType status;
|
||||
final StateType status;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<Color> colors;
|
||||
switch (status) {
|
||||
case ServiceStateType.uninitialized:
|
||||
case StateType.uninitialized:
|
||||
colors = BrandColors.uninitializedGradientColors;
|
||||
break;
|
||||
case ServiceStateType.stable:
|
||||
case StateType.stable:
|
||||
colors = BrandColors.stableGradientColors;
|
||||
break;
|
||||
case ServiceStateType.warning:
|
||||
case StateType.warning:
|
||||
colors = BrandColors.warningGradientColors;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -10,14 +10,14 @@
|
|||
// import 'package:selfprivacy/ui/pages/rootRoute.dart';
|
||||
// import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||
|
||||
// class OnboardingPage extends StatefulWidget {
|
||||
// const OnboardingPage({Key key}) : super(key: key);
|
||||
// class InitializingPage extends StatefulWidget {
|
||||
// const InitializingPage({Key key}) : super(key: key);
|
||||
|
||||
// @override
|
||||
// _OnboardingPageState createState() => _OnboardingPageState();
|
||||
// _InitializingPageState createState() => _InitializingPageState();
|
||||
// }
|
||||
|
||||
// class _OnboardingPageState extends State<OnboardingPage> {
|
||||
// class _InitializingPageState extends State<InitializingPage> {
|
||||
// PageController controller;
|
||||
// var currentPage = 0;
|
||||
|
||||
|
@ -113,9 +113,10 @@
|
|||
// children: [
|
||||
// Image.asset('assets/images/logos/hetzner.png'),
|
||||
// SizedBox(height: 10),
|
||||
// Text('1. Подключите сервер Hetzner').h2,
|
||||
// BrandText.h2('1. Подключите сервер Hetzner'),
|
||||
// SizedBox(height: 10),
|
||||
// Text('Здесь будут жить наши данные и SelfPrivacy-сервисы').body2,
|
||||
// BrandText.body2(
|
||||
// 'Здесь будут жить наши данные и SelfPrivacy-сервисы'),
|
||||
// _MockForm(
|
||||
// onPressed: _nextPage,
|
||||
// hintText: 'Hetzner API Token',
|
||||
|
@ -137,7 +138,7 @@
|
|||
// children: [
|
||||
// Image.asset('assets/images/logos/namecheap.png'),
|
||||
// SizedBox(height: 10),
|
||||
// Text('2. Настройте домен ').h2,
|
||||
// BrandText.h2('2. Настройте домен'),
|
||||
// SizedBox(height: 10),
|
||||
// RichText(
|
||||
// text: TextSpan(
|
||||
|
@ -179,9 +180,9 @@
|
|||
// children: [
|
||||
// Image.asset('assets/images/logos/cloudflare.png'),
|
||||
// SizedBox(height: 10),
|
||||
// Text('3. Подключите CloudFlare DNS').h2,
|
||||
// BrandText.h2('3. Подключите CloudFlare DNS'),
|
||||
// SizedBox(height: 10),
|
||||
// Text('Для управления DNS вашего домена').body2,
|
||||
// BrandText.body2('Для управления DNS вашего домена'),
|
||||
// _MockForm(
|
||||
// onPressed: _nextPage,
|
||||
// hintText: 'CloudFlare API Token',
|
||||
|
@ -202,10 +203,10 @@
|
|||
// children: [
|
||||
// Image.asset('assets/images/logos/aws.png'),
|
||||
// SizedBox(height: 10),
|
||||
// Text('4. Подключите Amazon AWS для бекапа').h2,
|
||||
// BrandText.h2('4. Подключите Amazon AWS для бекапа'),
|
||||
// SizedBox(height: 10),
|
||||
// Text('IaaS-провайдер, для бесплатного хранения резервных копии ваших данных в зашифрованном виде')
|
||||
// .body2,
|
||||
// BrandText.body2(
|
||||
// 'IaaS-провайдер, для бесплатного хранения резервных копии ваших данных в зашифрованном виде'),
|
||||
// _MockForm(
|
||||
// onPressed: () {
|
||||
// Navigator.of(context)
|
||||
|
@ -254,7 +255,7 @@
|
|||
// child: Column(
|
||||
// children: [
|
||||
// SizedBox(height: 40),
|
||||
// Text('Как получить Hetzner API Token').h2,
|
||||
// BrandText.h2('Как получить Hetzner API Token'),
|
||||
// SizedBox(height: 20),
|
||||
// RichText(
|
||||
// text: TextSpan(
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import 'package:flutter/material.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/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_header/brand_header.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> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cards = ProviderTypes.values
|
||||
.map((type) => _Card(
|
||||
provider:
|
||||
ProviderModel(state: ServiceStateType.stable, type: type)))
|
||||
final cards = ProviderType.values
|
||||
.map((type) =>
|
||||
_Card(provider: ProviderModel(state: StateType.stable, type: type)))
|
||||
.toList();
|
||||
return Scaffold(
|
||||
appBar: PreferredSize(
|
||||
|
@ -49,16 +49,16 @@ class _Card extends StatelessWidget {
|
|||
String stableText;
|
||||
|
||||
switch (provider.type) {
|
||||
case ProviderTypes.server:
|
||||
case ProviderType.server:
|
||||
title = 'Сервер';
|
||||
stableText = 'В норме';
|
||||
break;
|
||||
case ProviderTypes.domain:
|
||||
case ProviderType.domain:
|
||||
title = 'Домен';
|
||||
message = 'example.com';
|
||||
stableText = 'Домен настроен';
|
||||
break;
|
||||
case ProviderTypes.backup:
|
||||
case ProviderType.backup:
|
||||
message = '22 янв 2021 14:30';
|
||||
title = 'Резервное копирование';
|
||||
stableText = 'В норме';
|
||||
|
@ -91,8 +91,7 @@ class _Card extends StatelessWidget {
|
|||
BrandText.body2(message),
|
||||
SizedBox(height: 10),
|
||||
],
|
||||
if (provider.state == ServiceStateType.stable)
|
||||
BrandText.body2(stableText),
|
||||
if (provider.state == StateType.stable) BrandText.body2(stableText),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -115,75 +114,84 @@ class _ProviderDetails extends StatelessWidget {
|
|||
String title;
|
||||
|
||||
switch (provider.type) {
|
||||
case ProviderTypes.server:
|
||||
case ProviderType.server:
|
||||
title = 'Сервер';
|
||||
break;
|
||||
case ProviderTypes.domain:
|
||||
case ProviderType.domain:
|
||||
title = 'Домен';
|
||||
|
||||
break;
|
||||
case ProviderTypes.backup:
|
||||
case ProviderType.backup:
|
||||
title = 'Резервное копирование';
|
||||
break;
|
||||
}
|
||||
return BrandModalSheet(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Padding(
|
||||
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(
|
||||
child: Navigator(
|
||||
key: navigatorKey,
|
||||
initialRoute: '/',
|
||||
onGenerateRoute: (_) {
|
||||
return materialRoute(
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 13),
|
||||
IconStatusMaks(
|
||||
status: provider.state,
|
||||
child: Icon(provider.icon, size: 40, color: Colors.white),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Padding(
|
||||
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),
|
||||
BrandText.h1(title),
|
||||
SizedBox(height: 10),
|
||||
BrandText.body1(statusText),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Text('Статусы сервера и сервис провайдера и т.д.')
|
||||
Padding(
|
||||
padding: brandPagePadding1,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 13),
|
||||
IconStatusMaks(
|
||||
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('Статусы сервера и сервис провайдера и т.д.')
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -11,57 +11,51 @@ class SettingsPage extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: PreferredSize(
|
||||
child: BrandHeader(title: 'Настройки', hasBackButton: true),
|
||||
preferredSize: Size.fromHeight(52),
|
||||
return ListView(
|
||||
padding: brandPagePadding2,
|
||||
children: [
|
||||
SizedBox(height: 10),
|
||||
BrandHeader(title: 'Настройки', hasBackButton: true),
|
||||
BrandDivider(),
|
||||
SwitcherBlock(
|
||||
onChange: (_) {},
|
||||
child: _TextColumn(
|
||||
title: 'Allow Auto-upgrade',
|
||||
value: 'Wether to allow automatic packages upgrades',
|
||||
),
|
||||
isActive: true,
|
||||
),
|
||||
body: ListView(
|
||||
padding: brandPagePadding2,
|
||||
children: [
|
||||
BrandDivider(),
|
||||
SwitcherBlock(
|
||||
onChange: (_) {},
|
||||
child: _TextColumn(
|
||||
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',
|
||||
),
|
||||
)
|
||||
],
|
||||
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',
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
import 'package:flutter/cupertino.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_text/brand_text.dart';
|
||||
import 'package:selfprivacy/ui/pages/more/more.dart';
|
||||
import 'package:selfprivacy/ui/pages/providers/providers.dart';
|
||||
import 'package:selfprivacy/ui/pages/services/services.dart';
|
||||
import 'package:selfprivacy/ui/pages/users/users.dart';
|
||||
|
||||
import 'initializing/initializing.dart';
|
||||
|
||||
|
||||
class RootPage extends StatefulWidget {
|
||||
const RootPage({Key key}) : super(key: key);
|
||||
|
||||
|
@ -25,20 +30,23 @@ class _RootPageState extends State<RootPage>
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
tabController.dispose();
|
||||
super.dispose();
|
||||
tabController.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var isReady =
|
||||
context.select((ProvidersCubit bloc) => bloc.state.isFullyInitialized);
|
||||
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
body: TabBarView(
|
||||
controller: tabController,
|
||||
children: [
|
||||
ProvidersPage(),
|
||||
ServicesPage(),
|
||||
UsersPage(),
|
||||
isReady ? ProvidersPage() : InitializingPage(),
|
||||
isReady ? ServicesPage() : _NotReady(),
|
||||
isReady ? UsersPage() : _NotReady(),
|
||||
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'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ class _Card extends StatelessWidget {
|
|||
SizedBox(height: 10),
|
||||
BrandText.h2(title),
|
||||
SizedBox(height: 10),
|
||||
if (service.state == ServiceStateType.uninitialized) ...[
|
||||
if (service.state == StateType.uninitialized) ...[
|
||||
BrandText.body1(description),
|
||||
SizedBox(height: 10),
|
||||
BrandButton.text(
|
||||
|
@ -102,7 +102,7 @@ class _Card extends StatelessWidget {
|
|||
context.read<ServicesCubit>().connect(service);
|
||||
})
|
||||
],
|
||||
if (service.state == ServiceStateType.stable)
|
||||
if (service.state == StateType.stable)
|
||||
BrandText.body2('Подключен'),
|
||||
],
|
||||
),
|
||||
|
|
Loading…
Reference in New Issue