add dark theme

fdroid
Kherel 2020-12-08 20:26:51 +01:00
parent 7ebfc2c048
commit 9a8c16344f
27 changed files with 798 additions and 512 deletions

View File

@ -1,5 +1,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/services/services_cubit.dart';
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
@ -10,8 +12,15 @@ class BlocAndProviderConfig extends StatelessWidget {
@override
Widget build(BuildContext context) {
var platformBrightness =
SchedulerBinding.instance.window.platformBrightness;
// var platformBrightness = Brightness.dark;
return MultiProvider(
providers: [
BlocProvider<AppSettingsCubit>(
create: (BuildContext context) => AppSettingsCubit(
isDarkModeOn: platformBrightness == Brightness.dark),
),
BlocProvider<ServicesCubit>(
create: (BuildContext context) => ServicesCubit(),
),

View File

@ -41,7 +41,8 @@ class BrandColors {
static const dividerColor = gray5;
static const warning = red;
static get navBackground => white.withOpacity(0.8);
static get navBackgroundLight => white.withOpacity(0.8);
static get navBackgroundDark => black.withOpacity(0.8);
static const List<Color> uninitializedGradientColors = [
Color(0xFF555555),

View File

@ -4,7 +4,7 @@ import 'package:selfprivacy/config/text_themes.dart';
import 'brand_colors.dart';
final theme = ThemeData(
final ligtTheme = ThemeData(
primaryColor: BrandColors.primary,
brightness: Brightness.light,
scaffoldBackgroundColor: BrandColors.scaffoldBackground,
@ -24,13 +24,43 @@ final theme = ThemeData(
TextTheme(
headline1: headline1Style,
headline2: headline2Style,
caption: captionStyle,
caption: headline4Style,
bodyText1: body1Style,
subtitle1: TextStyle(fontSize: 15, height: 1.6), // text input style
),
),
);
var darkTheme = ligtTheme.copyWith(
brightness: Brightness.dark,
scaffoldBackgroundColor: Color(0xFF202120),
iconTheme: IconThemeData(color: BrandColors.gray3),
cardColor: BrandColors.gray1,
textTheme: GoogleFonts.interTextTheme(
TextTheme(
headline1: headline1Style.copyWith(color: BrandColors.white),
headline2: headline2Style.copyWith(color: BrandColors.white),
caption: headline4Style.copyWith(color: BrandColors.white),
bodyText1: body1Style.copyWith(color: BrandColors.white),
subtitle1: TextStyle(fontSize: 15, height: 1.6), // text input style
),
),
inputDecorationTheme: InputDecorationTheme(
labelStyle: TextStyle(color: BrandColors.white),
hintStyle: TextStyle(color: BrandColors.white),
border: OutlineInputBorder(
borderSide: BorderSide(
color: BrandColors.white,
),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: BrandColors.white,
),
),
),
);
final brandPagePadding1 = EdgeInsets.symmetric(horizontal: 15, vertical: 30);
final brandPagePadding2 = EdgeInsets.symmetric(horizontal: 15);

View File

@ -29,7 +29,7 @@ final headline3Style = GoogleFonts.inter(
color: BrandColors.headlineColor,
);
final captionStyle = GoogleFonts.inter(
final headline4Style = GoogleFonts.inter(
fontSize: 18,
fontWeight: NamedFontWeight.medium,
color: BrandColors.headlineColor,

View File

@ -0,0 +1,18 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/widgets.dart';
export 'package:provider/provider.dart';
part 'app_settings_state.dart';
class AppSettingsCubit extends Cubit<AppSettingsState> {
AppSettingsCubit({
bool isDarkModeOn,
}) : super(
AppSettingsState(isDarkModeOn: isDarkModeOn),
);
void update({@required bool isDarkModeOn}) {
emit(AppSettingsState(isDarkModeOn: isDarkModeOn));
}
}

View File

@ -0,0 +1,12 @@
part of 'app_settings_cubit.dart';
class AppSettingsState extends Equatable {
const AppSettingsState({
@required this.isDarkModeOn,
});
final bool isDarkModeOn;
@override
List<Object> get props => [isDarkModeOn];
}

View File

@ -7,6 +7,7 @@ import 'package:selfprivacy/ui/pages/rootRoute.dart';
import 'config/bloc_config.dart';
import 'config/brand_theme.dart';
import 'config/localization.dart';
import 'logic/cubit/app_settings/app_settings_cubit.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
@ -20,21 +21,22 @@ void main() {
);
}
var _showOnbording = false;
var _showOnbording = true;
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.light, // Manually changnig appbar color
child: MaterialApp(
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
debugShowCheckedModeBanner: false,
title: 'SelfPrivacy',
theme: theme,
theme: context.watch<AppSettingsCubit>().state.isDarkModeOn
? darkTheme
: ligtTheme,
home: _showOnbording ? OnboardingPage() : RootPage(),
),
);

View File

@ -2,7 +2,7 @@ import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/utils/extensions/text_extension.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
enum BrandButtonTypes { rised, text, iconText }
@ -182,9 +182,7 @@ class _IconTextButton extends StatelessWidget {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
).body1,
BrandText.body1(title),
Padding(
padding: const EdgeInsets.all(12.0),
child: icon,

View File

@ -12,7 +12,9 @@ class BrandCard extends StatelessWidget {
return Container(
margin: EdgeInsets.only(bottom: 30),
decoration: BoxDecoration(
color: BrandColors.white,
color: Theme.of(context).brightness == Brightness.dark
? BrandColors.black
: BrandColors.white,
borderRadius: BorderRadius.circular(20),
).ev8,
padding: EdgeInsets.symmetric(

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
import 'package:selfprivacy/utils/extensions/text_extension.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
class BrandHeader extends StatelessWidget {
const BrandHeader({
@ -30,7 +30,7 @@ class BrandHeader extends StatelessWidget {
),
SizedBox(width: 10),
],
Text(title).caption,
BrandText.h4(title),
],
),
),

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.dart';
class BrandModalSheet extends StatelessWidget {
const BrandModalSheet({
@ -39,7 +38,7 @@ class BrandModalSheet extends StatelessWidget {
decoration: BoxDecoration(
borderRadius:
BorderRadius.vertical(top: Radius.circular(20)),
color: BrandColors.white,
color: Theme.of(context).scaffoldBackgroundColor,
),
width: double.infinity,
child: child,

View File

@ -4,15 +4,15 @@ import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
final _kBottomTabBarHeight = 51;
class BottomTabBar extends StatefulWidget {
BottomTabBar({Key key, this.controller}) : super(key: key);
class BrandTabBar extends StatefulWidget {
BrandTabBar({Key key, this.controller}) : super(key: key);
final TabController controller;
@override
_BottomTabBarState createState() => _BottomTabBarState();
_BrandTabBarState createState() => _BrandTabBarState();
}
class _BottomTabBarState extends State<BottomTabBar> {
class _BrandTabBarState extends State<BrandTabBar> {
int currentIndex;
@override
void initState() {
@ -30,11 +30,14 @@ class _BottomTabBarState extends State<BottomTabBar> {
@override
Widget build(BuildContext context) {
final paddingBottom = MediaQuery.of(context).padding.bottom;
return SizedBox(
height: paddingBottom + _kBottomTabBarHeight,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16),
color: BrandColors.navBackground,
color: Theme.of(context).brightness == Brightness.dark
? BrandColors.navBackgroundDark
: BrandColors.navBackgroundLight,
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -50,8 +53,10 @@ class _BottomTabBarState extends State<BottomTabBar> {
}
_getIconButton(String label, IconData iconData, int index) {
var color =
currentIndex == index ? BrandColors.black : BrandColors.inactive;
var acitivColor = Theme.of(context).brightness == Brightness.dark
? BrandColors.white
: BrandColors.black;
var color = currentIndex == index ? acitivColor : BrandColors.inactive;
return InkWell(
onTap: () => widget.controller.animateTo(index),
child: Padding(

View File

@ -0,0 +1,119 @@
import 'package:flutter/material.dart';
import 'package:selfprivacy/config/text_themes.dart';
enum TextType {
h1, // right now only at onboarding and opened providers
h2, // cards titles
h3, // titles in about page
h4, // caption
body1, // normal
body2, // with opacity
small
}
class BrandText extends StatelessWidget {
const BrandText(
this.text, {
Key key,
this.style,
@required this.type,
this.overflow,
this.softWrap,
}) : super(key: key);
final String text;
final TextStyle style;
final TextType type;
final TextOverflow overflow;
final bool softWrap;
factory BrandText.h1(
String text, {
TextStyle style,
TextOverflow overflow,
bool softWrap,
}) =>
BrandText(
text,
type: TextType.h1,
style: style,
);
factory BrandText.h2(String text, {TextStyle style}) => BrandText(
text,
type: TextType.h2,
style: style,
);
factory BrandText.h3(String text, {TextStyle style}) => BrandText(
text,
type: TextType.h3,
style: style,
);
factory BrandText.h4(String text, {TextStyle style}) => BrandText(
text,
type: TextType.h4,
style: style,
);
factory BrandText.body1(String text, {TextStyle style}) => BrandText(
text,
type: TextType.body1,
style: style,
);
factory BrandText.body2(String text, {TextStyle style}) => BrandText(
text,
type: TextType.body2,
style: style,
);
factory BrandText.small(String text, {TextStyle style}) => BrandText(
text,
type: TextType.small,
style: style,
);
@override
Text build(BuildContext context) {
TextStyle style;
var isDark = Theme.of(context).brightness == Brightness.dark;
switch (type) {
case TextType.h1:
style = isDark
? headline1Style.copyWith(color: Colors.white)
: headline1Style;
break;
case TextType.h2:
style = isDark
? headline2Style.copyWith(color: Colors.white)
: headline2Style;
break;
case TextType.h3:
style = isDark
? headline3Style.copyWith(color: Colors.white)
: headline3Style;
break;
case TextType.h4:
style = isDark
? headline4Style.copyWith(color: Colors.white)
: headline4Style;
break;
case TextType.body1:
style = isDark ? body1Style.copyWith(color: Colors.white) : body1Style;
break;
case TextType.body2:
style = isDark
? body2Style.copyWith(color: Colors.white.withOpacity(0.6))
: body2Style;
break;
case TextType.small:
style = isDark ? smallStyle.copyWith(color: Colors.white) : smallStyle;
break;
}
if (this.style != null) {
style = style.merge(this.style);
}
return Text(
text,
style: style,
overflow: overflow,
softWrap: softWrap,
);
}
}

View File

@ -6,10 +6,12 @@ class SwitcherBlock extends StatelessWidget {
Key key,
@required this.child,
@required this.isActive,
@required this.onChange,
}) : super(key: key);
final Widget child;
final bool isActive;
final ValueChanged<bool> onChange;
@override
Widget build(BuildContext context) {
@ -28,7 +30,7 @@ class SwitcherBlock extends StatelessWidget {
Switch(
activeColor: BrandColors.green1,
activeTrackColor: BrandColors.green2,
onChanged: (v) {},
onChanged: onChange,
value: isActive,
),
],

View File

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart';
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
import 'package:selfprivacy/utils/extensions/text_extension.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
class AboutPage extends StatelessWidget {
const AboutPage({Key key}) : super(key: key);
@ -20,28 +20,28 @@ class AboutPage extends StatelessWidget {
children: [
BrandDivider(),
SizedBox(height: 20),
Text('О проекте').h3,
BrandText.h3('О проекте'),
SizedBox(height: 10),
Text('Всё больше организаций хотят владеть нашими данными').body1,
BrandText.body1(
'Всё больше организаций хотят владеть нашими данными'),
SizedBox(height: 10),
Text('А мы сами хотим распоряжаться своими данными на своем сервере.')
.body1,
BrandText.body1(
'А мы сами хотим распоряжаться своими данными на своем сервере.'),
SizedBox(height: 20),
BrandDivider(),
SizedBox(height: 10),
Text('Миссия проекта').h3,
BrandText.h3('Миссия проекта'),
SizedBox(height: 10),
Text('Цифровая независимость и приватность доступная каждому'),
BrandText.body1(
'Цифровая независимость и приватность доступная каждому'),
SizedBox(height: 20),
BrandDivider(),
SizedBox(height: 10),
Text('Цель').h3,
BrandText.h3('Цель'),
SizedBox(height: 10),
Text(
BrandText.body1(
'Развивать программу, которая позволит каждому создавать приватные сервисы для себя и своих близких'),
SizedBox(height: 10),
Text(
'Развивать программу, которая позволит каждому создавать приватные сервисы для себя и своих близких'),
],
),
),

View File

@ -1,37 +1,68 @@
import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart';
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
import 'package:selfprivacy/ui/components/switch_block/switch_bloc.dart';
import 'package:selfprivacy/utils/extensions/text_extension.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
class AppSettingsPage extends StatelessWidget {
class AppSettingsPage extends StatefulWidget {
const AppSettingsPage({Key key}) : super(key: key);
@override
_AppSettingsPageState createState() => _AppSettingsPageState();
}
class _AppSettingsPageState extends State<AppSettingsPage> {
@override
Widget build(BuildContext context) {
var appSettings = context.watch<AppSettingsCubit>();
var isDarkModeOn = appSettings.state.isDarkModeOn;
return SafeArea(
child: Scaffold(
appBar: PreferredSize(
child:
BrandHeader(title: 'Настройки приложения', hasBackButton: true),
preferredSize: Size.fromHeight(52),
),
body: ListView(
padding: brandPagePadding2,
children: [
BrandDivider(),
SwitcherBlock(
child: _TextColumn(
title: 'Dark Theme',
value: 'Change your the app theme',
),
isActive: true,
),
],
),
),
child: Builder(builder: (context) {
return Scaffold(
appBar: PreferredSize(
child:
BrandHeader(title: 'Настройки приложения', hasBackButton: true),
preferredSize: Size.fromHeight(52),
),
body: ListView(
padding: brandPagePadding2,
children: [
BrandDivider(),
Container(
padding: EdgeInsets.only(top: 20, bottom: 5),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(width: 1, color: BrandColors.dividerColor),
)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: _TextColumn(
title: 'Dark Theme',
value: 'Change your the app theme',
),
),
SizedBox(width: 5),
Switch(
activeColor: BrandColors.green1,
activeTrackColor: BrandColors.green2,
value: Theme.of(context).brightness == Brightness.dark,
onChanged: (value) =>
appSettings.update(isDarkModeOn: !isDarkModeOn),
),
],
),
)
],
),
);
}),
);
}
}
@ -52,21 +83,21 @@ class _TextColumn extends StatelessWidget {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title).body1.copyWith(
style: TextStyle(color: hasWarning ? BrandColors.warning : null)),
BrandText.body1(
title,
style: TextStyle(color: hasWarning ? BrandColors.warning : null),
),
SizedBox(height: 5),
Text(value)
.body1
.copyWith(
style: TextStyle(
fontSize: 13,
height: 1.53,
color: BrandColors.gray1,
),
)
.copyWith(
style:
TextStyle(color: hasWarning ? BrandColors.warning : null)),
BrandText.body1(
title,
style: TextStyle(color: hasWarning ? BrandColors.warning : null),
),
BrandText.body1(value,
style: TextStyle(
fontSize: 13,
height: 1.53,
color: BrandColors.gray1,
).merge(TextStyle(color: hasWarning ? BrandColors.warning : null))),
],
);
}

View File

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart';
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
import 'package:selfprivacy/utils/extensions/text_extension.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:package_info/package_info.dart';
class InfoPage extends StatelessWidget {
@ -24,9 +24,8 @@ class InfoPage extends StatelessWidget {
FutureBuilder(
future: _version(),
builder: (context, snapshot) {
return Text(
'Тут любая служебная информация, v.${snapshot.data}')
.body1;
return BrandText.body1(
'Тут любая служебная информация, v.${snapshot.data}');
}),
],
),

View File

@ -4,7 +4,7 @@ import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart';
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
import 'package:selfprivacy/utils/extensions/text_extension.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart';
import 'about/about.dart';
@ -80,11 +80,14 @@ class _NavItem extends StatelessWidget {
),
child: Row(
children: [
Text(title).body1,
BrandText.body1(title),
Spacer(),
SizedBox(
width: 56,
child: Icon(iconData, size: 20),
child: Icon(
iconData,
size: 20,
),
),
],
),

View File

@ -1,340 +1,53 @@
import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/config/text_themes.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/dots_indicator/dots_indicator.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:selfprivacy/ui/pages/rootRoute.dart';
import 'package:selfprivacy/utils/extensions/text_extension.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart';
class OnboardingPage extends StatefulWidget {
class OnboardingPage extends StatelessWidget {
const OnboardingPage({Key key}) : super(key: key);
@override
_OnboardingPageState createState() => _OnboardingPageState();
}
class _OnboardingPageState extends State<OnboardingPage> {
PageController controller;
var currentPage = 0;
@override
void initState() {
controller = PageController(
initialPage: 0,
)..addListener(() {
if (currentPage != controller.page.toInt()) {
setState(() {
currentPage = controller.page.toInt();
});
}
});
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {});
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
var steps = getSteps();
return SafeArea(
child: Scaffold(
body: ListView(
shrinkWrap: true,
children: [
Padding(
padding: brandPagePadding1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Начало').caption,
Text('SelfPrivacy').h1,
SizedBox(
height: 10,
),
RichText(
text: TextSpan(
children: [
TextSpan(
text:
'Для устойчивости и приватности требует много учёток. Полная инструкция на ',
style: body2Style,
),
BrandSpanButton.link(
text: 'selfprivacy.org/start',
urlString: 'https://selfprivacy.org/start',
),
],
),
),
],
),
),
Container(
height: 480,
child: PageView.builder(
physics: NeverScrollableScrollPhysics(),
allowImplicitScrolling: false,
controller: controller,
itemBuilder: (_, index) {
return Padding(
padding: brandPagePadding2,
child: steps[index],
);
},
itemCount: 4,
),
),
DotsIndicator(
activeIndex: currentPage,
count: steps.length,
),
SizedBox(height: 50),
],
),
),
);
}
List<Widget> getSteps() => <Widget>[
BrandCard(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.asset('assets/images/logos/hetzner.png'),
SizedBox(height: 10),
Text('1. Подключите сервер Hetzner').h2,
SizedBox(height: 10),
Text('Здесь будут жить наши данные и SelfPrivacy-сервисы').body2,
_MockForm(
onPressed: _nextPage,
hintText: 'Hetzner API Token',
length: 2,
),
SizedBox(height: 20),
Spacer(),
BrandButton.text(
onPressed: () => _showModal(context, _HowHetzner()),
title: 'Как получить API Token',
),
],
body: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 15,
vertical: 45,
),
),
BrandCard(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.asset('assets/images/logos/namecheap.png'),
SizedBox(height: 10),
Text('2. Настройте домен ').h2,
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,
),
],
Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
BrandText.h1(
'Онбординг',
),
SizedBox(height: 20),
BrandText.body2(
'Тут рассказ на 1-2 слайда о том, что делает это приложение, какие твои проблемы решает и как (в общем чего ожидать от сервиса).',
),
],
),
),
),
_MockForm(
onPressed: _nextPage,
hintText: 'Домен, например, selfprivacy.org',
submitButtonText: 'Проверить DNS',
length: 2,
),
Spacer(),
BrandButton.text(
onPressed: () {},
title: 'Как настроить DNS CloudFlare',
),
],
),
),
BrandCard(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.asset('assets/images/logos/cloudflare.png'),
SizedBox(height: 10),
Text('3. Подключите CloudFlare DNS').h2,
SizedBox(height: 10),
Text('Для управления DNS вашего домена').body2,
_MockForm(
onPressed: _nextPage,
hintText: 'CloudFlare API Token',
length: 2,
),
Spacer(),
BrandButton.text(
onPressed: () {},
title: 'Как получить API Token',
),
],
),
),
BrandCard(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.asset('assets/images/logos/aws.png'),
SizedBox(height: 10),
Text('4. Подключите Amazon AWS для бекапа').h2,
SizedBox(height: 10),
Text('IaaS-провайдер, для бесплатного хранения резервных копии ваших данных в зашифрованном виде')
.body2,
_MockForm(
BrandButton.rised(
onPressed: () {
Navigator.of(context)
.pushReplacement(materialRoute(RootPage()));
},
hintText: 'Amazon AWS Access Key',
length: 2,
),
Spacer(),
BrandButton.text(
onPressed: () {},
title: 'Как получить API Token',
),
title: 'Приступим!',
)
],
),
),
];
void _showModal(BuildContext context, Widget widget) {
showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (BuildContext context) {
return widget;
},
);
}
void _nextPage() => controller.nextPage(
duration: Duration(milliseconds: 300),
curve: Curves.easeIn,
);
}
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),
Text('Как получить Hetzner API Token').h2,
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 _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 = '';
@override
Widget build(BuildContext context) {
return Column(
children: [
SizedBox(height: 20),
TextField(
onChanged: (value) => {
setState(() {
text = value;
})
},
decoration: InputDecoration(hintText: widget.hintText),
),
SizedBox(height: 20),
BrandButton.rised(
onPressed:
text.length == widget.length ? widget.onPressed ?? () {} : null,
title: widget.submitButtonText,
),
],
);
}
}

View File

@ -5,7 +5,7 @@ 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/utils/extensions/text_extension.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
class OnboardingPage extends StatelessWidget {
const OnboardingPage({Key key}) : super(key: key);
@ -16,8 +16,8 @@ class OnboardingPage extends StatelessWidget {
body: ListView(
padding: brandPagePadding1,
children: [
Text('Начало').caption,
Text('SelfPrivacy').h1,
BrandText.h4('Начало'),
BrandText.h1('SelfPrivacy'),
SizedBox(
height: 10,
),
@ -43,10 +43,10 @@ class OnboardingPage extends StatelessWidget {
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(
hintText: 'Hetzner API Token',
),
@ -64,7 +64,7 @@ class OnboardingPage extends StatelessWidget {
children: [
Image.asset('assets/images/logos/namecheap.png'),
SizedBox(height: 10),
Text('2. Настройте домен ').h2,
BrandText.h2('2. Настройте домен'),
SizedBox(height: 10),
RichText(
text: TextSpan(
@ -103,9 +103,9 @@ class OnboardingPage extends StatelessWidget {
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(
hintText: 'CloudFlare API Token',
),
@ -123,10 +123,10 @@ class OnboardingPage extends StatelessWidget {
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(
hintText: 'Amazon AWS Access Key',
),
@ -163,25 +163,27 @@ class _HowHetzner extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BrandModalSheet(
child: Column(
children: [
SizedBox(height: 40),
Text('Как получить Hetzner API Token').h2,
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: '''
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 (с иконкой ключика).
@ -192,13 +194,14 @@ class _HowHetzner extends StatelessWidget {
6 В поле Description, даём нашему токену название (это может быть любое название, которые вам нравиться. Сути оно не меняет.
''',
style: body1Style,
),
],
''',
style: body1Style,
),
],
),
),
),
],
],
),
),
);
}

View File

@ -0,0 +1,340 @@
// import 'package:flutter/material.dart';
// import 'package:selfprivacy/config/brand_theme.dart';
// import 'package:selfprivacy/config/text_themes.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';
// import 'package:selfprivacy/ui/components/dots_indicator/dots_indicator.dart';
// 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);
// @override
// _OnboardingPageState createState() => _OnboardingPageState();
// }
// class _OnboardingPageState extends State<OnboardingPage> {
// PageController controller;
// var currentPage = 0;
// @override
// void initState() {
// controller = PageController(
// initialPage: 0,
// )..addListener(() {
// if (currentPage != controller.page.toInt()) {
// setState(() {
// currentPage = controller.page.toInt();
// });
// }
// });
// super.initState();
// WidgetsBinding.instance.addPostFrameCallback((_) {});
// }
// @override
// void dispose() {
// controller.dispose();
// super.dispose();
// }
// @override
// Widget build(BuildContext context) {
// var steps = getSteps();
// return SafeArea(
// child: Scaffold(
// body: ListView(
// shrinkWrap: true,
// children: [
// Padding(
// padding: brandPagePadding1,
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// 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',
// ),
// ],
// ),
// ),
// ],
// ),
// ),
// Container(
// height: 480,
// child: PageView.builder(
// physics: NeverScrollableScrollPhysics(),
// allowImplicitScrolling: false,
// controller: controller,
// itemBuilder: (_, index) {
// return Padding(
// padding: brandPagePadding2,
// child: steps[index],
// );
// },
// itemCount: 4,
// ),
// ),
// DotsIndicator(
// activeIndex: currentPage,
// count: steps.length,
// ),
// SizedBox(height: 50),
// ],
// ),
// ),
// );
// }
// List<Widget> getSteps() => <Widget>[
// BrandCard(
// child: Column(
// mainAxisSize: MainAxisSize.min,
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Image.asset('assets/images/logos/hetzner.png'),
// SizedBox(height: 10),
// Text('1. Подключите сервер Hetzner').h2,
// SizedBox(height: 10),
// Text('Здесь будут жить наши данные и SelfPrivacy-сервисы').body2,
// _MockForm(
// onPressed: _nextPage,
// hintText: 'Hetzner API Token',
// length: 2,
// ),