update
|
@ -39,7 +39,7 @@ android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
applicationId "pro.kherel.selfprivacy"
|
applicationId "pro.kherel.selfprivacy"
|
||||||
minSdkVersion 16
|
minSdkVersion 18
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
|
|
|
@ -15,11 +15,9 @@
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize"
|
||||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
android:allowBackup="false" >
|
||||||
the Android process has started. This theme is visible to the user
|
<!-- https://github.com/mogol/flutter_secure_storage allowBackup="false" -->
|
||||||
while the Flutter UI initializes. After that, this theme continues
|
|
||||||
to determine the Window background behind the Flutter UI. -->
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.flutter.embedding.android.NormalTheme"
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
android:resource="@style/NormalTheme"
|
android:resource="@style/NormalTheme"
|
||||||
|
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 885 B |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 12 KiB |
BIN
assets/images/icon/logo_android.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
assets/images/icon/logo_ios.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
|
@ -1,5 +1,7 @@
|
||||||
PODS:
|
PODS:
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
|
- flutter_secure_storage (3.3.1):
|
||||||
|
- Flutter
|
||||||
- package_info (0.0.1):
|
- package_info (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- path_provider (0.0.1):
|
- path_provider (0.0.1):
|
||||||
|
@ -11,6 +13,7 @@ PODS:
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
|
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||||
- package_info (from `.symlinks/plugins/package_info/ios`)
|
- package_info (from `.symlinks/plugins/package_info/ios`)
|
||||||
- path_provider (from `.symlinks/plugins/path_provider/ios`)
|
- path_provider (from `.symlinks/plugins/path_provider/ios`)
|
||||||
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
|
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
|
||||||
|
@ -19,6 +22,8 @@ DEPENDENCIES:
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
Flutter:
|
Flutter:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
|
flutter_secure_storage:
|
||||||
|
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
||||||
package_info:
|
package_info:
|
||||||
:path: ".symlinks/plugins/package_info/ios"
|
:path: ".symlinks/plugins/package_info/ios"
|
||||||
path_provider:
|
path_provider:
|
||||||
|
@ -30,6 +35,7 @@ EXTERNAL SOURCES:
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
|
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
|
||||||
|
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
|
||||||
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
|
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
|
||||||
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
|
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
|
||||||
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
|
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
|
||||||
|
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 670 B After Width: | Height: | Size: 354 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 608 B |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 906 B |
Before Width: | Height: | Size: 992 B After Width: | Height: | Size: 525 B |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 907 B |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 608 B |
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 2.2 KiB |
|
@ -1,8 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.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/initializing/initializing_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/providers/providers_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,14 +13,22 @@ class BlocAndProviderConfig extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
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 isDark = platformBrightness == Brightness.dark;
|
||||||
// var platformBrightness = Brightness.dark;
|
var isDark = false;
|
||||||
|
|
||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
BlocProvider(create: (_) => AppSettingsCubit(isDarkModeOn: isDark)),
|
BlocProvider(
|
||||||
BlocProvider(create: (_) => InitializingCubit()),
|
create: (_) => AppSettingsCubit(
|
||||||
|
isDarkModeOn: isDark,
|
||||||
|
isOnbordingShowing: true,
|
||||||
|
)..load(),
|
||||||
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (_) => AppConfigCubit()..load(),
|
||||||
|
),
|
||||||
BlocProvider(create: (_) => ServicesCubit()),
|
BlocProvider(create: (_) => ServicesCubit()),
|
||||||
BlocProvider(create: (_) => ProvidersCubit()),
|
BlocProvider(create: (_) => ProvidersCubit()),
|
||||||
BlocProvider(create: (_) => UsersCubit()),
|
BlocProvider(create: (_) => UsersCubit()),
|
||||||
|
|
|
@ -1,33 +1,23 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class BrandColors {
|
class BrandColors {
|
||||||
/// ![](https://www.colorhexa.com/093CEF.png)
|
|
||||||
static const Color blue = Color(0xFF093CEF);
|
static const Color blue = Color(0xFF093CEF);
|
||||||
static const Color white = Colors.white;
|
static const Color white = Colors.white;
|
||||||
static const Color black = Colors.black;
|
static const Color black = Colors.black;
|
||||||
|
|
||||||
/// ![](https://www.colorhexa.com/555555.png)
|
|
||||||
static const Color gray1 = Color(0xFF555555);
|
static const Color gray1 = Color(0xFF555555);
|
||||||
|
|
||||||
/// ![](https://www.colorhexa.com/7C7C7C.png)
|
|
||||||
static const Color gray2 = Color(0xFF7C7C7C);
|
static const Color gray2 = Color(0xFF7C7C7C);
|
||||||
|
|
||||||
/// ![](https://www.colorhexa.com/fafafa.png)
|
|
||||||
static const Color gray3 = Color(0xFFFAFAFA);
|
static const Color gray3 = Color(0xFFFAFAFA);
|
||||||
|
|
||||||
/// ![](https://www.colorhexa.com/DDDDDD.png)
|
|
||||||
static const Color gray4 = Color(0xFFDDDDDD);
|
static const Color gray4 = Color(0xFFDDDDDD);
|
||||||
|
|
||||||
/// ![](https://www.colorhexa.com/EDEEF1.png)
|
|
||||||
static const Color gray5 = Color(0xFFEDEEF1);
|
static const Color gray5 = Color(0xFFEDEEF1);
|
||||||
|
static Color gray6 = Color(0xFF181818).withOpacity(0.7);
|
||||||
|
static const Color grey7 = Color(0xFFABABAB);
|
||||||
|
|
||||||
/// ![](https://www.colorhexa.com/FA0E0E.png)
|
static const Color red1 = Color(0xFFFA0E0E);
|
||||||
static const Color red = Color(0xFFFA0E0E);
|
static const Color red2 = Color(0xFFE65527);
|
||||||
|
|
||||||
/// ![](https://www.colorhexa.com/00AF54.png)
|
|
||||||
static const Color green1 = Color(0xFF00AF54);
|
static const Color green1 = Color(0xFF00AF54);
|
||||||
|
|
||||||
/// ![](https://www.colorhexa.com/0F8849.png)
|
|
||||||
static const Color green2 = Color(0xFF0F8849);
|
static const Color green2 = Color(0xFF0F8849);
|
||||||
|
|
||||||
static get navBackgroundLight => white.withOpacity(0.8);
|
static get navBackgroundLight => white.withOpacity(0.8);
|
||||||
|
@ -60,5 +50,5 @@ class BrandColors {
|
||||||
static const textColor1 = black;
|
static const textColor1 = black;
|
||||||
static const textColor2 = gray1;
|
static const textColor2 = gray1;
|
||||||
static const dividerColor = gray5;
|
static const dividerColor = gray5;
|
||||||
static const warning = red;
|
static const warning = red1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,20 +23,20 @@ final ligtTheme = ThemeData(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(4)),
|
borderRadius: BorderRadius.all(Radius.circular(4)),
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
width: 1,
|
width: 1,
|
||||||
color: BrandColors.red,
|
color: BrandColors.red1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
focusedErrorBorder: OutlineInputBorder(
|
focusedErrorBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(4)),
|
borderRadius: BorderRadius.all(Radius.circular(4)),
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
width: 1,
|
width: 1,
|
||||||
color: BrandColors.red,
|
color: BrandColors.red1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
errorStyle: GoogleFonts.inter(
|
errorStyle: GoogleFonts.inter(
|
||||||
textStyle: TextStyle(
|
textStyle: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: BrandColors.red,
|
color: BrandColors.red1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
53
lib/config/hive_config.dart
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/server_details.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/user.dart';
|
||||||
|
|
||||||
|
class HiveConfig {
|
||||||
|
static Future<void> init() async {
|
||||||
|
await Hive.initFlutter();
|
||||||
|
Hive.registerAdapter(UserAdapter());
|
||||||
|
Hive.registerAdapter(HetznerServerDetailsAdapter());
|
||||||
|
Hive.registerAdapter(CloudFlareDomainAdapter());
|
||||||
|
|
||||||
|
await Hive.openBox(BNames.appSettings);
|
||||||
|
var cipher = HiveAesCipher(await getEncriptedKey());
|
||||||
|
|
||||||
|
await Hive.openBox(BNames.appConfig, encryptionCipher: cipher);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<Uint8List> getEncriptedKey() async {
|
||||||
|
final FlutterSecureStorage secureStorage = FlutterSecureStorage();
|
||||||
|
var containsEncryptionKey =
|
||||||
|
await secureStorage.containsKey(key: BNames.key);
|
||||||
|
if (!containsEncryptionKey) {
|
||||||
|
var key = Hive.generateSecureKey();
|
||||||
|
await secureStorage.write(key: BNames.key, value: base64UrlEncode(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64Url.decode(await secureStorage.read(key: BNames.key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BNames {
|
||||||
|
static String appConfig = 'appConfig';
|
||||||
|
static String isDarkModeOn = 'isDarkModeOn';
|
||||||
|
static String isOnbordingShowing = 'isOnbordingShowing';
|
||||||
|
|
||||||
|
static String appSettings = 'appSettings';
|
||||||
|
|
||||||
|
static String key = 'key';
|
||||||
|
|
||||||
|
static String domain = 'domain';
|
||||||
|
static String hetznerKey = 'hetznerKey';
|
||||||
|
static String cloudFlareKey = 'cloudFlareKey';
|
||||||
|
static String rootUser = 'rootUser';
|
||||||
|
static String server = 'server';
|
||||||
|
static String isDnsCheckedAndDkimSet = 'isDnsCheckedAndDkimSet';
|
||||||
|
static String serverInitStart = 'serverInitStart';
|
||||||
|
}
|
|
@ -18,6 +18,12 @@ final headline1Style = GoogleFonts.inter(
|
||||||
);
|
);
|
||||||
|
|
||||||
final headline2Style = GoogleFonts.inter(
|
final headline2Style = GoogleFonts.inter(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: NamedFontWeight.extraBold,
|
||||||
|
color: BrandColors.headlineColor,
|
||||||
|
);
|
||||||
|
|
||||||
|
final onboardingTitle = GoogleFonts.inter(
|
||||||
fontSize: 30,
|
fontSize: 30,
|
||||||
fontWeight: NamedFontWeight.extraBold,
|
fontWeight: NamedFontWeight.extraBold,
|
||||||
color: BrandColors.headlineColor,
|
color: BrandColors.headlineColor,
|
||||||
|
@ -40,6 +46,8 @@ final body2Style = defaultTextStyle.copyWith(
|
||||||
color: BrandColors.textColor2,
|
color: BrandColors.textColor2,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final mediumStyle = defaultTextStyle.copyWith(fontSize: 13, height: 1.53);
|
||||||
|
|
||||||
final smallStyle = defaultTextStyle.copyWith(fontSize: 11, height: 1.45);
|
final smallStyle = defaultTextStyle.copyWith(fontSize: 11, height: 1.45);
|
||||||
|
|
||||||
final linkStyle = defaultTextStyle.copyWith(color: BrandColors.blue);
|
final linkStyle = defaultTextStyle.copyWith(color: BrandColors.blue);
|
||||||
|
|
11
lib/logic/api_maps/api_map.dart
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
|
||||||
|
abstract class ApiMap {
|
||||||
|
String rootAddress;
|
||||||
|
|
||||||
|
Dio client = Dio();
|
||||||
|
|
||||||
|
void close() {
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
}
|
127
lib/logic/api_maps/cloud_flare.dart
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/api_map.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/dns_records.dart';
|
||||||
|
|
||||||
|
class CloudflareApi extends ApiMap {
|
||||||
|
CloudflareApi([String token]) {
|
||||||
|
if (token != null) {
|
||||||
|
client.options = BaseOptions(headers: {'Authorization': 'Bearer $token'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String rootAddress = 'https://api.cloudflare.com/client/v4';
|
||||||
|
|
||||||
|
Future<bool> isValid(String token) async {
|
||||||
|
var url = '$rootAddress/user/tokens/verify';
|
||||||
|
var options = Options(
|
||||||
|
headers: {'Authorization': 'Bearer $token'},
|
||||||
|
validateStatus: (status) {
|
||||||
|
return status == HttpStatus.ok || status == HttpStatus.unauthorized;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Response response = await client.get(url, options: options);
|
||||||
|
|
||||||
|
if (response.statusCode == HttpStatus.ok) {
|
||||||
|
return true;
|
||||||
|
} else if (response.statusCode == HttpStatus.unauthorized) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
throw Exception('something bad happend');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> getZoneId(String token, String domain) async {
|
||||||
|
var url = '$rootAddress/zones';
|
||||||
|
|
||||||
|
var options = Options(
|
||||||
|
headers: {'Authorization': 'Bearer $token'},
|
||||||
|
validateStatus: (status) {
|
||||||
|
return status == HttpStatus.ok || status == HttpStatus.forbidden;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Response response = await client.get(
|
||||||
|
url,
|
||||||
|
options: options,
|
||||||
|
queryParameters: {'name': domain},
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return response.data['result'][0]['id'];
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> createMultipleDnsRecords({
|
||||||
|
String ip4,
|
||||||
|
CloudFlareDomain cloudFlareDomain,
|
||||||
|
}) async {
|
||||||
|
var domainName = cloudFlareDomain.name;
|
||||||
|
var domainZoneId = cloudFlareDomain.zoneId;
|
||||||
|
|
||||||
|
var domainA = DnsRecords(type: 'A', name: domainName, content: ip4);
|
||||||
|
var apiA = DnsRecords(type: 'A', name: 'api', content: ip4);
|
||||||
|
var cloudA = DnsRecords(type: 'A', name: 'cloud', content: ip4);
|
||||||
|
var gitA = DnsRecords(type: 'A', name: 'git', content: ip4);
|
||||||
|
var meetA = DnsRecords(type: 'A', name: 'meet', content: ip4);
|
||||||
|
var passwordA = DnsRecords(type: 'A', name: 'password', content: ip4);
|
||||||
|
var socialA = DnsRecords(type: 'A', name: 'social', content: ip4);
|
||||||
|
var mx = DnsRecords(type: 'MX', name: '@', content: domainName);
|
||||||
|
|
||||||
|
var txt1 = DnsRecords(
|
||||||
|
type: 'TXT',
|
||||||
|
name: '_dmarc',
|
||||||
|
content: 'v=DMARC1; p=none',
|
||||||
|
ttl: 18000,
|
||||||
|
);
|
||||||
|
|
||||||
|
var txt2 = DnsRecords(
|
||||||
|
type: 'TXT',
|
||||||
|
name: cloudFlareDomain.name,
|
||||||
|
content: 'v=spf1 a mx ip4:$ip4 -all',
|
||||||
|
ttl: 18000,
|
||||||
|
);
|
||||||
|
|
||||||
|
var listDnsRecords = <DnsRecords>[
|
||||||
|
domainA,
|
||||||
|
apiA,
|
||||||
|
cloudA,
|
||||||
|
gitA,
|
||||||
|
meetA,
|
||||||
|
passwordA,
|
||||||
|
socialA,
|
||||||
|
mx,
|
||||||
|
txt1,
|
||||||
|
txt2
|
||||||
|
];
|
||||||
|
|
||||||
|
var allFutures = <Future>[];
|
||||||
|
|
||||||
|
for (var record in listDnsRecords) {
|
||||||
|
var url = '$rootAddress/zones/$domainZoneId/dns_records';
|
||||||
|
|
||||||
|
allFutures.add(
|
||||||
|
client.post(
|
||||||
|
url,
|
||||||
|
data: record.toJson(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Future.wait(allFutures);
|
||||||
|
}
|
||||||
|
|
||||||
|
// createSDKIM(String dkim) {
|
||||||
|
// var txt3 = DnsRecords(
|
||||||
|
// type: 'TXT',
|
||||||
|
// name: 'selector._domainkey',
|
||||||
|
// content: dkim,
|
||||||
|
// ttl: 18000,
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
}
|
64
lib/logic/api_maps/hetzner.dart
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/api_map.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/server_details.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/user.dart';
|
||||||
|
|
||||||
|
class HetznerApi extends ApiMap {
|
||||||
|
HetznerApi([String token]) {
|
||||||
|
if (token != null) {
|
||||||
|
client.options = BaseOptions(headers: {'Authorization': 'Bearer $token'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String rootAddress = 'https://api.hetzner.cloud/v1/servers';
|
||||||
|
|
||||||
|
Future<bool> isValid(String token) async {
|
||||||
|
var options = Options(
|
||||||
|
headers: {'Authorization': 'Bearer $token'},
|
||||||
|
validateStatus: (status) {
|
||||||
|
return status == HttpStatus.ok || status == HttpStatus.unauthorized;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Response response = await client.get(rootAddress, options: options);
|
||||||
|
|
||||||
|
if (response.statusCode == HttpStatus.ok) {
|
||||||
|
return true;
|
||||||
|
} else if (response.statusCode == HttpStatus.unauthorized) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
throw Exception('something bad happend');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<HetznerServerDetails> createServer({
|
||||||
|
@required User rootUser,
|
||||||
|
@required String domainName,
|
||||||
|
}) async {
|
||||||
|
var data = {
|
||||||
|
"name": "selfprivacy-server",
|
||||||
|
"server_type": "cx11",
|
||||||
|
"start_after_create": true,
|
||||||
|
"image": "ubuntu-20.04",
|
||||||
|
"ssh_keys": [],
|
||||||
|
"volumes": [],
|
||||||
|
"networks": [],
|
||||||
|
"user_data":
|
||||||
|
"#cloud-config\nruncmd:\n- curl https://git.selfprivacy.org/ilchub/selfprivacy-nixos-infect/raw/branch/master/nixos-infect | PROVIDER=hetzner NIX_CHANNEL=nixos-20.09 DOMAIN=$domainName USER=${rootUser.login} PASSWORD=${rootUser.password} HASHED_PASSWORD=${rootUser.hashPassword} bash 2>&1 | tee /tmp/infect.log \nruncmd:\n- curl https://git.selfprivacy.org/ilchub/selfprivacy-nixos-infect/raw/branch/master/nixos-infect | PROVIDER=hetzner NIX_CHANNEL=nixos-20.09 DOMAIN=$domainName USER=${rootUser.login} PASSWORD=${rootUser.password} HASHED_PASSWORD=${rootUser.hashPassword} bash 2>&1 | tee /tmp/infect.log",
|
||||||
|
};
|
||||||
|
Response response = await client.post(
|
||||||
|
rootAddress,
|
||||||
|
data: data,
|
||||||
|
);
|
||||||
|
|
||||||
|
return HetznerServerDetails(
|
||||||
|
id: response.data['server']['id'],
|
||||||
|
ip4: response.data['server']['public_net']['ipv4']['ip'],
|
||||||
|
serverInitializaionDateTime: DateTime.now(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
86
lib/logic/cubit/app_config/app_config_cubit.dart
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:selfprivacy/config/hive_config.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/cloud_flare.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/hetzner.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/server_details.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/user.dart';
|
||||||
|
|
||||||
|
part 'app_config_state.dart';
|
||||||
|
|
||||||
|
class AppConfigCubit extends Cubit<AppConfigState> {
|
||||||
|
AppConfigCubit() : super(InitialAppConfigState());
|
||||||
|
|
||||||
|
Box box = Hive.box(BNames.appConfig);
|
||||||
|
|
||||||
|
void load() {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
hetznerKey: box.get(BNames.hetznerKey),
|
||||||
|
cloudFlareKey: box.get(BNames.cloudFlareKey),
|
||||||
|
domain: box.get(BNames.domain),
|
||||||
|
rootUser: box.get(BNames.rootUser),
|
||||||
|
hetznerServer: box.get(BNames.server),
|
||||||
|
isDnsCheckedAndDkimSet: box.get(BNames.isDnsCheckedAndDkimSet),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
box.clear();
|
||||||
|
emit(InitialAppConfigState());
|
||||||
|
}
|
||||||
|
|
||||||
|
void setHetznerKey(String key) {
|
||||||
|
box.put(BNames.hetznerKey, key);
|
||||||
|
emit(state.copyWith(hetznerKey: key));
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCloudFlare(String cloudFlareKey) {
|
||||||
|
box.put(BNames.cloudFlareKey, cloudFlareKey);
|
||||||
|
emit(state.copyWith(cloudFlareKey: cloudFlareKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDomain(CloudFlareDomain domain) {
|
||||||
|
box.put(BNames.domain, domain);
|
||||||
|
emit(state.copyWith(domain: domain));
|
||||||
|
}
|
||||||
|
|
||||||
|
void setRootUser(User rootUser) {
|
||||||
|
box.put(BNames.rootUser, rootUser);
|
||||||
|
emit(state.copyWith(rootUser: rootUser));
|
||||||
|
}
|
||||||
|
|
||||||
|
void setIsDnsCheckedAndDkimSet() {
|
||||||
|
box.put(BNames.isDnsCheckedAndDkimSet, true);
|
||||||
|
emit(state.copyWith(isDnsCheckedAndDkimSet: true));
|
||||||
|
}
|
||||||
|
|
||||||
|
void createServer() async {
|
||||||
|
emit(state.copyWith(isLoading: true));
|
||||||
|
var hetznerApi = HetznerApi(state.hetznerKey);
|
||||||
|
var cloudflareApi = CloudflareApi(state.cloudFlareKey);
|
||||||
|
|
||||||
|
var serverDetails = await hetznerApi.createServer(
|
||||||
|
rootUser: state.rootUser,
|
||||||
|
domainName: state.cloudFlareDomain.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
cloudflareApi
|
||||||
|
.createMultipleDnsRecords(
|
||||||
|
ip4: serverDetails.ip4,
|
||||||
|
cloudFlareDomain: state.cloudFlareDomain,
|
||||||
|
)
|
||||||
|
.then((_) => cloudflareApi.close());
|
||||||
|
await box.put(BNames.server, serverDetails);
|
||||||
|
|
||||||
|
hetznerApi.close();
|
||||||
|
|
||||||
|
emit(state.copyWith(
|
||||||
|
isLoading: false,
|
||||||
|
hetznerServer: serverDetails,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
76
lib/logic/cubit/app_config/app_config_state.dart
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
part of 'app_config_cubit.dart';
|
||||||
|
|
||||||
|
class AppConfigState extends Equatable {
|
||||||
|
const AppConfigState({
|
||||||
|
this.hetznerKey,
|
||||||
|
this.cloudFlareKey,
|
||||||
|
this.cloudFlareDomain,
|
||||||
|
this.rootUser,
|
||||||
|
this.server,
|
||||||
|
this.isDnsCheckedAndDkimSet = false,
|
||||||
|
this.isLoading = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [
|
||||||
|
hetznerKey,
|
||||||
|
cloudFlareKey,
|
||||||
|
cloudFlareDomain,
|
||||||
|
rootUser,
|
||||||
|
server,
|
||||||
|
isDnsCheckedAndDkimSet,
|
||||||
|
isLoading,
|
||||||
|
];
|
||||||
|
|
||||||
|
final String hetznerKey;
|
||||||
|
final String cloudFlareKey;
|
||||||
|
final CloudFlareDomain cloudFlareDomain;
|
||||||
|
final User rootUser;
|
||||||
|
final HetznerServerDetails server;
|
||||||
|
final bool isDnsCheckedAndDkimSet;
|
||||||
|
|
||||||
|
final isLoading;
|
||||||
|
|
||||||
|
AppConfigState copyWith({
|
||||||
|
String hetznerKey,
|
||||||
|
String cloudFlareKey,
|
||||||
|
CloudFlareDomain domain,
|
||||||
|
User rootUser,
|
||||||
|
HetznerServerDetails hetznerServer,
|
||||||
|
bool isDnsCheckedAndDkimSet,
|
||||||
|
bool isLoading,
|
||||||
|
DateTime serverInitStart,
|
||||||
|
}) =>
|
||||||
|
AppConfigState(
|
||||||
|
hetznerKey: hetznerKey ?? this.hetznerKey,
|
||||||
|
cloudFlareKey: cloudFlareKey ?? this.cloudFlareKey,
|
||||||
|
cloudFlareDomain: domain ?? this.cloudFlareDomain,
|
||||||
|
rootUser: rootUser ?? this.rootUser,
|
||||||
|
server: hetznerServer ?? this.server,
|
||||||
|
isDnsCheckedAndDkimSet: isDnsCheckedAndDkimSet ?? this.isDnsCheckedAndDkimSet,
|
||||||
|
isLoading: isLoading ?? this.isLoading,
|
||||||
|
);
|
||||||
|
|
||||||
|
bool get isHetznerFilled => hetznerKey != null;
|
||||||
|
bool get isCloudFlareFilled => cloudFlareKey != null;
|
||||||
|
bool get isDomainFilled => cloudFlareDomain != null;
|
||||||
|
bool get isUserFilled => rootUser != null;
|
||||||
|
bool get isServerFilled => server != null;
|
||||||
|
|
||||||
|
bool get isFullyInitilized => _fulfilementList.every((el) => el);
|
||||||
|
|
||||||
|
int get progress => _fulfilementList.where((el) => el).length;
|
||||||
|
|
||||||
|
List<bool> get _fulfilementList => [
|
||||||
|
isHetznerFilled,
|
||||||
|
isCloudFlareFilled,
|
||||||
|
isDomainFilled,
|
||||||
|
isUserFilled,
|
||||||
|
isServerFilled,
|
||||||
|
isDnsCheckedAndDkimSet,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
class InitialAppConfigState extends AppConfigState {
|
||||||
|
InitialAppConfigState() : super();
|
||||||
|
}
|
|
@ -1,18 +1,44 @@
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:selfprivacy/config/hive_config.dart';
|
||||||
export 'package:provider/provider.dart';
|
export 'package:provider/provider.dart';
|
||||||
|
|
||||||
part 'app_settings_state.dart';
|
part 'app_settings_state.dart';
|
||||||
|
|
||||||
class AppSettingsCubit extends Cubit<AppSettingsState> {
|
class AppSettingsCubit extends Cubit<AppSettingsState> {
|
||||||
AppSettingsCubit({
|
AppSettingsCubit({
|
||||||
bool isDarkModeOn,
|
@required bool isDarkModeOn,
|
||||||
|
@required bool isOnbordingShowing,
|
||||||
}) : super(
|
}) : super(
|
||||||
AppSettingsState(isDarkModeOn: isDarkModeOn),
|
AppSettingsState(
|
||||||
|
isDarkModeOn: isDarkModeOn,
|
||||||
|
isOnbordingShowing: isOnbordingShowing,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
void update({@required bool isDarkModeOn}) {
|
Box box = Hive.box(BNames.appSettings);
|
||||||
emit(AppSettingsState(isDarkModeOn: isDarkModeOn));
|
|
||||||
|
void load() {
|
||||||
|
bool isDarkModeOn = box.get(BNames.isDarkModeOn);
|
||||||
|
bool isOnbordingShowing = box.get(BNames.isOnbordingShowing);
|
||||||
|
|
||||||
|
emit(state.copyWith(
|
||||||
|
isDarkModeOn: isDarkModeOn,
|
||||||
|
isOnbordingShowing: isOnbordingShowing,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateDarkMode({@required bool isDarkModeOn}) {
|
||||||
|
box.put(BNames.isDarkModeOn, isDarkModeOn);
|
||||||
|
|
||||||
|
emit(state.copyWith(isDarkModeOn: isDarkModeOn));
|
||||||
|
}
|
||||||
|
|
||||||
|
void turnOffOnboarding() {
|
||||||
|
box.put(BNames.isOnbordingShowing, false);
|
||||||
|
|
||||||
|
emit(state.copyWith(isOnbordingShowing: false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,18 @@ part of 'app_settings_cubit.dart';
|
||||||
class AppSettingsState extends Equatable {
|
class AppSettingsState extends Equatable {
|
||||||
const AppSettingsState({
|
const AppSettingsState({
|
||||||
@required this.isDarkModeOn,
|
@required this.isDarkModeOn,
|
||||||
|
@required this.isOnbordingShowing,
|
||||||
});
|
});
|
||||||
|
|
||||||
final bool isDarkModeOn;
|
final bool isDarkModeOn;
|
||||||
|
final bool isOnbordingShowing;
|
||||||
|
|
||||||
|
AppSettingsState copyWith({isDarkModeOn, isOnbordingShowing}) =>
|
||||||
|
AppSettingsState(
|
||||||
|
isDarkModeOn: isDarkModeOn ?? this.isDarkModeOn,
|
||||||
|
isOnbordingShowing: isOnbordingShowing ?? this.isOnbordingShowing,
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [isDarkModeOn];
|
List<Object> get props => [isDarkModeOn, isOnbordingShowing];
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/cloud_flare.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
|
||||||
|
|
||||||
|
class CloudFlareFormCubit extends FormCubit {
|
||||||
|
CloudflareApi apiClient = CloudflareApi();
|
||||||
|
|
||||||
|
CloudFlareFormCubit(this.initializingCubit) {
|
||||||
|
var regExp = RegExp(r"\s+|[!$%^&*()_@+|~=`{}\[\]:<>?,.\/]");
|
||||||
|
apiKey = FieldCubit(
|
||||||
|
initalValue: '',
|
||||||
|
validations: [
|
||||||
|
RequiredStringValidation('required'),
|
||||||
|
ValidationModel<String>(
|
||||||
|
(s) => regExp.hasMatch(s), 'invalid key format'),
|
||||||
|
LegnthStringValidationWithLenghShowing(40, 'length is [] shoud be 40')
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
super.setFields([apiKey]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<void> onSubmit() async {
|
||||||
|
initializingCubit.setCloudFlare(apiKey.state.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
final AppConfigCubit initializingCubit;
|
||||||
|
|
||||||
|
FieldCubit<String> apiKey;
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<bool> asyncValidation() async {
|
||||||
|
var isKeyValid = await apiClient.isValid(apiKey.state.value);
|
||||||
|
|
||||||
|
if (!isKeyValid) {
|
||||||
|
apiKey.setError('bad key');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() async {
|
||||||
|
apiClient.close();
|
||||||
|
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/cloud_flare.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
|
||||||
|
|
||||||
|
class DomainFormCubit extends FormCubit {
|
||||||
|
CloudflareApi apiClient = CloudflareApi();
|
||||||
|
|
||||||
|
DomainFormCubit(this.initializingCubit) {
|
||||||
|
var regExp =
|
||||||
|
RegExp(r"^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}");
|
||||||
|
domainName = FieldCubit(
|
||||||
|
initalValue: '',
|
||||||
|
validations: [
|
||||||
|
RequiredStringValidation('required'),
|
||||||
|
ValidationModel<String>(
|
||||||
|
(s) => !regExp.hasMatch(s), 'invalid domain format'),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
super.setFields([domainName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<void> onSubmit() async {
|
||||||
|
var domain = CloudFlareDomain(
|
||||||
|
name: domainName.state.value,
|
||||||
|
zoneId: zoneId,
|
||||||
|
);
|
||||||
|
initializingCubit.setDomain(domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
final AppConfigCubit initializingCubit;
|
||||||
|
|
||||||
|
FieldCubit<String> domainName;
|
||||||
|
String zoneId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<bool> asyncValidation() async {
|
||||||
|
var key = initializingCubit.state.cloudFlareKey;
|
||||||
|
|
||||||
|
var zoneId = await apiClient.getZoneId(key, domainName.state.value);
|
||||||
|
|
||||||
|
if (zoneId == null) {
|
||||||
|
domainName.setError('Domain not in the list');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.zoneId = zoneId;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() async {
|
||||||
|
apiClient.close();
|
||||||
|
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,22 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/initializing/initializing_cubit.dart';
|
import 'package:selfprivacy/logic/api_maps/hetzner.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||||
|
|
||||||
class HetznerFormCubit extends FormCubit {
|
class HetznerFormCubit extends FormCubit {
|
||||||
|
HetznerApi apiClient = HetznerApi();
|
||||||
|
|
||||||
HetznerFormCubit(this.initializingCubit) {
|
HetznerFormCubit(this.initializingCubit) {
|
||||||
var regExp = RegExp(r"\s+|[-!$%^&*()_@+|~=`{}\[\]:" ";<>?,.\/]");
|
var regExp = RegExp(r"\s+|[-!$%^&*()_@+|~=`{}\[\]:<>?,.\/]");
|
||||||
apiKey = FieldCubit(
|
apiKey = FieldCubit(
|
||||||
initalValue: '',
|
initalValue: '',
|
||||||
validations: [
|
validations: [
|
||||||
RequiredStringValidation('required'),
|
RequiredStringValidation('required'),
|
||||||
ValidationModel<String>(
|
ValidationModel<String>(
|
||||||
(s) => regExp.hasMatch(s), 'invalid key format'),
|
(s) => regExp.hasMatch(s), 'invalid key format'),
|
||||||
LegnthStringValidation(11, 'length is [] shoud be 11')
|
LegnthStringValidationWithLenghShowing(64, 'length is [] shoud be 64')
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -21,24 +25,28 @@ class HetznerFormCubit extends FormCubit {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<void> onSubmit() async {
|
FutureOr<void> onSubmit() async {
|
||||||
print(apiKey.state.value);
|
initializingCubit.setHetznerKey(apiKey.state.value);
|
||||||
await Future.delayed(const Duration(milliseconds: 300));
|
|
||||||
// initializingCubit.setHetznerKey(apiKey.state.value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final InitializingCubit initializingCubit;
|
final AppConfigCubit initializingCubit;
|
||||||
|
|
||||||
FieldCubit<String> apiKey;
|
FieldCubit<String> apiKey;
|
||||||
}
|
|
||||||
|
|
||||||
class LegnthStringValidation extends ValidationModel<String> {
|
|
||||||
LegnthStringValidation(int length, String errorText)
|
|
||||||
: super((n) => n.length != length, errorText);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String check(String val) {
|
FutureOr<bool> asyncValidation() async {
|
||||||
var length = val.length;
|
var isKeyValid = await apiClient.isValid(apiKey.state.value);
|
||||||
var errorMassage = this.errorMassage.replaceAll("[]", length.toString());
|
|
||||||
return test(val) ? errorMassage : null;
|
if (!isKeyValid) {
|
||||||
|
apiKey.setError('bad key');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() async {
|
||||||
|
apiClient.close();
|
||||||
|
|
||||||
|
return super.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/hetzner.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/user.dart';
|
||||||
|
|
||||||
|
class UserFormCubit extends FormCubit {
|
||||||
|
HetznerApi apiClient = HetznerApi();
|
||||||
|
|
||||||
|
UserFormCubit(this.initializingCubit) {
|
||||||
|
var userRegExp = RegExp(r"\W");
|
||||||
|
var passwordRegExp = RegExp(r"[\n\r\s]+");
|
||||||
|
|
||||||
|
userName = FieldCubit(
|
||||||
|
initalValue: '',
|
||||||
|
validations: [
|
||||||
|
RequiredStringValidation('required'),
|
||||||
|
ValidationModel<String>(
|
||||||
|
(s) => userRegExp.hasMatch(s), 'invalid format'),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
password = FieldCubit(
|
||||||
|
initalValue: '',
|
||||||
|
validations: [
|
||||||
|
RequiredStringValidation('required'),
|
||||||
|
ValidationModel<String>(
|
||||||
|
(s) => passwordRegExp.hasMatch(s), 'invalid format'),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
super.setFields([userName, password]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<void> onSubmit() async {
|
||||||
|
var user = User(
|
||||||
|
login: userName.state.value,
|
||||||
|
password: password.state.value,
|
||||||
|
);
|
||||||
|
initializingCubit.setRootUser(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
final AppConfigCubit initializingCubit;
|
||||||
|
|
||||||
|
FieldCubit<String> userName;
|
||||||
|
FieldCubit<String> password;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() async {
|
||||||
|
apiClient.close();
|
||||||
|
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
}
|
51
lib/logic/cubit/forms/user/user.dart
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/user.dart';
|
||||||
|
|
||||||
|
class CloudFlareFormCubit extends FormCubit {
|
||||||
|
CloudFlareFormCubit({
|
||||||
|
this.userCubit,
|
||||||
|
User user,
|
||||||
|
}) {
|
||||||
|
var isEdit = user != null;
|
||||||
|
|
||||||
|
var userRegExp = RegExp(r"\W");
|
||||||
|
var passwordRegExp = RegExp(r"[\n\r\s]+");
|
||||||
|
|
||||||
|
login = FieldCubit(
|
||||||
|
initalValue: isEdit ? user.login : '',
|
||||||
|
validations: [
|
||||||
|
RequiredStringValidation('required'),
|
||||||
|
ValidationModel<String>(
|
||||||
|
(s) => userRegExp.hasMatch(s), 'invalid format'),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
password = FieldCubit(
|
||||||
|
initalValue: isEdit ? user.password : '',
|
||||||
|
validations: [
|
||||||
|
RequiredStringValidation('required'),
|
||||||
|
ValidationModel<String>(
|
||||||
|
(s) => passwordRegExp.hasMatch(s), 'invalid format'),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
super.setFields([login, password]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<void> onSubmit() {
|
||||||
|
var user = User(
|
||||||
|
login: login.state.value,
|
||||||
|
password: password.state.value,
|
||||||
|
);
|
||||||
|
userCubit.add(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
FieldCubit<String> login;
|
||||||
|
FieldCubit<String> password;
|
||||||
|
|
||||||
|
UsersCubit userCubit;
|
||||||
|
}
|
13
lib/logic/cubit/forms/validations/validations.dart
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
|
|
||||||
|
class LegnthStringValidationWithLenghShowing extends ValidationModel<String> {
|
||||||
|
LegnthStringValidationWithLenghShowing(int length, String errorText)
|
||||||
|
: super((n) => n.length != length, errorText);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String check(String val) {
|
||||||
|
var length = val.length;
|
||||||
|
var errorMassage = this.errorMassage.replaceAll("[]", length.toString());
|
||||||
|
return test(val) ? errorMassage : null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,30 +0,0 @@
|
||||||
import 'package:bloc/bloc.dart';
|
|
||||||
import 'package:equatable/equatable.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/config.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/user.dart';
|
|
||||||
|
|
||||||
part 'initializing_state.dart';
|
|
||||||
|
|
||||||
class InitializingCubit extends Cubit<InitializingState> {
|
|
||||||
InitializingCubit() : super(InitialInitializingState());
|
|
||||||
|
|
||||||
void setHetznerKey(String key) {
|
|
||||||
var newCofig = state.appConfig.copyWith(hatzner: key);
|
|
||||||
emit(InitializingState(newCofig));
|
|
||||||
}
|
|
||||||
|
|
||||||
void setCloudFlare(String cloudFlareKey) {
|
|
||||||
var newCofig = state.appConfig.copyWith(cloudFlare: cloudFlareKey);
|
|
||||||
emit(InitializingState(newCofig));
|
|
||||||
}
|
|
||||||
|
|
||||||
void setDomain(String domain) {
|
|
||||||
var newCofig = state.appConfig.copyWith(domain: domain);
|
|
||||||
emit(InitializingState(newCofig));
|
|
||||||
}
|
|
||||||
|
|
||||||
void setRootUser(User rootUser) {
|
|
||||||
var newCofig = state.appConfig.copyWith(rootUser: rootUser);
|
|
||||||
emit(InitializingState(newCofig));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
part of 'initializing_cubit.dart';
|
|
||||||
|
|
||||||
class InitializingState extends Equatable {
|
|
||||||
const InitializingState(this.appConfig);
|
|
||||||
|
|
||||||
final AppConfig appConfig;
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [appConfig];
|
|
||||||
|
|
||||||
bool get isHatznerFilled => appConfig.hatzner != null;
|
|
||||||
bool get isCloudFlareFilled => appConfig.cloudFlare != null;
|
|
||||||
bool get isDomainFilled => appConfig.domain != null;
|
|
||||||
bool get isUserFilled => appConfig.rootUser != null;
|
|
||||||
|
|
||||||
bool get isFullyInitilized => _fulfilementList.every((el) => el);
|
|
||||||
|
|
||||||
int get progress => _fulfilementList.where((el) => el).length;
|
|
||||||
|
|
||||||
List<bool> get _fulfilementList =>
|
|
||||||
[isHatznerFilled, isCloudFlareFilled, isDomainFilled, isUserFilled];
|
|
||||||
}
|
|
||||||
|
|
||||||
class InitialInitializingState extends InitializingState {
|
|
||||||
InitialInitializingState() : super(AppConfig.empty());
|
|
||||||
}
|
|
|
@ -1,13 +1,12 @@
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:selfprivacy/logic/models/user.dart';
|
import 'package:selfprivacy/logic/models/user.dart';
|
||||||
import 'package:selfprivacy/utils/password_generator.dart';
|
|
||||||
export 'package:provider/provider.dart';
|
export 'package:provider/provider.dart';
|
||||||
|
|
||||||
part 'users_state.dart';
|
part 'users_state.dart';
|
||||||
|
|
||||||
class UsersCubit extends Cubit<UsersState> {
|
class UsersCubit extends Cubit<UsersState> {
|
||||||
UsersCubit() : super(UsersState(initMockUsers));
|
UsersCubit() : super(UsersState([]));
|
||||||
|
|
||||||
void add(User user) {
|
void add(User user) {
|
||||||
var users = state.users;
|
var users = state.users;
|
||||||
|
@ -24,20 +23,20 @@ class UsersCubit extends Cubit<UsersState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final initMockUsers = <User>[
|
// final initMockUsers = <User>[
|
||||||
User(login: 'Heartbreaking.Goose', password: genPass()),
|
// User(login: 'Heartbreaking.Goose', password: genPass()),
|
||||||
User(login: 'Alma.lawson', password: genPass()),
|
// User(login: 'Alma.lawson', password: genPass()),
|
||||||
User(login: 'Bee.gees', password: genPass()),
|
// User(login: 'Bee.gees', password: genPass()),
|
||||||
User(login: 'Bim.jennings', password: genPass()),
|
// User(login: 'Bim.jennings', password: genPass()),
|
||||||
User(login: 'Debra.holt', password: genPass()),
|
// User(login: 'Debra.holt', password: genPass()),
|
||||||
User(login: 'Georgia.young', password: genPass()),
|
// User(login: 'Georgia.young', password: genPass()),
|
||||||
User(login: 'Kenzi.lawson', password: genPass()),
|
// User(login: 'Kenzi.lawson', password: genPass()),
|
||||||
User(login: 'Le.jennings', password: genPass()),
|
// User(login: 'Le.jennings', password: genPass()),
|
||||||
User(login: 'Kirill.Zh', password: genPass()),
|
// User(login: 'Kirill.Zh', password: genPass()),
|
||||||
User(login: 'Tina.Bolton', password: genPass()),
|
// User(login: 'Tina.Bolton', password: genPass()),
|
||||||
User(login: 'Rebekah.Lynn', password: genPass()),
|
// User(login: 'Rebekah.Lynn', password: genPass()),
|
||||||
User(login: 'Aleena.Armstrong', password: genPass()),
|
// User(login: 'Aleena.Armstrong', password: genPass()),
|
||||||
User(login: 'Rosemary.Williams', password: genPass()),
|
// User(login: 'Rosemary.Williams', password: genPass()),
|
||||||
User(login: 'Sullivan.Nixon', password: genPass()),
|
// User(login: 'Sullivan.Nixon', password: genPass()),
|
||||||
User(login: 'Aleena.Armstrong', password: genPass()),
|
// User(login: 'Aleena.Armstrong', password: genPass()),
|
||||||
];
|
// ];
|
||||||
|
|
|
@ -7,4 +7,6 @@ class UsersState extends Equatable {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => users;
|
List<Object> get props => users;
|
||||||
|
|
||||||
|
bool get isEmpty => users.isEmpty;
|
||||||
}
|
}
|
||||||
|
|
19
lib/logic/models/cloudflare_domain.dart
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
|
||||||
|
part 'cloudflare_domain.g.dart';
|
||||||
|
|
||||||
|
@HiveType(typeId: 3)
|
||||||
|
class CloudFlareDomain {
|
||||||
|
CloudFlareDomain({this.name, this.zoneId});
|
||||||
|
|
||||||
|
@HiveField(0)
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
@HiveField(1)
|
||||||
|
final String zoneId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return '$name: $zoneId';
|
||||||
|
}
|
||||||
|
}
|
44
lib/logic/models/cloudflare_domain.g.dart
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'cloudflare_domain.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// TypeAdapterGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
class CloudFlareDomainAdapter extends TypeAdapter<CloudFlareDomain> {
|
||||||
|
@override
|
||||||
|
final int typeId = 3;
|
||||||
|
|
||||||
|
@override
|
||||||
|
CloudFlareDomain read(BinaryReader reader) {
|
||||||
|
final numOfFields = reader.readByte();
|
||||||
|
final fields = <int, dynamic>{
|
||||||
|
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||||
|
};
|
||||||
|
return CloudFlareDomain(
|
||||||
|
name: fields[0] as String,
|
||||||
|
zoneId: fields[1] as String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void write(BinaryWriter writer, CloudFlareDomain obj) {
|
||||||
|
writer
|
||||||
|
..writeByte(2)
|
||||||
|
..writeByte(0)
|
||||||
|
..write(obj.name)
|
||||||
|
..writeByte(1)
|
||||||
|
..write(obj.zoneId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => typeId.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is CloudFlareDomainAdapter &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
typeId == other.typeId;
|
||||||
|
}
|
|
@ -1,36 +0,0 @@
|
||||||
import 'package:equatable/equatable.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/user.dart';
|
|
||||||
|
|
||||||
class AppConfig extends Equatable {
|
|
||||||
const AppConfig({
|
|
||||||
this.hatzner,
|
|
||||||
this.cloudFlare,
|
|
||||||
this.domain,
|
|
||||||
this.rootUser,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String hatzner;
|
|
||||||
final String cloudFlare;
|
|
||||||
final String domain;
|
|
||||||
final User rootUser;
|
|
||||||
|
|
||||||
factory AppConfig.empty() {
|
|
||||||
return AppConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
AppConfig copyWith({
|
|
||||||
hatzner,
|
|
||||||
cloudFlare,
|
|
||||||
domain,
|
|
||||||
rootUser,
|
|
||||||
}) =>
|
|
||||||
AppConfig(
|
|
||||||
hatzner: hatzner ?? this.hatzner,
|
|
||||||
cloudFlare: cloudFlare ?? this.cloudFlare,
|
|
||||||
domain: domain ?? this.domain,
|
|
||||||
rootUser: rootUser ?? this.rootUser,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [hatzner, cloudFlare, domain, rootUser];
|
|
||||||
}
|
|
25
lib/logic/models/dns_records.dart
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
part 'dns_records.g.dart';
|
||||||
|
|
||||||
|
@JsonSerializable(createToJson: true, createFactory: false)
|
||||||
|
class DnsRecords {
|
||||||
|
DnsRecords({
|
||||||
|
@required this.type,
|
||||||
|
@required this.name,
|
||||||
|
@required this.content,
|
||||||
|
this.ttl = 3600,
|
||||||
|
this.priority = 10,
|
||||||
|
this.proxied = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String type;
|
||||||
|
final String name;
|
||||||
|
final String content;
|
||||||
|
final int ttl;
|
||||||
|
final int priority;
|
||||||
|
final bool proxied;
|
||||||
|
|
||||||
|
toJson() => _$DnsRecordsToJson(this);
|
||||||
|
}
|
17
lib/logic/models/dns_records.g.dart
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'dns_records.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
Map<String, dynamic> _$DnsRecordsToJson(DnsRecords instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'type': instance.type,
|
||||||
|
'name': instance.name,
|
||||||
|
'content': instance.content,
|
||||||
|
'ttl': instance.ttl,
|
||||||
|
'priority': instance.priority,
|
||||||
|
'proxied': instance.proxied,
|
||||||
|
};
|
24
lib/logic/models/server_details.dart
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
|
||||||
|
part 'server_details.g.dart';
|
||||||
|
|
||||||
|
@HiveType(typeId: 2)
|
||||||
|
class HetznerServerDetails {
|
||||||
|
HetznerServerDetails({
|
||||||
|
@required this.ip4,
|
||||||
|
@required this.id,
|
||||||
|
@required this.serverInitializaionDateTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
@HiveField(0)
|
||||||
|
final String ip4;
|
||||||
|
|
||||||
|
@HiveField(1)
|
||||||
|
final int id;
|
||||||
|
|
||||||
|
@HiveField(2)
|
||||||
|
final DateTime serverInitializaionDateTime;
|
||||||
|
|
||||||
|
String toString() => id.toString();
|
||||||
|
}
|
47
lib/logic/models/server_details.g.dart
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'server_details.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// TypeAdapterGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
class HetznerServerDetailsAdapter extends TypeAdapter<HetznerServerDetails> {
|
||||||
|
@override
|
||||||
|
final int typeId = 2;
|
||||||
|
|
||||||
|
@override
|
||||||
|
HetznerServerDetails read(BinaryReader reader) {
|
||||||
|
final numOfFields = reader.readByte();
|
||||||
|
final fields = <int, dynamic>{
|
||||||
|
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||||
|
};
|
||||||
|
return HetznerServerDetails(
|
||||||
|
ip4: fields[0] as String,
|
||||||
|
id: fields[1] as int,
|
||||||
|
serverInitializaionDateTime: fields[2] as DateTime,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void write(BinaryWriter writer, HetznerServerDetails obj) {
|
||||||
|
writer
|
||||||
|
..writeByte(3)
|
||||||
|
..writeByte(0)
|
||||||
|
..write(obj.ip4)
|
||||||
|
..writeByte(1)
|
||||||
|
..write(obj.id)
|
||||||
|
..writeByte(2)
|
||||||
|
..write(obj.serverInitializaionDateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => typeId.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is HetznerServerDetailsAdapter &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
typeId == other.typeId;
|
||||||
|
}
|
|
@ -3,18 +3,32 @@ import 'dart:ui';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:selfprivacy/utils/color_utils.dart';
|
import 'package:selfprivacy/utils/color_utils.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:selfprivacy/utils/crypto.dart';
|
||||||
|
|
||||||
|
part 'user.g.dart';
|
||||||
|
|
||||||
|
@HiveType(typeId: 1)
|
||||||
class User extends Equatable {
|
class User extends Equatable {
|
||||||
User({
|
User({
|
||||||
@required this.login,
|
@required this.login,
|
||||||
@required this.password,
|
@required this.password,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@HiveField(0)
|
||||||
final String login;
|
final String login;
|
||||||
|
|
||||||
|
@HiveField(1)
|
||||||
final String password;
|
final String password;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [login, password];
|
List<Object> get props => [login, password];
|
||||||
|
|
||||||
Color get color => stringToColor(login);
|
Color get color => stringToColor(login);
|
||||||
|
|
||||||
|
String get hashPassword => convertToSha512Hash(password);
|
||||||
|
|
||||||
|
String toString() {
|
||||||
|
return login;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
44
lib/logic/models/user.g.dart
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'user.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// TypeAdapterGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
class UserAdapter extends TypeAdapter<User> {
|
||||||
|
@override
|
||||||
|
final int typeId = 1;
|
||||||
|
|
||||||
|
@override
|
||||||
|
User read(BinaryReader reader) {
|
||||||
|
final numOfFields = reader.readByte();
|
||||||
|
final fields = <int, dynamic>{
|
||||||
|
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||||
|
};
|
||||||
|
return User(
|
||||||
|
login: fields[0] as String,
|
||||||
|
password: fields[1] as String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void write(BinaryWriter writer, User obj) {
|
||||||
|
writer
|
||||||
|
..writeByte(2)
|
||||||
|
..writeByte(0)
|
||||||
|
..write(obj.login)
|
||||||
|
..writeByte(1)
|
||||||
|
..write(obj.password);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => typeId.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is UserAdapter &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
typeId == other.typeId;
|
||||||
|
}
|
|
@ -1,15 +1,18 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:selfprivacy/config/hive_config.dart';
|
||||||
|
import 'package:selfprivacy/ui/pages/initializing/initializing.dart';
|
||||||
import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart';
|
import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:selfprivacy/ui/pages/rootRoute.dart';
|
import 'package:selfprivacy/ui/pages/rootRoute.dart';
|
||||||
|
|
||||||
import 'config/bloc_config.dart';
|
import 'config/bloc_config.dart';
|
||||||
import 'config/brand_theme.dart';
|
import 'config/brand_theme.dart';
|
||||||
import 'config/localization.dart';
|
import 'config/localization.dart';
|
||||||
import 'logic/cubit/app_settings/app_settings_cubit.dart';
|
import 'logic/cubit/app_settings/app_settings_cubit.dart';
|
||||||
|
|
||||||
void main() {
|
void main() async {
|
||||||
|
await HiveConfig.init();
|
||||||
|
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
|
@ -21,11 +24,11 @@ void main() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var _showOnbording = true;
|
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
var appSettings = context.watch<AppSettingsCubit>().state;
|
||||||
|
|
||||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||||
value: SystemUiOverlayStyle.light, // Manually changnig appbar color
|
value: SystemUiOverlayStyle.light, // Manually changnig appbar color
|
||||||
child: MaterialApp(
|
child: MaterialApp(
|
||||||
|
@ -34,10 +37,10 @@ class MyApp extends StatelessWidget {
|
||||||
locale: context.locale,
|
locale: context.locale,
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
title: 'SelfPrivacy',
|
title: 'SelfPrivacy',
|
||||||
theme: context.watch<AppSettingsCubit>().state.isDarkModeOn
|
theme: appSettings.isDarkModeOn ? darkTheme : ligtTheme,
|
||||||
? darkTheme
|
home: appSettings.isOnbordingShowing
|
||||||
: ligtTheme,
|
? OnboardingPage(nextPage: InitializingPage())
|
||||||
home: _showOnbording ? OnboardingPage() : RootPage(),
|
: RootPage(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,18 @@
|
||||||
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/ui/components/brand_text/brand_text.dart';
|
|
||||||
import 'package:selfprivacy/utils/extensions/elevation_extension.dart';
|
import 'package:selfprivacy/utils/extensions/elevation_extension.dart';
|
||||||
|
|
||||||
class BrandCard extends StatelessWidget {
|
class BrandCard extends StatelessWidget {
|
||||||
const BrandCard({
|
const BrandCard({
|
||||||
Key key,
|
Key key,
|
||||||
this.child,
|
this.child,
|
||||||
this.isBlocked = false,
|
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final bool isBlocked;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget res = Container(
|
return Container(
|
||||||
margin: EdgeInsets.only(bottom: 30),
|
margin: EdgeInsets.only(bottom: 30),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).brightness == Brightness.dark
|
color: Theme.of(context).brightness == Brightness.dark
|
||||||
|
@ -29,44 +26,5 @@ class BrandCard extends StatelessWidget {
|
||||||
),
|
),
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isBlocked) {
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
return IgnorePointer(
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
ColorFiltered(
|
|
||||||
colorFilter: ColorFilter.mode(
|
|
||||||
Colors.white,
|
|
||||||
BlendMode.saturation,
|
|
||||||
),
|
|
||||||
child: res,
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
child: Container(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
color: Colors.white.withOpacity(0.8),
|
|
||||||
),
|
|
||||||
padding: EdgeInsets.all(10),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
BrandText.h3('Blocked'),
|
|
||||||
BrandText.h4('finish initializing first')
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,9 @@ class _BrandTabBarState extends State<BrandTabBar> {
|
||||||
var acitivColor = Theme.of(context).brightness == Brightness.dark
|
var acitivColor = Theme.of(context).brightness == Brightness.dark
|
||||||
? BrandColors.white
|
? BrandColors.white
|
||||||
: BrandColors.black;
|
: BrandColors.black;
|
||||||
var color = currentIndex == index ? acitivColor : BrandColors.inactive;
|
|
||||||
|
var isActive = currentIndex == index;
|
||||||
|
var color = isActive ? acitivColor : BrandColors.inactive;
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () => widget.controller.animateTo(index),
|
onTap: () => widget.controller.animateTo(index),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
@ -75,7 +77,22 @@ class _BrandTabBarState extends State<BrandTabBar> {
|
||||||
children: [
|
children: [
|
||||||
Icon(iconData, color: color),
|
Icon(iconData, color: color),
|
||||||
SizedBox(height: 3),
|
SizedBox(height: 3),
|
||||||
Text(label, style: TextStyle(fontSize: 9, color: color))
|
Row(
|
||||||
|
children: [
|
||||||
|
if (isActive) ...[
|
||||||
|
Container(
|
||||||
|
height: 5,
|
||||||
|
width: 5,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: BrandColors.red2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 5),
|
||||||
|
],
|
||||||
|
Text(label, style: TextStyle(fontSize: 9, color: color)),
|
||||||
|
],
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -8,24 +8,27 @@ enum TextType {
|
||||||
h4, // caption
|
h4, // caption
|
||||||
body1, // normal
|
body1, // normal
|
||||||
body2, // with opacity
|
body2, // with opacity
|
||||||
small
|
medium,
|
||||||
|
small,
|
||||||
|
onboardingTitle
|
||||||
}
|
}
|
||||||
|
|
||||||
class BrandText extends StatelessWidget {
|
class BrandText extends StatelessWidget {
|
||||||
const BrandText(
|
const BrandText(this.text,
|
||||||
this.text, {
|
{Key key,
|
||||||
Key key,
|
this.style,
|
||||||
this.style,
|
@required this.type,
|
||||||
@required this.type,
|
this.overflow,
|
||||||
this.overflow,
|
this.softWrap,
|
||||||
this.softWrap,
|
this.textAlign})
|
||||||
}) : super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
final String text;
|
final String text;
|
||||||
final TextStyle style;
|
final TextStyle style;
|
||||||
final TextType type;
|
final TextType type;
|
||||||
final TextOverflow overflow;
|
final TextOverflow overflow;
|
||||||
final bool softWrap;
|
final bool softWrap;
|
||||||
|
final TextAlign textAlign;
|
||||||
|
|
||||||
factory BrandText.h1(
|
factory BrandText.h1(
|
||||||
String text, {
|
String text, {
|
||||||
|
@ -38,6 +41,13 @@ class BrandText extends StatelessWidget {
|
||||||
type: TextType.h1,
|
type: TextType.h1,
|
||||||
style: style,
|
style: style,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
factory BrandText.onboardingTitle(String text, {TextStyle style}) =>
|
||||||
|
BrandText(
|
||||||
|
text,
|
||||||
|
type: TextType.onboardingTitle,
|
||||||
|
style: style,
|
||||||
|
);
|
||||||
factory BrandText.h2(String text, {TextStyle style}) => BrandText(
|
factory BrandText.h2(String text, {TextStyle style}) => BrandText(
|
||||||
text,
|
text,
|
||||||
type: TextType.h2,
|
type: TextType.h2,
|
||||||
|
@ -63,6 +73,10 @@ class BrandText extends StatelessWidget {
|
||||||
type: TextType.body2,
|
type: TextType.body2,
|
||||||
style: style,
|
style: style,
|
||||||
);
|
);
|
||||||
|
factory BrandText.medium(String text,
|
||||||
|
{TextStyle style, TextAlign textAlign}) =>
|
||||||
|
BrandText(text,
|
||||||
|
type: TextType.medium, style: style, textAlign: textAlign);
|
||||||
factory BrandText.small(String text, {TextStyle style}) => BrandText(
|
factory BrandText.small(String text, {TextStyle style}) => BrandText(
|
||||||
text,
|
text,
|
||||||
type: TextType.small,
|
type: TextType.small,
|
||||||
|
@ -105,6 +119,15 @@ class BrandText extends StatelessWidget {
|
||||||
case TextType.small:
|
case TextType.small:
|
||||||
style = isDark ? smallStyle.copyWith(color: Colors.white) : smallStyle;
|
style = isDark ? smallStyle.copyWith(color: Colors.white) : smallStyle;
|
||||||
break;
|
break;
|
||||||
|
case TextType.onboardingTitle:
|
||||||
|
style = isDark
|
||||||
|
? onboardingTitle.copyWith(color: Colors.white)
|
||||||
|
: onboardingTitle;
|
||||||
|
break;
|
||||||
|
case TextType.medium:
|
||||||
|
style =
|
||||||
|
isDark ? mediumStyle.copyWith(color: Colors.white) : mediumStyle;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (this.style != null) {
|
if (this.style != null) {
|
||||||
style = style.merge(this.style);
|
style = style.merge(this.style);
|
||||||
|
@ -114,6 +137,7 @@ class BrandText extends StatelessWidget {
|
||||||
style: style,
|
style: style,
|
||||||
overflow: overflow,
|
overflow: overflow,
|
||||||
softWrap: softWrap,
|
softWrap: softWrap,
|
||||||
|
textAlign: textAlign,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/brand_colors.dart';
|
import 'package:selfprivacy/config/brand_colors.dart';
|
||||||
import 'package:selfprivacy/logic/models/state_types.dart';
|
import 'package:selfprivacy/logic/models/state_types.dart';
|
||||||
|
|
||||||
class IconStatusMaks extends StatelessWidget {
|
class IconStatusMask extends StatelessWidget {
|
||||||
IconStatusMaks({this.child, this.status});
|
IconStatusMask({this.child, this.status});
|
||||||
final Icon child;
|
final Icon child;
|
||||||
|
|
||||||
final StateType status;
|
final StateType status;
|
||||||
|
|
19
lib/ui/components/not_ready_card/not_ready_card.dart
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/config/brand_colors.dart';
|
||||||
|
|
||||||
|
class NotReadyCard extends StatelessWidget {
|
||||||
|
const NotReadyCard({Key key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 15, vertical: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(15), color: BrandColors.gray6),
|
||||||
|
child: Text(
|
||||||
|
'Завершите настройку приложения используя "Мастер подключения" для продолжения работы',
|
||||||
|
style: TextStyle(color: BrandColors.white),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,54 +24,37 @@ class _ProgressBarState extends State<ProgressBar> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
double progress = 1 / widget.steps.length * (widget.activeIndex + 0.3);
|
double progress = 1 / widget.steps.length * (widget.activeIndex + 0.3);
|
||||||
|
var isDark = context.watch<AppSettingsCubit>().state.isDarkModeOn;
|
||||||
|
var style = isDark ? progressTextStyleDark : progressTextStyleLight;
|
||||||
|
|
||||||
|
var allSteps = widget.steps.asMap().map(
|
||||||
|
(i, step) {
|
||||||
|
var value = _stepTitle(index: i, style: style, step: step);
|
||||||
|
return MapEntry(i, value);
|
||||||
|
},
|
||||||
|
).values;
|
||||||
|
|
||||||
|
List<Widget> odd = [];
|
||||||
|
List<Widget> even = [];
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
for (var step in allSteps) {
|
||||||
|
if (i.isEven) {
|
||||||
|
even.add(step);
|
||||||
|
} else {
|
||||||
|
odd.add(step);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
even.add(Spacer());
|
||||||
|
odd.insert(0, Spacer());
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
BrandText.h4('Progress'),
|
BrandText.h2('Progress'),
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
Row(
|
Row(children: even),
|
||||||
children: widget.steps
|
|
||||||
.asMap()
|
|
||||||
.map(
|
|
||||||
(i, step) {
|
|
||||||
var isActive = i == widget.activeIndex;
|
|
||||||
var checked = i < widget.activeIndex;
|
|
||||||
|
|
||||||
var isDark =
|
|
||||||
context.watch<AppSettingsCubit>().state.isDarkModeOn;
|
|
||||||
var style =
|
|
||||||
isDark ? progressTextStyleDark : progressTextStyleLight;
|
|
||||||
|
|
||||||
style = isActive
|
|
||||||
? style.copyWith(fontWeight: FontWeight.w700)
|
|
||||||
: style;
|
|
||||||
return MapEntry(
|
|
||||||
i,
|
|
||||||
Expanded(
|
|
||||||
child: RichText(
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
text: TextSpan(
|
|
||||||
style: progressTextStyleLight,
|
|
||||||
children: [
|
|
||||||
checked
|
|
||||||
? WidgetSpan(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
bottom: 1, right: 2),
|
|
||||||
child: Icon(BrandIcons.check, size: 14),
|
|
||||||
))
|
|
||||||
: TextSpan(text: '${i + 1}.', style: style),
|
|
||||||
TextSpan(text: step, style: style)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.values
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
SizedBox(height: 3),
|
SizedBox(height: 3),
|
||||||
Container(
|
Container(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
|
@ -98,8 +81,42 @@ class _ProgressBarState extends State<ProgressBar> {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
|
SizedBox(height: 3),
|
||||||
|
Row(
|
||||||
|
children: odd,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Expanded _stepTitle({
|
||||||
|
int index,
|
||||||
|
TextStyle style,
|
||||||
|
String step,
|
||||||
|
}) {
|
||||||
|
var isActive = index == widget.activeIndex;
|
||||||
|
var checked = index < widget.activeIndex;
|
||||||
|
|
||||||
|
style = isActive ? style.copyWith(fontWeight: FontWeight.w700) : style;
|
||||||
|
return Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: RichText(
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
text: TextSpan(
|
||||||
|
style: progressTextStyleLight,
|
||||||
|
children: [
|
||||||
|
checked
|
||||||
|
? WidgetSpan(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 1, right: 2),
|
||||||
|
child: Icon(BrandIcons.check, size: 14),
|
||||||
|
))
|
||||||
|
: TextSpan(text: '${index + 1}.', style: style),
|
||||||
|
TextSpan(text: step, style: style)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,12 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:selfprivacy/config/brand_theme.dart';
|
import 'package:selfprivacy/config/brand_theme.dart';
|
||||||
import 'package:selfprivacy/config/text_themes.dart';
|
import 'package:selfprivacy/config/text_themes.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/forms/initializing/cloudflare_form_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/forms/initializing/domain_form_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/forms/initializing/hetzner_form_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/forms/initializing/hetzner_form_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/initializing/initializing_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/forms/initializing/user_form_cubit.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/user.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.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_card/brand_card.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';
|
||||||
|
@ -16,96 +18,71 @@ import 'package:selfprivacy/ui/components/progress_bar/progress_bar.dart';
|
||||||
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 InitializingPage extends StatefulWidget {
|
class InitializingPage extends StatelessWidget {
|
||||||
const InitializingPage({Key key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
_InitializingPageState createState() => _InitializingPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _InitializingPageState extends State<InitializingPage> {
|
|
||||||
PageController pageController = PageController(viewportFraction: 1);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
pageController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var cubit = context.watch<InitializingCubit>();
|
var cubit = context.watch<AppConfigCubit>();
|
||||||
|
var actualPage = [
|
||||||
return SafeArea(
|
_stepHetzner(cubit),
|
||||||
child: Scaffold(
|
_stepCloudflare(cubit),
|
||||||
body: ListView(
|
_stepDomain(cubit),
|
||||||
children: [
|
_stepUser(cubit),
|
||||||
Padding(
|
_stepServer(cubit),
|
||||||
padding: brandPagePadding1,
|
Container(child: Text('Everythigng is initialized'))
|
||||||
child: Column(
|
][cubit.state.progress];
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
return BlocListener<AppConfigCubit, AppConfigState>(
|
||||||
children: [
|
listener: (context, state) {
|
||||||
BrandText.h4('Начало'),
|
if (state.isFullyInitilized) {
|
||||||
BrandText.h1('SelfPrivacy'),
|
Navigator.of(context).pushReplacement(materialRoute(RootPage()));
|
||||||
SizedBox(
|
}
|
||||||
height: 10,
|
},
|
||||||
),
|
child: SafeArea(
|
||||||
RichText(
|
child: Scaffold(
|
||||||
text: TextSpan(
|
body: ListView(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
Padding(
|
||||||
text:
|
padding: brandPagePadding1,
|
||||||
'Для устойчивости и приватности требует много учёток. Полная инструкция на ',
|
child: Column(
|
||||||
style: body2Style,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
),
|
children: [
|
||||||
BrandSpanButton.link(
|
ProgressBar(
|
||||||
text:
|
steps: [
|
||||||
'https://selfprivacy.org/posts/getting_started/',
|
'Hetzner',
|
||||||
urlString:
|
'CloudFlare',
|
||||||
'https://selfprivacy.org/posts/getting_started/',
|
'Domain',
|
||||||
),
|
'User',
|
||||||
|
'Server',
|
||||||
|
'Check'
|
||||||
],
|
],
|
||||||
|
activeIndex: cubit.state.progress,
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
SizedBox(height: 10),
|
),
|
||||||
ProgressBar(
|
|
||||||
steps: ['Server', 'DNS', 'Domain', 'User'],
|
|
||||||
// progress: cubit.state.progress,
|
|
||||||
activeIndex: cubit.state.progress,
|
|
||||||
),
|
|
||||||
SizedBox(height: 20),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
_addCard(
|
||||||
Container(
|
AnimatedSwitcher(
|
||||||
height: 500,
|
duration: Duration(milliseconds: 300),
|
||||||
child: PageView(
|
child: actualPage,
|
||||||
// physics: NeverScrollableScrollPhysics(),
|
),
|
||||||
controller: pageController,
|
|
||||||
children: [
|
|
||||||
_addCard(_stepOne(cubit)),
|
|
||||||
_addCard(_stepTwo(cubit)),
|
|
||||||
_addCard(_stepThree(cubit)),
|
|
||||||
_addCard(_stepFour(cubit)),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
BrandButton.text(
|
||||||
BrandButton.text(title: 'Настрою потом', onPressed: _goToMainPage),
|
title:
|
||||||
SizedBox(height: 30),
|
cubit.state.isFullyInitilized ? 'Close' : 'Настрою потом',
|
||||||
],
|
onPressed: () {
|
||||||
|
Navigator.of(context).pushAndRemoveUntil(
|
||||||
|
materialRoute(RootPage()),
|
||||||
|
(predicate) => predicate == null,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
SizedBox(height: 30),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _goToMainPage() {
|
Widget _stepHetzner(AppConfigCubit initializingCubit) {
|
||||||
Navigator.of(context).pushAndRemoveUntil(
|
|
||||||
materialRoute(RootPage()),
|
|
||||||
(predicate) => predicate == null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _stepOne(InitializingCubit initializingCubit) {
|
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => HetznerFormCubit(initializingCubit),
|
create: (context) => HetznerFormCubit(initializingCubit),
|
||||||
child: Builder(builder: (context) {
|
child: Builder(builder: (context) {
|
||||||
|
@ -124,19 +101,17 @@ class _InitializingPageState extends State<InitializingPage> {
|
||||||
CubitFormTextField(
|
CubitFormTextField(
|
||||||
formFieldCubit: formCubit.apiKey,
|
formFieldCubit: formCubit.apiKey,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
scrollPadding: EdgeInsets.only(bottom: 70),
|
scrollPadding: EdgeInsets.only(bottom: 70),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Hetzner API Token',
|
hintText: 'Hetzner API Token',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 20),
|
Spacer(),
|
||||||
BrandButton.rised(
|
BrandButton.rised(
|
||||||
onPressed:
|
onPressed:
|
||||||
formCubit.state.isSubmitting ? null : formCubit.trySubmit,
|
formCubit.state.isSubmitting ? null : formCubit.trySubmit,
|
||||||
title: 'Подключить',
|
title: 'Подключить',
|
||||||
),
|
),
|
||||||
Spacer(),
|
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
BrandButton.text(
|
BrandButton.text(
|
||||||
onPressed: () => _showModal(context, _HowHetzner()),
|
onPressed: () => _showModal(context, _HowHetzner()),
|
||||||
|
@ -159,110 +134,161 @@ class _InitializingPageState extends State<InitializingPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _stepTwo(InitializingCubit cubit) {
|
Widget _stepCloudflare(AppConfigCubit initializingCubit) {
|
||||||
return Column(
|
return BlocProvider(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
create: (context) => CloudFlareFormCubit(initializingCubit),
|
||||||
children: [
|
child: Builder(builder: (context) {
|
||||||
Image.asset('assets/images/logos/cloudflare.png'),
|
var formCubit = context.watch<CloudFlareFormCubit>();
|
||||||
BrandText.h2('Подключите CloudFlare DNS'),
|
|
||||||
SizedBox(height: 10),
|
return Column(
|
||||||
BrandText.body2('Для управления DNS вашего домена'),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Expanded(
|
children: [
|
||||||
child: _MockForm(
|
Spacer(),
|
||||||
hintText: 'CloudFlare API Token',
|
Image.asset('assets/images/logos/cloudflare.png'),
|
||||||
length: 64,
|
BrandText.h2('Подключите CloudFlare'),
|
||||||
onPressed: () {
|
SizedBox(height: 10),
|
||||||
cubit.setCloudFlare('key');
|
BrandText.body2('Для управления DNS вашего домена'),
|
||||||
pageController.animateToPage(
|
Spacer(),
|
||||||
2,
|
CubitFormTextField(
|
||||||
curve: Curves.easeIn,
|
formFieldCubit: formCubit.apiKey,
|
||||||
duration: Duration(milliseconds: 200),
|
textAlign: TextAlign.center,
|
||||||
);
|
scrollPadding: EdgeInsets.only(bottom: 70),
|
||||||
},
|
decoration: InputDecoration(
|
||||||
),
|
hintText: 'CloudFlare API Token',
|
||||||
),
|
),
|
||||||
SizedBox(height: 20),
|
),
|
||||||
BrandButton.text(
|
Spacer(),
|
||||||
onPressed: () {},
|
BrandButton.rised(
|
||||||
title: 'Как получить API Token',
|
onPressed:
|
||||||
),
|
formCubit.state.isSubmitting ? null : formCubit.trySubmit,
|
||||||
],
|
title: 'Подключить',
|
||||||
|
),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
BrandButton.text(
|
||||||
|
onPressed: () {},
|
||||||
|
title: 'Как получить API Token',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _stepThree(InitializingCubit cubit) {
|
Widget _stepDomain(AppConfigCubit initializingCubit) {
|
||||||
return Column(
|
return BlocProvider(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
create: (context) => DomainFormCubit(initializingCubit),
|
||||||
children: [
|
child: Builder(builder: (context) {
|
||||||
SizedBox(height: 10),
|
var formCubit = context.watch<DomainFormCubit>();
|
||||||
BrandText.h2('Введите домен:'),
|
|
||||||
Expanded(
|
return Column(
|
||||||
child: _MockForm(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
hintText: 'домен',
|
children: [
|
||||||
length: 10,
|
Spacer(),
|
||||||
onPressed: () {
|
BrandText.h2('Введите домен:'),
|
||||||
cubit.setDomain('domain');
|
SizedBox(height: 10),
|
||||||
pageController.animateToPage(
|
CubitFormTextField(
|
||||||
3,
|
keyboardType: TextInputType.emailAddress,
|
||||||
curve: Curves.easeIn,
|
formFieldCubit: formCubit.domainName,
|
||||||
duration: Duration(milliseconds: 200),
|
textAlign: TextAlign.center,
|
||||||
);
|
scrollPadding: EdgeInsets.only(bottom: 70),
|
||||||
},
|
decoration: InputDecoration(
|
||||||
),
|
hintText: 'Домен',
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
),
|
||||||
BrandButton.text(
|
Spacer(),
|
||||||
onPressed: () => _showModal(context, _HowHetzner()),
|
BrandButton.rised(
|
||||||
title: 'Как получить API Token',
|
onPressed:
|
||||||
),
|
formCubit.state.isSubmitting ? null : formCubit.trySubmit,
|
||||||
],
|
title: 'Подключить',
|
||||||
|
),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
BrandButton.text(
|
||||||
|
onPressed: () => _showModal(context, _HowHetzner()),
|
||||||
|
title: 'Как получить API Token',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _stepFour(InitializingCubit cubit) {
|
Widget _stepUser(AppConfigCubit initializingCubit) {
|
||||||
return Column(
|
return BlocProvider(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
create: (context) => UserFormCubit(initializingCubit),
|
||||||
children: [
|
child: Builder(builder: (context) {
|
||||||
SizedBox(height: 10),
|
var formCubit = context.watch<UserFormCubit>();
|
||||||
Expanded(
|
|
||||||
child: Column(
|
return Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
TextField(
|
children: [
|
||||||
decoration: InputDecoration(
|
Spacer(),
|
||||||
hintText: 'нинейм',
|
SizedBox(height: 10),
|
||||||
),
|
CubitFormTextField(
|
||||||
|
formFieldCubit: formCubit.userName,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
scrollPadding: EdgeInsets.only(bottom: 70),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Никнейм',
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
),
|
||||||
TextField(
|
SizedBox(height: 10),
|
||||||
obscureText: true,
|
CubitFormTextField(
|
||||||
decoration: InputDecoration(
|
formFieldCubit: formCubit.password,
|
||||||
hintText: 'пароль',
|
textAlign: TextAlign.center,
|
||||||
),
|
scrollPadding: EdgeInsets.only(bottom: 70),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Пароль',
|
||||||
),
|
),
|
||||||
Spacer(),
|
),
|
||||||
BrandButton.rised(
|
Spacer(),
|
||||||
onPressed: () {
|
BrandButton.rised(
|
||||||
cubit.setRootUser(
|
onPressed:
|
||||||
User(login: 'aa', password: 'bbb'),
|
formCubit.state.isSubmitting ? null : formCubit.trySubmit,
|
||||||
);
|
title: 'Подключить',
|
||||||
_goToMainPage();
|
),
|
||||||
},
|
SizedBox(height: 10),
|
||||||
title: 'some text',
|
BrandButton.text(
|
||||||
),
|
onPressed: () => _showModal(context, _HowHetzner()),
|
||||||
],
|
title: 'Как получить API Token',
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
SizedBox(height: 10),
|
);
|
||||||
BrandButton.text(
|
}),
|
||||||
onPressed: () => _showModal(context, _HowHetzner()),
|
|
||||||
title: 'Как получить API Token',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _stepServer(AppConfigCubit appConfigCubit) {
|
||||||
|
var isLoading = appConfigCubit.state.isLoading;
|
||||||
|
return Builder(builder: (context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Spacer(
|
||||||
|
flex: 2,
|
||||||
|
),
|
||||||
|
BrandText.h2('Создать сервер'),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
BrandText.body2('Создать сервер'),
|
||||||
|
Spacer(),
|
||||||
|
BrandButton.rised(
|
||||||
|
onPressed: isLoading ? null : appConfigCubit.createServer,
|
||||||
|
title: isLoading ? 'loading' : 'Создать сервер',
|
||||||
|
),
|
||||||
|
Spacer(
|
||||||
|
flex: 2,
|
||||||
|
),
|
||||||
|
BrandButton.text(
|
||||||
|
onPressed: () => _showModal(context, _HowHetzner()),
|
||||||
|
title: 'Что это значит?',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Widget _addCard(Widget child) {
|
Widget _addCard(Widget child) {
|
||||||
return Padding(
|
return Container(
|
||||||
|
height: 500,
|
||||||
padding: brandPagePadding2,
|
padding: brandPagePadding2,
|
||||||
child: BrandCard(
|
child: BrandCard(
|
||||||
child: child,
|
child: child,
|
||||||
|
@ -322,235 +348,3 @@ class _HowHetzner extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
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} символа',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Spacer(),
|
|
||||||
BrandButton.rised(
|
|
||||||
onPressed: _valid ? onPressed : null,
|
|
||||||
title: widget.submitButtonText,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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: 48,
|
|
||||||
// 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(
|
|
||||||
// isBlocked: true,
|
|
||||||
// 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),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// break;
|
|
||||||
// case ProviderType.backup:
|
|
||||||
// return BrandCard(
|
|
||||||
// isBlocked: true,
|
|
||||||
// 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;
|
|
||||||
// }
|
|
||||||
|
|
|
@ -2,9 +2,11 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/config/brand_colors.dart';
|
import 'package:selfprivacy/config/brand_colors.dart';
|
||||||
import 'package:selfprivacy/config/brand_theme.dart';
|
import 'package:selfprivacy/config/brand_theme.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/app_config/app_config_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_divider/brand_divider.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_header/brand_header.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||||
|
import 'package:selfprivacy/utils/named_font_weight.dart';
|
||||||
|
|
||||||
class AppSettingsPage extends StatefulWidget {
|
class AppSettingsPage extends StatefulWidget {
|
||||||
const AppSettingsPage({Key key}) : super(key: key);
|
const AppSettingsPage({Key key}) : super(key: key);
|
||||||
|
@ -53,11 +55,75 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
|
||||||
activeColor: BrandColors.green1,
|
activeColor: BrandColors.green1,
|
||||||
activeTrackColor: BrandColors.green2,
|
activeTrackColor: BrandColors.green2,
|
||||||
value: Theme.of(context).brightness == Brightness.dark,
|
value: Theme.of(context).brightness == Brightness.dark,
|
||||||
onChanged: (value) =>
|
onChanged: (value) => appSettings.updateDarkMode(
|
||||||
appSettings.update(isDarkModeOn: !isDarkModeOn),
|
isDarkModeOn: !isDarkModeOn),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
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: 'Reset app config',
|
||||||
|
value: 'Reset api keys and root user',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 5),
|
||||||
|
RaisedButton(
|
||||||
|
color: BrandColors.red1,
|
||||||
|
child: Text(
|
||||||
|
'Reset',
|
||||||
|
style: TextStyle(
|
||||||
|
color: BrandColors.white,
|
||||||
|
fontWeight: NamedFontWeight.demiBold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
child: AlertDialog(
|
||||||
|
title: Text('Are you sure?'),
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: ListBody(
|
||||||
|
children: <Widget>[
|
||||||
|
Text('Reset all keys?'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
child: Text(
|
||||||
|
'Reset',
|
||||||
|
style: TextStyle(
|
||||||
|
color: BrandColors.red1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
context.read<AppConfigCubit>().reset();
|
||||||
|
Navigator.of(context)..pop()..pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
child: Text('Cancel'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context)..pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -88,10 +154,6 @@ class _TextColumn extends StatelessWidget {
|
||||||
style: TextStyle(color: hasWarning ? BrandColors.warning : null),
|
style: TextStyle(color: hasWarning ? BrandColors.warning : null),
|
||||||
),
|
),
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
BrandText.body1(
|
|
||||||
title,
|
|
||||||
style: TextStyle(color: hasWarning ? BrandColors.warning : null),
|
|
||||||
),
|
|
||||||
BrandText.body1(value,
|
BrandText.body1(value,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
|
|
|
@ -6,6 +6,8 @@ import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||||
import 'package:selfprivacy/ui/pages/initializing/initializing.dart';
|
import 'package:selfprivacy/ui/pages/initializing/initializing.dart';
|
||||||
|
import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart';
|
||||||
|
import 'package:selfprivacy/ui/pages/rootRoute.dart';
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||||
|
|
||||||
import 'about/about.dart';
|
import 'about/about.dart';
|
||||||
|
@ -49,6 +51,11 @@ class MorePage extends StatelessWidget {
|
||||||
iconData: BrandIcons.help,
|
iconData: BrandIcons.help,
|
||||||
goTo: InfoPage(),
|
goTo: InfoPage(),
|
||||||
),
|
),
|
||||||
|
_NavItem(
|
||||||
|
title: 'Onboarding',
|
||||||
|
iconData: BrandIcons.triangle,
|
||||||
|
goTo: OnboardingPage(nextPage: RootPage()),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||||
import 'package:selfprivacy/ui/pages/initializing/initializing.dart';
|
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||||
|
|
||||||
class OnboardingPage extends StatefulWidget {
|
class OnboardingPage extends StatefulWidget {
|
||||||
const OnboardingPage({Key key}) : super(key: key);
|
const OnboardingPage({Key key, @required this.nextPage}) : super(key: key);
|
||||||
|
|
||||||
|
final Widget nextPage;
|
||||||
@override
|
@override
|
||||||
_OnboardingPageState createState() => _OnboardingPageState();
|
_OnboardingPageState createState() => _OnboardingPageState();
|
||||||
}
|
}
|
||||||
|
@ -107,8 +108,9 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||||
),
|
),
|
||||||
BrandButton.rised(
|
BrandButton.rised(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
context.read<AppSettingsCubit>().turnOffOnboarding();
|
||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
.pushReplacement(materialRoute(InitializingPage()));
|
.pushReplacement(materialRoute(widget.nextPage));
|
||||||
},
|
},
|
||||||
title: 'Понял',
|
title: 'Понял',
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
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/initializing/initializing_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.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/state_types.dart';
|
import 'package:selfprivacy/logic/models/state_types.dart';
|
||||||
|
@ -9,6 +9,7 @@ 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';
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||||
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart';
|
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
|
||||||
import 'package:selfprivacy/ui/pages/providers/settings/settings.dart';
|
import 'package:selfprivacy/ui/pages/providers/settings/settings.dart';
|
||||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||||
|
|
||||||
|
@ -22,9 +23,12 @@ class ProvidersPage extends StatefulWidget {
|
||||||
class _ProvidersPageState extends State<ProvidersPage> {
|
class _ProvidersPageState extends State<ProvidersPage> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized;
|
||||||
|
|
||||||
final cards = ProviderType.values
|
final cards = ProviderType.values
|
||||||
.map((type) =>
|
.map((type) => _Card(
|
||||||
_Card(provider: ProviderModel(state: StateType.stable, type: type)))
|
provider:
|
||||||
|
ProviderModel(state: StateType.uninitialized, type: type)))
|
||||||
.toList();
|
.toList();
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: PreferredSize(
|
appBar: PreferredSize(
|
||||||
|
@ -33,7 +37,13 @@ class _ProvidersPageState extends State<ProvidersPage> {
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
padding: brandPagePadding2,
|
padding: brandPagePadding2,
|
||||||
children: cards,
|
children: [
|
||||||
|
if (!isReady) ...[
|
||||||
|
NotReadyCard(),
|
||||||
|
SizedBox(height: 24),
|
||||||
|
],
|
||||||
|
...cards,
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -48,8 +58,6 @@ class _Card extends StatelessWidget {
|
||||||
String title;
|
String title;
|
||||||
String message;
|
String message;
|
||||||
String stableText;
|
String stableText;
|
||||||
var isFullyInitilized =
|
|
||||||
context.watch<InitializingCubit>().state.isFullyInitilized;
|
|
||||||
|
|
||||||
switch (provider.type) {
|
switch (provider.type) {
|
||||||
case ProviderType.server:
|
case ProviderType.server:
|
||||||
|
@ -80,11 +88,10 @@ class _Card extends StatelessWidget {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
child: BrandCard(
|
child: BrandCard(
|
||||||
isBlocked: !isFullyInitilized,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
IconStatusMaks(
|
IconStatusMask(
|
||||||
status: provider.state,
|
status: provider.state,
|
||||||
child: Icon(provider.icon, size: 30, color: Colors.white),
|
child: Icon(provider.icon, size: 30, color: Colors.white),
|
||||||
),
|
),
|
||||||
|
@ -176,7 +183,7 @@ class _ProviderDetails extends StatelessWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: 13),
|
SizedBox(height: 13),
|
||||||
IconStatusMaks(
|
IconStatusMask(
|
||||||
status: provider.state,
|
status: provider.state,
|
||||||
child:
|
child:
|
||||||
Icon(provider.icon, size: 40, color: Colors.white),
|
Icon(provider.icon, size: 40, color: Colors.white),
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
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/initializing/initializing_cubit.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';
|
||||||
|
@ -34,9 +31,6 @@ class _RootPageState extends State<RootPage>
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var isUserFilled =
|
|
||||||
context.watch<InitializingCubit>().state.isFullyInitilized;
|
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
body: TabBarView(
|
body: TabBarView(
|
||||||
|
@ -44,7 +38,7 @@ class _RootPageState extends State<RootPage>
|
||||||
children: [
|
children: [
|
||||||
ProvidersPage(),
|
ProvidersPage(),
|
||||||
ServicesPage(),
|
ServicesPage(),
|
||||||
isUserFilled ? UsersPage() : _NotReady(),
|
UsersPage(),
|
||||||
MorePage(),
|
MorePage(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -55,20 +49,3 @@ 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'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
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/initializing/initializing_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/service.dart';
|
import 'package:selfprivacy/logic/models/service.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
||||||
|
@ -9,6 +9,7 @@ import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||||
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart';
|
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
|
||||||
|
|
||||||
class ServicesPage extends StatefulWidget {
|
class ServicesPage extends StatefulWidget {
|
||||||
ServicesPage({Key key}) : super(key: key);
|
ServicesPage({Key key}) : super(key: key);
|
||||||
|
@ -23,6 +24,8 @@ class _ServicesPageState extends State<ServicesPage> {
|
||||||
final serviceCubit = context.watch<ServicesCubit>();
|
final serviceCubit = context.watch<ServicesCubit>();
|
||||||
final connected = serviceCubit.state.connected;
|
final connected = serviceCubit.state.connected;
|
||||||
final uninitialized = serviceCubit.state.uninitialized;
|
final uninitialized = serviceCubit.state.uninitialized;
|
||||||
|
var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: PreferredSize(
|
appBar: PreferredSize(
|
||||||
child: BrandHeader(title: 'Сервисы'),
|
child: BrandHeader(title: 'Сервисы'),
|
||||||
|
@ -31,6 +34,7 @@ class _ServicesPageState extends State<ServicesPage> {
|
||||||
body: ListView(
|
body: ListView(
|
||||||
padding: brandPagePadding2,
|
padding: brandPagePadding2,
|
||||||
children: [
|
children: [
|
||||||
|
if (!isReady) NotReadyCard(),
|
||||||
SizedBox(height: 24),
|
SizedBox(height: 24),
|
||||||
...connected.map((service) => _Card(service: service)).toList(),
|
...connected.map((service) => _Card(service: service)).toList(),
|
||||||
if (uninitialized.isNotEmpty) ...[
|
if (uninitialized.isNotEmpty) ...[
|
||||||
|
@ -53,7 +57,6 @@ class _Card extends StatelessWidget {
|
||||||
String title;
|
String title;
|
||||||
IconData iconData;
|
IconData iconData;
|
||||||
String description;
|
String description;
|
||||||
var isFullyInitilized = context.watch<InitializingCubit>().state.isFullyInitilized;
|
|
||||||
|
|
||||||
switch (service.type) {
|
switch (service.type) {
|
||||||
case ServiceTypes.messanger:
|
case ServiceTypes.messanger:
|
||||||
|
@ -85,11 +88,10 @@ class _Card extends StatelessWidget {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return BrandCard(
|
return BrandCard(
|
||||||
isBlocked: !isFullyInitilized,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
IconStatusMaks(
|
IconStatusMask(
|
||||||
status: service.state,
|
status: service.state,
|
||||||
child: Icon(iconData, size: 30, color: Colors.white),
|
child: Icon(iconData, size: 30, color: Colors.white),
|
||||||
),
|
),
|
||||||
|
|
37
lib/ui/pages/users/empty.dart
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
part of 'users.dart';
|
||||||
|
|
||||||
|
class _NoUsers extends StatelessWidget {
|
||||||
|
const _NoUsers({Key key, @required this.text})
|
||||||
|
: assert(text != null),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
final String text;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(BrandIcons.users, size: 50, color: BrandColors.grey7),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
BrandText.h2(
|
||||||
|
'Здесь пока никого',
|
||||||
|
style: TextStyle(
|
||||||
|
color: BrandColors.grey7,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
BrandText.medium(
|
||||||
|
text,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: BrandColors.grey7,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
34
lib/ui/pages/users/fab.dart
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
part of 'users.dart';
|
||||||
|
|
||||||
|
class _Fab extends StatelessWidget {
|
||||||
|
const _Fab({Key key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: 48.0,
|
||||||
|
height: 48.0,
|
||||||
|
child: RawMaterialButton(
|
||||||
|
fillColor: BrandColors.blue,
|
||||||
|
shape: CircleBorder(),
|
||||||
|
elevation: 0.0,
|
||||||
|
highlightElevation: 2,
|
||||||
|
child: Icon(
|
||||||
|
Icons.add,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 34,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
showModalBottomSheet<void>(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return _NewUser();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
74
lib/ui/pages/users/new_user.dart
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
part of 'users.dart';
|
||||||
|
|
||||||
|
class _NewUser extends StatefulWidget {
|
||||||
|
const _NewUser({Key key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
__NewUserState createState() => __NewUserState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class __NewUserState extends State<_NewUser> {
|
||||||
|
var passController = TextEditingController(text: genPass());
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// final usersCubit = context.watch<UsersCubit>();
|
||||||
|
|
||||||
|
return BrandModalSheet(
|
||||||
|
child: Container(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
BrandHeader(title: 'Новый пользователь'),
|
||||||
|
SizedBox(width: 14),
|
||||||
|
Padding(
|
||||||
|
padding: brandPagePadding2,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
TextField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Логин',
|
||||||
|
suffixText: '@example',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
TextField(
|
||||||
|
controller: passController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
alignLabelWithHint: false,
|
||||||
|
labelText: 'Пароль',
|
||||||
|
suffixIcon: Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8),
|
||||||
|
child: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
BrandIcons.refresh,
|
||||||
|
color: BrandColors.blue,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
passController.value =
|
||||||
|
TextEditingValue(text: genPass());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 30),
|
||||||
|
BrandButton.rised(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
title: 'Создать',
|
||||||
|
),
|
||||||
|
SizedBox(height: 40),
|
||||||
|
Text(
|
||||||
|
'Новый пользователь автоматически получит доступ ко всем сервисам. Ещё какое-то описание.'),
|
||||||
|
SizedBox(height: 30),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
40
lib/ui/pages/users/user.dart
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
part of 'users.dart';
|
||||||
|
|
||||||
|
class _User extends StatelessWidget {
|
||||||
|
const _User({Key key, this.user}) : super(key: key);
|
||||||
|
|
||||||
|
final User user;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: () {
|
||||||
|
showModalBottomSheet<void>(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return _UserDetails(user: user);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: brandPagePadding2,
|
||||||
|
height: 48,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 17,
|
||||||
|
height: 17,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: user.color,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 20),
|
||||||
|
BrandText.h4(user.login),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
159
lib/ui/pages/users/user_details.dart
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
part of 'users.dart';
|
||||||
|
|
||||||
|
class _UserDetails extends StatelessWidget {
|
||||||
|
const _UserDetails({
|
||||||
|
Key key,
|
||||||
|
this.user,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final User user;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BrandModalSheet(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
height: 200,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: user.color,
|
||||||
|
borderRadius: BorderRadius.vertical(
|
||||||
|
top: Radius.circular(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
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.reset:
|
||||||
|
break;
|
||||||
|
case PopupMenuItemType.delete:
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
child: AlertDialog(
|
||||||
|
title: Text('Подтверждение '),
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: ListBody(
|
||||||
|
children: <Widget>[
|
||||||
|
Text('удалить учетную запись?'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
child: Text('Отменить'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context)..pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
child: Text(
|
||||||
|
'Удалить',
|
||||||
|
style: TextStyle(
|
||||||
|
color: BrandColors.red1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context)..pop()..pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.more_vert),
|
||||||
|
itemBuilder: (BuildContext context) => [
|
||||||
|
PopupMenuItem<PopupMenuItemType>(
|
||||||
|
value: PopupMenuItemType.reset,
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.only(left: 5),
|
||||||
|
child: Text('Сбросить пароль'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
PopupMenuItem<PopupMenuItemType>(
|
||||||
|
value: PopupMenuItemType.delete,
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.only(left: 5),
|
||||||
|
child: Text(
|
||||||
|
'Удалить',
|
||||||
|
style: TextStyle(color: BrandColors.red1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
vertical: 20,
|
||||||
|
horizontal: 15,
|
||||||
|
),
|
||||||
|
child: BrandText.h1(
|
||||||
|
user.login,
|
||||||
|
softWrap: true,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
Padding(
|
||||||
|
padding: brandPagePadding2.copyWith(bottom: 20),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
BrandText.small('Учетная запись'),
|
||||||
|
Container(
|
||||||
|
height: 40,
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: BrandText.h4('${user.login}@example.com'),
|
||||||
|
),
|
||||||
|
SizedBox(height: 14),
|
||||||
|
BrandText.small('Пароль'),
|
||||||
|
Container(
|
||||||
|
height: 40,
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: BrandText.h4(user.password),
|
||||||
|
),
|
||||||
|
SizedBox(height: 24),
|
||||||
|
BrandDivider(),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
BrandButton.iconText(
|
||||||
|
title: 'Отправить реквизиты для входа',
|
||||||
|
icon: Icon(BrandIcons.share),
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
BrandDivider(),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
Text(
|
||||||
|
'Вам был создан доступ к сервисам с логином <login> и паролем <password> к сервисам:- E-mail с адресом <username@domain.com>- Менеджер паролей: <pass.domain.com>- Файловое облако: <cloud.mydomain.com>- Видеоконференция <meet.domain.com>- Git сервер <git.mydomain.com>'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PopupMenuItemType {
|
||||||
|
reset,
|
||||||
|
delete,
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
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/config/brand_theme.dart';
|
import 'package:selfprivacy/config/brand_theme.dart';
|
||||||
|
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/user.dart';
|
import 'package:selfprivacy/logic/models/user.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
||||||
|
@ -9,318 +10,74 @@ import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
|
||||||
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
|
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.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';
|
||||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||||
|
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
|
||||||
import 'package:selfprivacy/utils/password_generator.dart';
|
import 'package:selfprivacy/utils/password_generator.dart';
|
||||||
|
|
||||||
|
part 'fab.dart';
|
||||||
|
part 'new_user.dart';
|
||||||
|
part 'user_details.dart';
|
||||||
|
part 'user.dart';
|
||||||
|
part 'empty.dart';
|
||||||
|
|
||||||
class UsersPage extends StatelessWidget {
|
class UsersPage extends StatelessWidget {
|
||||||
const UsersPage({Key key}) : super(key: key);
|
const UsersPage({Key key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final usersCubit = context.watch<UsersCubit>();
|
final usersCubit = context.watch<UsersCubit>();
|
||||||
|
var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized;
|
||||||
final users = usersCubit.state.users;
|
final users = usersCubit.state.users;
|
||||||
|
final isEmpty = usersCubit.state.isEmpty;
|
||||||
|
|
||||||
|
Widget child;
|
||||||
|
|
||||||
|
if (!isReady) {
|
||||||
|
child = isNotReady();
|
||||||
|
} else {
|
||||||
|
child = isEmpty
|
||||||
|
? Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: _NoUsers(
|
||||||
|
text: 'Добавьте первого пользователя',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: ListView(
|
||||||
|
children: [
|
||||||
|
...users.map((user) => _User(user: user)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: PreferredSize(
|
appBar: PreferredSize(
|
||||||
child: BrandHeader(title: 'Пользователи'),
|
child: BrandHeader(title: 'Пользователи'),
|
||||||
preferredSize: Size.fromHeight(52),
|
preferredSize: Size.fromHeight(52),
|
||||||
),
|
),
|
||||||
floatingActionButton: Container(
|
floatingActionButton: isReady ? _Fab() : null,
|
||||||
width: 48.0,
|
body: child,
|
||||||
height: 48.0,
|
|
||||||
child: RawMaterialButton(
|
|
||||||
fillColor: BrandColors.blue,
|
|
||||||
shape: CircleBorder(),
|
|
||||||
elevation: 0.0,
|
|
||||||
highlightElevation: 2,
|
|
||||||
child: Icon(
|
|
||||||
Icons.add,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 34,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
showModalBottomSheet<void>(
|
|
||||||
context: context,
|
|
||||||
isScrollControlled: true,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return _NewUser();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: ListView(
|
|
||||||
children: [
|
|
||||||
...users.map((user) => _User(user: user)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
class _User extends StatelessWidget {
|
Widget isNotReady() {
|
||||||
const _User({Key key, this.user}) : super(key: key);
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
final User user;
|
children: [
|
||||||
@override
|
Padding(
|
||||||
Widget build(BuildContext context) {
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
return InkWell(
|
child: NotReadyCard(),
|
||||||
onTap: () {
|
|
||||||
showModalBottomSheet<void>(
|
|
||||||
context: context,
|
|
||||||
isScrollControlled: true,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return _UserDetails(user: user);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
padding: brandPagePadding2,
|
|
||||||
height: 48,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 17,
|
|
||||||
height: 17,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: user.color,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 20),
|
|
||||||
BrandText.h4(user.login),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
Expanded(
|
||||||
);
|
child: Container(
|
||||||
}
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
}
|
child: Center(
|
||||||
|
child: _NoUsers(
|
||||||
class _NewUser extends StatefulWidget {
|
text:
|
||||||
const _NewUser({Key key}) : super(key: key);
|
'Подключите сервер, домен и DNS в разеде Провайдеры, чтобы добавить первого пользователя',
|
||||||
|
|
||||||
@override
|
|
||||||
__NewUserState createState() => __NewUserState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class __NewUserState extends State<_NewUser> {
|
|
||||||
var passController = TextEditingController(text: genPass());
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BrandModalSheet(
|
|
||||||
child: Container(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
BrandHeader(title: 'Новый пользователь'),
|
|
||||||
SizedBox(width: 14),
|
|
||||||
Padding(
|
|
||||||
padding: brandPagePadding2,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
TextField(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'Логин',
|
|
||||||
suffixText: '@example',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 20),
|
|
||||||
TextField(
|
|
||||||
controller: passController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
alignLabelWithHint: false,
|
|
||||||
labelText: 'Пароль',
|
|
||||||
suffixIcon: Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 8),
|
|
||||||
child: IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
BrandIcons.refresh,
|
|
||||||
color: BrandColors.blue,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
passController.value =
|
|
||||||
TextEditingValue(text: genPass());
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 30),
|
|
||||||
BrandButton.rised(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
title: 'Создать',
|
|
||||||
),
|
|
||||||
SizedBox(height: 40),
|
|
||||||
Text(
|
|
||||||
'Новый пользователь автоматически получит доступ ко всем сервисам. Ещё какое-то описание.'),
|
|
||||||
SizedBox(height: 30),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _UserDetails extends StatelessWidget {
|
|
||||||
const _UserDetails({
|
|
||||||
Key key,
|
|
||||||
this.user,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
final User user;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BrandModalSheet(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
height: 200,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: user.color,
|
|
||||||
borderRadius: BorderRadius.vertical(
|
|
||||||
top: Radius.circular(20),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
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.reset:
|
|
||||||
break;
|
|
||||||
case PopupMenuItemType.delete:
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
child: AlertDialog(
|
|
||||||
title: Text('Подтверждение '),
|
|
||||||
content: SingleChildScrollView(
|
|
||||||
child: ListBody(
|
|
||||||
children: <Widget>[
|
|
||||||
Text('удалить учетную запись?'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
actions: <Widget>[
|
|
||||||
TextButton(
|
|
||||||
child: Text('Отменить'),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context)..pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
child: Text(
|
|
||||||
'Удалить',
|
|
||||||
style: TextStyle(
|
|
||||||
color: BrandColors.red,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context)..pop()..pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
icon: Icon(Icons.more_vert),
|
|
||||||
itemBuilder: (BuildContext context) => [
|
|
||||||
PopupMenuItem<PopupMenuItemType>(
|
|
||||||
value: PopupMenuItemType.reset,
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.only(left: 5),
|
|
||||||
child: Text('Сбросить пароль'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
PopupMenuItem<PopupMenuItemType>(
|
|
||||||
value: PopupMenuItemType.delete,
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.only(left: 5),
|
|
||||||
child: Text(
|
|
||||||
'Удалить',
|
|
||||||
style: TextStyle(color: BrandColors.red),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Spacer(),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
vertical: 20,
|
|
||||||
horizontal: 15,
|
|
||||||
),
|
|
||||||
child: BrandText.h1(
|
|
||||||
user.login,
|
|
||||||
softWrap: true,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
SizedBox(height: 20),
|
)
|
||||||
Padding(
|
],
|
||||||
padding: brandPagePadding2.copyWith(bottom: 20),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
BrandText.small('Учетная запись'),
|
|
||||||
Container(
|
|
||||||
height: 40,
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: BrandText.h4('${user.login}@example.com'),
|
|
||||||
),
|
|
||||||
SizedBox(height: 14),
|
|
||||||
BrandText.small('Пароль'),
|
|
||||||
Container(
|
|
||||||
height: 40,
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: BrandText.h4(user.password),
|
|
||||||
),
|
|
||||||
SizedBox(height: 24),
|
|
||||||
BrandDivider(),
|
|
||||||
SizedBox(height: 20),
|
|
||||||
BrandButton.iconText(
|
|
||||||
title: 'Отправить реквизиты для входа',
|
|
||||||
icon: Icon(BrandIcons.share),
|
|
||||||
onPressed: () {},
|
|
||||||
),
|
|
||||||
SizedBox(height: 20),
|
|
||||||
BrandDivider(),
|
|
||||||
SizedBox(height: 20),
|
|
||||||
Text(
|
|
||||||
'Вам был создан доступ к сервисам с логином <login> и паролем <password> к сервисам:- E-mail с адресом <username@domain.com>- Менеджер паролей: <pass.domain.com>- Файловое облако: <cloud.mydomain.com>- Видеоконференция <meet.domain.com>- Git сервер <git.mydomain.com>'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PopupMenuItemType {
|
|
||||||
reset,
|
|
||||||
delete,
|
|
||||||
}
|
|
||||||
|
|
10
lib/utils/crypto.dart
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:crypto/crypto.dart';
|
||||||
|
|
||||||
|
String convertToSha512Hash(String text) {
|
||||||
|
var bytes = utf8.encode(text);
|
||||||
|
|
||||||
|
var hash = sha512.convert(bytes);
|
||||||
|
return hash.toString();
|
||||||
|
}
|
321
pubspec.lock
|
@ -1,6 +1,20 @@
|
||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
|
_fe_analyzer_shared:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _fe_analyzer_shared
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "14.0.0"
|
||||||
|
analyzer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: analyzer
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.41.1"
|
||||||
archive:
|
archive:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -36,6 +50,62 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0-nullsafety.1"
|
version: "2.1.0-nullsafety.1"
|
||||||
|
build:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.6.0"
|
||||||
|
build_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_config
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.5"
|
||||||
|
build_daemon:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_daemon
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
|
build_resolvers:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_resolvers
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.1"
|
||||||
|
build_runner:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: build_runner
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.11"
|
||||||
|
build_runner_core:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_runner_core
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.5"
|
||||||
|
built_collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: built_collection
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "4.3.2"
|
||||||
|
built_value:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: built_value
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "7.1.0"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -50,6 +120,20 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0-nullsafety.1"
|
version: "1.2.0-nullsafety.1"
|
||||||
|
checked_yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: checked_yaml
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.4"
|
||||||
|
cli_util:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cli_util
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.0"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -57,6 +141,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0-nullsafety.1"
|
version: "1.1.0-nullsafety.1"
|
||||||
|
code_builder:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: code_builder
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.5.0"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -72,7 +163,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.1"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: crypto
|
name: crypto
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
|
@ -84,7 +175,7 @@ packages:
|
||||||
name: cubit_form
|
name: cubit_form
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.0.14"
|
version: "0.0.15"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -92,6 +183,27 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
|
dart_style:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dart_style
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.10"
|
||||||
|
dartx:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dartx
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.5.0"
|
||||||
|
dio:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: dio
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.10"
|
||||||
easy_localization:
|
easy_localization:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -134,6 +246,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.2.1"
|
version: "5.2.1"
|
||||||
|
fixnum:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fixnum
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.11"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -158,6 +277,13 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_secure_storage:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.3.5"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -168,6 +294,13 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
google_fonts:
|
google_fonts:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -175,6 +308,34 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
|
graphs:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: graphs
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.0"
|
||||||
|
hive:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: hive
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.4+1"
|
||||||
|
hive_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: hive_flutter
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.1"
|
||||||
|
hive_generator:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: hive_generator
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.8.2"
|
||||||
http:
|
http:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -182,6 +343,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.2"
|
version: "0.12.2"
|
||||||
|
http_multi_server:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_multi_server
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
http_parser:
|
http_parser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -203,6 +371,41 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.16.1"
|
version: "0.16.1"
|
||||||
|
io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: io
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.4"
|
||||||
|
js:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: js
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.2"
|
||||||
|
json_annotation:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: json_annotation
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.1"
|
||||||
|
json_serializable:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: json_serializable
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.5.1"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.11.4"
|
||||||
mask_text_input_formatter:
|
mask_text_input_formatter:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -224,6 +427,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0-nullsafety.3"
|
version: "1.3.0-nullsafety.3"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.7"
|
||||||
nested:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -231,6 +441,27 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.0.4"
|
version: "0.0.4"
|
||||||
|
node_interop:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: node_interop
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
node_io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: node_io
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
|
package_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_config
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.3"
|
||||||
package_info:
|
package_info:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -308,6 +539,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.3"
|
version: "1.0.3"
|
||||||
|
pool:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pool
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
process:
|
process:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -322,6 +560,27 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.3.2+2"
|
version: "4.3.2+2"
|
||||||
|
pub_semver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_semver
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.4"
|
||||||
|
pubspec_parse:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pubspec_parse
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.7"
|
||||||
|
quiver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: quiver
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.5"
|
||||||
shared_preferences:
|
shared_preferences:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -364,6 +623,20 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.0.1+3"
|
version: "0.0.1+3"
|
||||||
|
shelf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.9"
|
||||||
|
shelf_web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_web_socket
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.3"
|
||||||
shortuuid:
|
shortuuid:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -376,6 +649,13 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.99"
|
version: "0.0.99"
|
||||||
|
source_gen:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_gen
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.10+1"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -397,6 +677,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0-nullsafety.1"
|
version: "2.1.0-nullsafety.1"
|
||||||
|
stream_transform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_transform
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
string_scanner:
|
string_scanner:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -418,6 +705,20 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.19-nullsafety.2"
|
version: "0.2.19-nullsafety.2"
|
||||||
|
time:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: time
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
timing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: timing
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.1+3"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -481,6 +782,20 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0-nullsafety.3"
|
version: "2.1.0-nullsafety.3"
|
||||||
|
watcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: watcher
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.7+15"
|
||||||
|
web_socket_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket_channel
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -510,5 +825,5 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.1"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.10.0-110 <2.11.0"
|
dart: ">=2.10.0 <2.11.0"
|
||||||
flutter: ">=1.22.0 <2.0.0"
|
flutter: ">=1.22.0 <2.0.0"
|
||||||
|
|
14
pubspec.yaml
|
@ -9,12 +9,18 @@ environment:
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
cubit_form: ^0.0.14
|
crypto: ^2.1.5
|
||||||
|
cubit_form: ^0.0.15
|
||||||
cupertino_icons: ^1.0.0
|
cupertino_icons: ^1.0.0
|
||||||
|
dio: ^3.0.10
|
||||||
easy_localization: ^2.3.3
|
easy_localization: ^2.3.3
|
||||||
equatable: ^1.2.5
|
equatable: ^1.2.5
|
||||||
flutter_bloc: ^6.1.1
|
flutter_bloc: ^6.1.1
|
||||||
|
flutter_secure_storage: ^3.3.5
|
||||||
google_fonts: ^1.1.1
|
google_fonts: ^1.1.1
|
||||||
|
hive: ^1.4.4+1
|
||||||
|
hive_flutter: ^0.3.1
|
||||||
|
json_annotation: ^3.1.1
|
||||||
package_info: ^0.4.3+2
|
package_info: ^0.4.3+2
|
||||||
provider: ^4.3.2+2
|
provider: ^4.3.2+2
|
||||||
url_launcher: ^5.7.10
|
url_launcher: ^5.7.10
|
||||||
|
@ -22,12 +28,16 @@ dependencies:
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
build_runner: ^1.10.11
|
||||||
flutter_launcher_icons: ^0.8.1
|
flutter_launcher_icons: ^0.8.1
|
||||||
|
hive_generator: ^0.8.2
|
||||||
|
json_serializable: ^3.5.1
|
||||||
|
|
||||||
flutter_icons:
|
flutter_icons:
|
||||||
android: "launcher_icon"
|
android: "launcher_icon"
|
||||||
ios: true
|
ios: true
|
||||||
image_path: "assets/images/icon/logo.png"
|
image_path_android: "assets/images/icon/logo_android.png"
|
||||||
|
image_path_ios: "assets/images/icon/logo_ios.png"
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|