update
|
@ -39,7 +39,7 @@ android {
|
|||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "pro.kherel.selfprivacy"
|
||||
minSdkVersion 16
|
||||
minSdkVersion 18
|
||||
targetSdkVersion 29
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
|
|
|
@ -15,11 +15,9 @@
|
|||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:allowBackup="false" >
|
||||
<!-- https://github.com/mogol/flutter_secure_storage allowBackup="false" -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.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:
|
||||
- Flutter (1.0.0)
|
||||
- flutter_secure_storage (3.3.1):
|
||||
- Flutter
|
||||
- package_info (0.0.1):
|
||||
- Flutter
|
||||
- path_provider (0.0.1):
|
||||
|
@ -11,6 +13,7 @@ PODS:
|
|||
|
||||
DEPENDENCIES:
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||
- package_info (from `.symlinks/plugins/package_info/ios`)
|
||||
- path_provider (from `.symlinks/plugins/path_provider/ios`)
|
||||
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
|
||||
|
@ -19,6 +22,8 @@ DEPENDENCIES:
|
|||
EXTERNAL SOURCES:
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
flutter_secure_storage:
|
||||
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
||||
package_info:
|
||||
:path: ".symlinks/plugins/package_info/ios"
|
||||
path_provider:
|
||||
|
@ -30,6 +35,7 @@ EXTERNAL SOURCES:
|
|||
|
||||
SPEC CHECKSUMS:
|
||||
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
|
||||
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
|
||||
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
|
||||
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
|
||||
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/scheduler.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/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/services/services_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
|
||||
|
@ -14,14 +13,22 @@ class BlocAndProviderConfig extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var platformBrightness =
|
||||
SchedulerBinding.instance.window.platformBrightness;
|
||||
var isDark = platformBrightness == Brightness.dark;
|
||||
// var platformBrightness = Brightness.dark;
|
||||
// var platformBrightness =
|
||||
// SchedulerBinding.instance.window.platformBrightness;
|
||||
// var isDark = platformBrightness == Brightness.dark;
|
||||
var isDark = false;
|
||||
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
BlocProvider(create: (_) => AppSettingsCubit(isDarkModeOn: isDark)),
|
||||
BlocProvider(create: (_) => InitializingCubit()),
|
||||
BlocProvider(
|
||||
create: (_) => AppSettingsCubit(
|
||||
isDarkModeOn: isDark,
|
||||
isOnbordingShowing: true,
|
||||
)..load(),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (_) => AppConfigCubit()..load(),
|
||||
),
|
||||
BlocProvider(create: (_) => ServicesCubit()),
|
||||
BlocProvider(create: (_) => ProvidersCubit()),
|
||||
BlocProvider(create: (_) => UsersCubit()),
|
||||
|
|
|
@ -1,33 +1,23 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class BrandColors {
|
||||
/// ![](https://www.colorhexa.com/093CEF.png)
|
||||
static const Color blue = Color(0xFF093CEF);
|
||||
static const Color white = Colors.white;
|
||||
static const Color black = Colors.black;
|
||||
|
||||
/// ![](https://www.colorhexa.com/555555.png)
|
||||
static const Color gray1 = Color(0xFF555555);
|
||||
|
||||
/// ![](https://www.colorhexa.com/7C7C7C.png)
|
||||
static const Color gray2 = Color(0xFF7C7C7C);
|
||||
|
||||
/// ![](https://www.colorhexa.com/fafafa.png)
|
||||
static const Color gray3 = Color(0xFFFAFAFA);
|
||||
|
||||
/// ![](https://www.colorhexa.com/DDDDDD.png)
|
||||
static const Color gray4 = Color(0xFFDDDDDD);
|
||||
|
||||
/// ![](https://www.colorhexa.com/EDEEF1.png)
|
||||
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 red = Color(0xFFFA0E0E);
|
||||
static const Color red1 = Color(0xFFFA0E0E);
|
||||
static const Color red2 = Color(0xFFE65527);
|
||||
|
||||
/// ![](https://www.colorhexa.com/00AF54.png)
|
||||
static const Color green1 = Color(0xFF00AF54);
|
||||
|
||||
/// ![](https://www.colorhexa.com/0F8849.png)
|
||||
static const Color green2 = Color(0xFF0F8849);
|
||||
|
||||
static get navBackgroundLight => white.withOpacity(0.8);
|
||||
|
@ -60,5 +50,5 @@ class BrandColors {
|
|||
static const textColor1 = black;
|
||||
static const textColor2 = gray1;
|
||||
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)),
|
||||
borderSide: BorderSide(
|
||||
width: 1,
|
||||
color: BrandColors.red,
|
||||
color: BrandColors.red1,
|
||||
),
|
||||
),
|
||||
focusedErrorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(4)),
|
||||
borderSide: BorderSide(
|
||||
width: 1,
|
||||
color: BrandColors.red,
|
||||
color: BrandColors.red1,
|
||||
),
|
||||
),
|
||||
errorStyle: GoogleFonts.inter(
|
||||
textStyle: TextStyle(
|
||||
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(
|
||||
fontSize: 24,
|
||||
fontWeight: NamedFontWeight.extraBold,
|
||||
color: BrandColors.headlineColor,
|
||||
);
|
||||
|
||||
final onboardingTitle = GoogleFonts.inter(
|
||||
fontSize: 30,
|
||||
fontWeight: NamedFontWeight.extraBold,
|
||||
color: BrandColors.headlineColor,
|
||||
|
@ -40,6 +46,8 @@ final body2Style = defaultTextStyle.copyWith(
|
|||
color: BrandColors.textColor2,
|
||||
);
|
||||
|
||||
final mediumStyle = defaultTextStyle.copyWith(fontSize: 13, height: 1.53);
|
||||
|
||||
final smallStyle = defaultTextStyle.copyWith(fontSize: 11, height: 1.45);
|
||||
|
||||
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:equatable/equatable.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:selfprivacy/config/hive_config.dart';
|
||||
export 'package:provider/provider.dart';
|
||||
|
||||
part 'app_settings_state.dart';
|
||||
|
||||
class AppSettingsCubit extends Cubit<AppSettingsState> {
|
||||
AppSettingsCubit({
|
||||
bool isDarkModeOn,
|
||||
@required bool isDarkModeOn,
|
||||
@required bool isOnbordingShowing,
|
||||
}) : super(
|
||||
AppSettingsState(isDarkModeOn: isDarkModeOn),
|
||||
AppSettingsState(
|
||||
isDarkModeOn: isDarkModeOn,
|
||||
isOnbordingShowing: isOnbordingShowing,
|
||||
),
|
||||
);
|
||||
|
||||
void update({@required bool isDarkModeOn}) {
|
||||
emit(AppSettingsState(isDarkModeOn: isDarkModeOn));
|
||||
Box box = Hive.box(BNames.appSettings);
|
||||
|
||||
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 {
|
||||
const AppSettingsState({
|
||||
@required this.isDarkModeOn,
|
||||
@required this.isOnbordingShowing,
|
||||
});
|
||||
|
||||
final bool isDarkModeOn;
|
||||
final bool isOnbordingShowing;
|
||||
|
||||
AppSettingsState copyWith({isDarkModeOn, isOnbordingShowing}) =>
|
||||
AppSettingsState(
|
||||
isDarkModeOn: isDarkModeOn ?? this.isDarkModeOn,
|
||||
isOnbordingShowing: isOnbordingShowing ?? this.isOnbordingShowing,
|
||||
);
|
||||
|
||||
@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 '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 {
|
||||
HetznerApi apiClient = HetznerApi();
|
||||
|
||||
HetznerFormCubit(this.initializingCubit) {
|
||||
var regExp = RegExp(r"\s+|[-!$%^&*()_@+|~=`{}\[\]:" ";<>?,.\/]");
|
||||
var regExp = RegExp(r"\s+|[-!$%^&*()_@+|~=`{}\[\]:<>?,.\/]");
|
||||
apiKey = FieldCubit(
|
||||
initalValue: '',
|
||||
validations: [
|
||||
RequiredStringValidation('required'),
|
||||
ValidationModel<String>(
|
||||
(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
|
||||
FutureOr<void> onSubmit() async {
|
||||
print(apiKey.state.value);
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
// initializingCubit.setHetznerKey(apiKey.state.value);
|
||||
initializingCubit.setHetznerKey(apiKey.state.value);
|
||||
}
|
||||
|
||||
final InitializingCubit initializingCubit;
|
||||
final AppConfigCubit initializingCubit;
|
||||
|
||||
FieldCubit<String> apiKey;
|
||||
}
|
||||
|
||||
class LegnthStringValidation extends ValidationModel<String> {
|
||||
LegnthStringValidation(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;
|
||||
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,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:equatable/equatable.dart';
|
||||
import 'package:selfprivacy/logic/models/user.dart';
|
||||
import 'package:selfprivacy/utils/password_generator.dart';
|
||||
export 'package:provider/provider.dart';
|
||||
|
||||
part 'users_state.dart';
|
||||
|
||||
class UsersCubit extends Cubit<UsersState> {
|
||||
UsersCubit() : super(UsersState(initMockUsers));
|
||||
UsersCubit() : super(UsersState([]));
|
||||
|
||||
void add(User user) {
|
||||
var users = state.users;
|
||||
|
@ -24,20 +23,20 @@ class UsersCubit extends Cubit<UsersState> {
|
|||
}
|
||||
}
|
||||
|
||||
final initMockUsers = <User>[
|
||||
User(login: 'Heartbreaking.Goose', password: genPass()),
|
||||
User(login: 'Alma.lawson', password: genPass()),
|
||||
User(login: 'Bee.gees', password: genPass()),
|
||||
User(login: 'Bim.jennings', password: genPass()),
|
||||
User(login: 'Debra.holt', password: genPass()),
|
||||
User(login: 'Georgia.young', password: genPass()),
|
||||
User(login: 'Kenzi.lawson', password: genPass()),
|
||||
User(login: 'Le.jennings', password: genPass()),
|
||||
User(login: 'Kirill.Zh', password: genPass()),
|
||||
User(login: 'Tina.Bolton', password: genPass()),
|
||||
User(login: 'Rebekah.Lynn', password: genPass()),
|
||||
User(login: 'Aleena.Armstrong', password: genPass()),
|
||||
User(login: 'Rosemary.Williams', password: genPass()),
|
||||
User(login: 'Sullivan.Nixon', password: genPass()),
|
||||
User(login: 'Aleena.Armstrong', password: genPass()),
|
||||
];
|
||||
// final initMockUsers = <User>[
|
||||
// User(login: 'Heartbreaking.Goose', password: genPass()),
|
||||
// User(login: 'Alma.lawson', password: genPass()),
|
||||
// User(login: 'Bee.gees', password: genPass()),
|
||||
// User(login: 'Bim.jennings', password: genPass()),
|
||||
// User(login: 'Debra.holt', password: genPass()),
|
||||
// User(login: 'Georgia.young', password: genPass()),
|
||||
// User(login: 'Kenzi.lawson', password: genPass()),
|
||||
// User(login: 'Le.jennings', password: genPass()),
|
||||
// User(login: 'Kirill.Zh', password: genPass()),
|
||||
// User(login: 'Tina.Bolton', password: genPass()),
|
||||
// User(login: 'Rebekah.Lynn', password: genPass()),
|
||||
// User(login: 'Aleena.Armstrong', password: genPass()),
|
||||
// User(login: 'Rosemary.Williams', password: genPass()),
|
||||
// User(login: 'Sullivan.Nixon', password: genPass()),
|
||||
// User(login: 'Aleena.Armstrong', password: genPass()),
|
||||
// ];
|
||||
|
|
|
@ -7,4 +7,6 @@ class UsersState extends Equatable {
|
|||
|
||||
@override
|
||||
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:flutter/foundation.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 {
|
||||
User({
|
||||
@required this.login,
|
||||
@required this.password,
|
||||
});
|
||||
|
||||
@HiveField(0)
|
||||
final String login;
|
||||
|
||||
@HiveField(1)
|
||||
final String password;
|
||||
|
||||
@override
|
||||
List<Object> get props => [login, password];
|
||||
|
||||
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/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:easy_localization/easy_localization.dart';
|
||||
import 'package:selfprivacy/ui/pages/rootRoute.dart';
|
||||
|
||||
import 'config/bloc_config.dart';
|
||||
import 'config/brand_theme.dart';
|
||||
import 'config/localization.dart';
|
||||
import 'logic/cubit/app_settings/app_settings_cubit.dart';
|
||||
|
||||
void main() {
|
||||
void main() async {
|
||||
await HiveConfig.init();
|
||||
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
runApp(
|
||||
|
@ -21,11 +24,11 @@ void main() {
|
|||
);
|
||||
}
|
||||
|
||||
var _showOnbording = true;
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var appSettings = context.watch<AppSettingsCubit>().state;
|
||||
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle.light, // Manually changnig appbar color
|
||||
child: MaterialApp(
|
||||
|
@ -34,10 +37,10 @@ class MyApp extends StatelessWidget {
|
|||
locale: context.locale,
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'SelfPrivacy',
|
||||
theme: context.watch<AppSettingsCubit>().state.isDarkModeOn
|
||||
? darkTheme
|
||||
: ligtTheme,
|
||||
home: _showOnbording ? OnboardingPage() : RootPage(),
|
||||
theme: appSettings.isDarkModeOn ? darkTheme : ligtTheme,
|
||||
home: appSettings.isOnbordingShowing
|
||||
? OnboardingPage(nextPage: InitializingPage())
|
||||
: RootPage(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,21 +1,18 @@
|
|||
import 'package:flutter/material.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';
|
||||
|
||||
class BrandCard extends StatelessWidget {
|
||||
const BrandCard({
|
||||
Key key,
|
||||
this.child,
|
||||
this.isBlocked = false,
|
||||
}) : super(key: key);
|
||||
|
||||
final Widget child;
|
||||
final bool isBlocked;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget res = Container(
|
||||
return Container(
|
||||
margin: EdgeInsets.only(bottom: 30),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
|
@ -29,44 +26,5 @@ class BrandCard extends StatelessWidget {
|
|||
),
|
||||
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
|
||||
? BrandColors.white
|
||||
: BrandColors.black;
|
||||
var color = currentIndex == index ? acitivColor : BrandColors.inactive;
|
||||
|
||||
var isActive = currentIndex == index;
|
||||
var color = isActive ? acitivColor : BrandColors.inactive;
|
||||
return InkWell(
|
||||
onTap: () => widget.controller.animateTo(index),
|
||||
child: Padding(
|
||||
|
@ -75,7 +77,22 @@ class _BrandTabBarState extends State<BrandTabBar> {
|
|||
children: [
|
||||
Icon(iconData, color: color),
|
||||
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
|
||||
body1, // normal
|
||||
body2, // with opacity
|
||||
small
|
||||
medium,
|
||||
small,
|
||||
onboardingTitle
|
||||
}
|
||||
|
||||
class BrandText extends StatelessWidget {
|
||||
const BrandText(
|
||||
this.text, {
|
||||
Key key,
|
||||
this.style,
|
||||
@required this.type,
|
||||
this.overflow,
|
||||
this.softWrap,
|
||||
}) : super(key: key);
|
||||
const BrandText(this.text,
|
||||
{Key key,
|
||||
this.style,
|
||||
@required this.type,
|
||||
this.overflow,
|
||||
this.softWrap,
|
||||
this.textAlign})
|
||||
: super(key: key);
|
||||
|
||||
final String text;
|
||||
final TextStyle style;
|
||||
final TextType type;
|
||||
final TextOverflow overflow;
|
||||
final bool softWrap;
|
||||
final TextAlign textAlign;
|
||||
|
||||
factory BrandText.h1(
|
||||
String text, {
|
||||
|
@ -38,6 +41,13 @@ class BrandText extends StatelessWidget {
|
|||
type: TextType.h1,
|
||||
style: style,
|
||||
);
|
||||
|
||||
factory BrandText.onboardingTitle(String text, {TextStyle style}) =>
|
||||
BrandText(
|
||||
text,
|
||||
type: TextType.onboardingTitle,
|
||||
style: style,
|
||||
);
|
||||
factory BrandText.h2(String text, {TextStyle style}) => BrandText(
|
||||
text,
|
||||
type: TextType.h2,
|
||||
|
@ -63,6 +73,10 @@ class BrandText extends StatelessWidget {
|
|||
type: TextType.body2,
|
||||
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(
|
||||
text,
|
||||
type: TextType.small,
|
||||
|
@ -105,6 +119,15 @@ class BrandText extends StatelessWidget {
|
|||
case TextType.small:
|
||||
style = isDark ? smallStyle.copyWith(color: Colors.white) : smallStyle;
|
||||
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) {
|
||||
style = style.merge(this.style);
|
||||
|
@ -114,6 +137,7 @@ class BrandText extends StatelessWidget {
|
|||
style: style,
|
||||
overflow: overflow,
|
||||
softWrap: softWrap,
|
||||
textAlign: textAlign,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
|||
import 'package:selfprivacy/config/brand_colors.dart';
|
||||
import 'package:selfprivacy/logic/models/state_types.dart';
|
||||
|
||||
class IconStatusMaks extends StatelessWidget {
|
||||
IconStatusMaks({this.child, this.status});
|
||||
class IconStatusMask extends StatelessWidget {
|
||||
IconStatusMask({this.child, this.status});
|
||||
final Icon child;
|
||||
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
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(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
BrandText.h4('Progress'),
|
||||
BrandText.h2('Progress'),
|
||||
SizedBox(height: 10),
|
||||
Row(
|
||||
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(),
|
||||
),
|
||||
Row(children: even),
|
||||
SizedBox(height: 3),
|
||||
Container(
|
||||
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:selfprivacy/config/brand_theme.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/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/models/user.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_card/brand_card.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_modal_sheet/brand_modal_sheet.dart';
|
||||
|
@ -16,96 +18,71 @@ import 'package:selfprivacy/ui/components/progress_bar/progress_bar.dart';
|
|||
import 'package:selfprivacy/ui/pages/rootRoute.dart';
|
||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||
|
||||
class InitializingPage extends StatefulWidget {
|
||||
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();
|
||||
}
|
||||
|
||||
class InitializingPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var cubit = context.watch<InitializingCubit>();
|
||||
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
body: ListView(
|
||||
children: [
|
||||
Padding(
|
||||
padding: brandPagePadding1,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
BrandText.h4('Начало'),
|
||||
BrandText.h1('SelfPrivacy'),
|
||||
SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text:
|
||||
'Для устойчивости и приватности требует много учёток. Полная инструкция на ',
|
||||
style: body2Style,
|
||||
),
|
||||
BrandSpanButton.link(
|
||||
text:
|
||||
'https://selfprivacy.org/posts/getting_started/',
|
||||
urlString:
|
||||
'https://selfprivacy.org/posts/getting_started/',
|
||||
),
|
||||
var cubit = context.watch<AppConfigCubit>();
|
||||
var actualPage = [
|
||||
_stepHetzner(cubit),
|
||||
_stepCloudflare(cubit),
|
||||
_stepDomain(cubit),
|
||||
_stepUser(cubit),
|
||||
_stepServer(cubit),
|
||||
Container(child: Text('Everythigng is initialized'))
|
||||
][cubit.state.progress];
|
||||
return BlocListener<AppConfigCubit, AppConfigState>(
|
||||
listener: (context, state) {
|
||||
if (state.isFullyInitilized) {
|
||||
Navigator.of(context).pushReplacement(materialRoute(RootPage()));
|
||||
}
|
||||
},
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
body: ListView(
|
||||
children: [
|
||||
Padding(
|
||||
padding: brandPagePadding1,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ProgressBar(
|
||||
steps: [
|
||||
'Hetzner',
|
||||
'CloudFlare',
|
||||
'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),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 500,
|
||||
child: PageView(
|
||||
// physics: NeverScrollableScrollPhysics(),
|
||||
controller: pageController,
|
||||
children: [
|
||||
_addCard(_stepOne(cubit)),
|
||||
_addCard(_stepTwo(cubit)),
|
||||
_addCard(_stepThree(cubit)),
|
||||
_addCard(_stepFour(cubit)),
|
||||
],
|
||||
_addCard(
|
||||
AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 300),
|
||||
child: actualPage,
|
||||
),
|
||||
),
|
||||
),
|
||||
BrandButton.text(title: 'Настрою потом', onPressed: _goToMainPage),
|
||||
SizedBox(height: 30),
|
||||
],
|
||||
BrandButton.text(
|
||||
title:
|
||||
cubit.state.isFullyInitilized ? 'Close' : 'Настрою потом',
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
materialRoute(RootPage()),
|
||||
(predicate) => predicate == null,
|
||||
);
|
||||
}),
|
||||
SizedBox(height: 30),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _goToMainPage() {
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
materialRoute(RootPage()),
|
||||
(predicate) => predicate == null,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _stepOne(InitializingCubit initializingCubit) {
|
||||
Widget _stepHetzner(AppConfigCubit initializingCubit) {
|
||||
return BlocProvider(
|
||||
create: (context) => HetznerFormCubit(initializingCubit),
|
||||
child: Builder(builder: (context) {
|
||||
|
@ -124,19 +101,17 @@ class _InitializingPageState extends State<InitializingPage> {
|
|||
CubitFormTextField(
|
||||
formFieldCubit: formCubit.apiKey,
|
||||
textAlign: TextAlign.center,
|
||||
keyboardType: TextInputType.number,
|
||||
scrollPadding: EdgeInsets.only(bottom: 70),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Hetzner API Token',
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Spacer(),
|
||||
BrandButton.rised(
|
||||
onPressed:
|
||||
formCubit.state.isSubmitting ? null : formCubit.trySubmit,
|
||||
title: 'Подключить',
|
||||
),
|
||||
Spacer(),
|
||||
SizedBox(height: 10),
|
||||
BrandButton.text(
|
||||
onPressed: () => _showModal(context, _HowHetzner()),
|
||||
|
@ -159,110 +134,161 @@ class _InitializingPageState extends State<InitializingPage> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _stepTwo(InitializingCubit cubit) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Image.asset('assets/images/logos/cloudflare.png'),
|
||||
BrandText.h2('Подключите CloudFlare DNS'),
|
||||
SizedBox(height: 10),
|
||||
BrandText.body2('Для управления DNS вашего домена'),
|
||||
Expanded(
|
||||
child: _MockForm(
|
||||
hintText: 'CloudFlare API Token',
|
||||
length: 64,
|
||||
onPressed: () {
|
||||
cubit.setCloudFlare('key');
|
||||
pageController.animateToPage(
|
||||
2,
|
||||
curve: Curves.easeIn,
|
||||
duration: Duration(milliseconds: 200),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
BrandButton.text(
|
||||
onPressed: () {},
|
||||
title: 'Как получить API Token',
|
||||
),
|
||||
],
|
||||
Widget _stepCloudflare(AppConfigCubit initializingCubit) {
|
||||
return BlocProvider(
|
||||
create: (context) => CloudFlareFormCubit(initializingCubit),
|
||||
child: Builder(builder: (context) {
|
||||
var formCubit = context.watch<CloudFlareFormCubit>();
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Spacer(),
|
||||
Image.asset('assets/images/logos/cloudflare.png'),
|
||||
BrandText.h2('Подключите CloudFlare'),
|
||||
SizedBox(height: 10),
|
||||
BrandText.body2('Для управления DNS вашего домена'),
|
||||
Spacer(),
|
||||
CubitFormTextField(
|
||||
formFieldCubit: formCubit.apiKey,
|
||||
textAlign: TextAlign.center,
|
||||
scrollPadding: EdgeInsets.only(bottom: 70),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'CloudFlare API Token',
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
BrandButton.rised(
|
||||
onPressed:
|
||||
formCubit.state.isSubmitting ? null : formCubit.trySubmit,
|
||||
title: 'Подключить',
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
BrandButton.text(
|
||||
onPressed: () {},
|
||||
title: 'Как получить API Token',
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _stepThree(InitializingCubit cubit) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 10),
|
||||
BrandText.h2('Введите домен:'),
|
||||
Expanded(
|
||||
child: _MockForm(
|
||||
hintText: 'домен',
|
||||
length: 10,
|
||||
onPressed: () {
|
||||
cubit.setDomain('domain');
|
||||
pageController.animateToPage(
|
||||
3,
|
||||
curve: Curves.easeIn,
|
||||
duration: Duration(milliseconds: 200),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
BrandButton.text(
|
||||
onPressed: () => _showModal(context, _HowHetzner()),
|
||||
title: 'Как получить API Token',
|
||||
),
|
||||
],
|
||||
Widget _stepDomain(AppConfigCubit initializingCubit) {
|
||||
return BlocProvider(
|
||||
create: (context) => DomainFormCubit(initializingCubit),
|
||||
child: Builder(builder: (context) {
|
||||
var formCubit = context.watch<DomainFormCubit>();
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Spacer(),
|
||||
BrandText.h2('Введите домен:'),
|
||||
SizedBox(height: 10),
|
||||
CubitFormTextField(
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
formFieldCubit: formCubit.domainName,
|
||||
textAlign: TextAlign.center,
|
||||
scrollPadding: EdgeInsets.only(bottom: 70),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Домен',
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
BrandButton.rised(
|
||||
onPressed:
|
||||
formCubit.state.isSubmitting ? null : formCubit.trySubmit,
|
||||
title: 'Подключить',
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
BrandButton.text(
|
||||
onPressed: () => _showModal(context, _HowHetzner()),
|
||||
title: 'Как получить API Token',
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _stepFour(InitializingCubit cubit) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
TextField(
|
||||
decoration: InputDecoration(
|
||||
hintText: 'нинейм',
|
||||
),
|
||||
Widget _stepUser(AppConfigCubit initializingCubit) {
|
||||
return BlocProvider(
|
||||
create: (context) => UserFormCubit(initializingCubit),
|
||||
child: Builder(builder: (context) {
|
||||
var formCubit = context.watch<UserFormCubit>();
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Spacer(),
|
||||
SizedBox(height: 10),
|
||||
CubitFormTextField(
|
||||
formFieldCubit: formCubit.userName,
|
||||
textAlign: TextAlign.center,
|
||||
scrollPadding: EdgeInsets.only(bottom: 70),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Никнейм',
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
TextField(
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'пароль',
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
CubitFormTextField(
|
||||
formFieldCubit: formCubit.password,
|
||||
textAlign: TextAlign.center,
|
||||
scrollPadding: EdgeInsets.only(bottom: 70),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Пароль',
|
||||
),
|
||||
Spacer(),
|
||||
BrandButton.rised(
|
||||
onPressed: () {
|
||||
cubit.setRootUser(
|
||||
User(login: 'aa', password: 'bbb'),
|
||||
);
|
||||
_goToMainPage();
|
||||
},
|
||||
title: 'some text',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
BrandButton.text(
|
||||
onPressed: () => _showModal(context, _HowHetzner()),
|
||||
title: 'Как получить API Token',
|
||||
),
|
||||
],
|
||||
),
|
||||
Spacer(),
|
||||
BrandButton.rised(
|
||||
onPressed:
|
||||
formCubit.state.isSubmitting ? null : formCubit.trySubmit,
|
||||
title: 'Подключить',
|
||||
),
|
||||
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) {
|
||||
return Padding(
|
||||
return Container(
|
||||
height: 500,
|
||||
padding: brandPagePadding2,
|
||||
child: BrandCard(
|
||||
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_theme.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_header/brand_header.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
|
||||
import 'package:selfprivacy/utils/named_font_weight.dart';
|
||||
|
||||
class AppSettingsPage extends StatefulWidget {
|
||||
const AppSettingsPage({Key key}) : super(key: key);
|
||||
|
@ -53,11 +55,75 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
|
|||
activeColor: BrandColors.green1,
|
||||
activeTrackColor: BrandColors.green2,
|
||||
value: Theme.of(context).brightness == Brightness.dark,
|
||||
onChanged: (value) =>
|
||||
appSettings.update(isDarkModeOn: !isDarkModeOn),
|
||||
onChanged: (value) => appSettings.updateDarkMode(
|
||||
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),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
BrandText.body1(
|
||||
title,
|
||||
style: TextStyle(color: hasWarning ? BrandColors.warning : null),
|
||||
),
|
||||
BrandText.body1(value,
|
||||
style: TextStyle(
|
||||
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_text/brand_text.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 'about/about.dart';
|
||||
|
@ -49,6 +51,11 @@ class MorePage extends StatelessWidget {
|
|||
iconData: BrandIcons.help,
|
||||
goTo: InfoPage(),
|
||||
),
|
||||
_NavItem(
|
||||
title: 'Onboarding',
|
||||
iconData: BrandIcons.triangle,
|
||||
goTo: OnboardingPage(nextPage: RootPage()),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
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_text/brand_text.dart';
|
||||
import 'package:selfprivacy/ui/pages/initializing/initializing.dart';
|
||||
import 'package:selfprivacy/utils/route_transitions/basic.dart';
|
||||
|
||||
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
|
||||
_OnboardingPageState createState() => _OnboardingPageState();
|
||||
}
|
||||
|
@ -107,8 +108,9 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
|||
),
|
||||
BrandButton.rised(
|
||||
onPressed: () {
|
||||
context.read<AppSettingsCubit>().turnOffOnboarding();
|
||||
Navigator.of(context)
|
||||
.pushReplacement(materialRoute(InitializingPage()));
|
||||
.pushReplacement(materialRoute(widget.nextPage));
|
||||
},
|
||||
title: 'Понял',
|
||||
),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:flutter/material.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/models/provider.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_text/brand_text.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/utils/route_transitions/basic.dart';
|
||||
|
||||
|
@ -22,9 +23,12 @@ class ProvidersPage extends StatefulWidget {
|
|||
class _ProvidersPageState extends State<ProvidersPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized;
|
||||
|
||||
final cards = ProviderType.values
|
||||
.map((type) =>
|
||||
_Card(provider: ProviderModel(state: StateType.stable, type: type)))
|
||||
.map((type) => _Card(
|
||||
provider:
|
||||
ProviderModel(state: StateType.uninitialized, type: type)))
|
||||
.toList();
|
||||
return Scaffold(
|
||||
appBar: PreferredSize(
|
||||
|
@ -33,7 +37,13 @@ class _ProvidersPageState extends State<ProvidersPage> {
|
|||
),
|
||||
body: ListView(
|
||||
padding: brandPagePadding2,
|
||||
children: cards,
|
||||
children: [
|
||||
if (!isReady) ...[
|
||||
NotReadyCard(),
|
||||
SizedBox(height: 24),
|
||||
],
|
||||
...cards,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -48,8 +58,6 @@ class _Card extends StatelessWidget {
|
|||
String title;
|
||||
String message;
|
||||
String stableText;
|
||||
var isFullyInitilized =
|
||||
context.watch<InitializingCubit>().state.isFullyInitilized;
|
||||
|
||||
switch (provider.type) {
|
||||
case ProviderType.server:
|
||||
|
@ -80,11 +88,10 @@ class _Card extends StatelessWidget {
|
|||
},
|
||||
),
|
||||
child: BrandCard(
|
||||
isBlocked: !isFullyInitilized,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
IconStatusMaks(
|
||||
IconStatusMask(
|
||||
status: provider.state,
|
||||
child: Icon(provider.icon, size: 30, color: Colors.white),
|
||||
),
|
||||
|
@ -176,7 +183,7 @@ class _ProviderDetails extends StatelessWidget {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 13),
|
||||
IconStatusMaks(
|
||||
IconStatusMask(
|
||||
status: provider.state,
|
||||
child:
|
||||
Icon(provider.icon, size: 40, color: Colors.white),
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import 'package:flutter/cupertino.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_text/brand_text.dart';
|
||||
import 'package:selfprivacy/ui/pages/more/more.dart';
|
||||
import 'package:selfprivacy/ui/pages/providers/providers.dart';
|
||||
import 'package:selfprivacy/ui/pages/services/services.dart';
|
||||
|
@ -34,9 +31,6 @@ class _RootPageState extends State<RootPage>
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var isUserFilled =
|
||||
context.watch<InitializingCubit>().state.isFullyInitilized;
|
||||
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
body: TabBarView(
|
||||
|
@ -44,7 +38,7 @@ class _RootPageState extends State<RootPage>
|
|||
children: [
|
||||
ProvidersPage(),
|
||||
ServicesPage(),
|
||||
isUserFilled ? UsersPage() : _NotReady(),
|
||||
UsersPage(),
|
||||
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: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/models/service.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_text/brand_text.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 {
|
||||
ServicesPage({Key key}) : super(key: key);
|
||||
|
@ -23,6 +24,8 @@ class _ServicesPageState extends State<ServicesPage> {
|
|||
final serviceCubit = context.watch<ServicesCubit>();
|
||||
final connected = serviceCubit.state.connected;
|
||||
final uninitialized = serviceCubit.state.uninitialized;
|
||||
var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized;
|
||||
|
||||
return Scaffold(
|
||||
appBar: PreferredSize(
|
||||
child: BrandHeader(title: 'Сервисы'),
|
||||
|
@ -31,6 +34,7 @@ class _ServicesPageState extends State<ServicesPage> {
|
|||
body: ListView(
|
||||
padding: brandPagePadding2,
|
||||
children: [
|
||||
if (!isReady) NotReadyCard(),
|
||||
SizedBox(height: 24),
|
||||
...connected.map((service) => _Card(service: service)).toList(),
|
||||
if (uninitialized.isNotEmpty) ...[
|
||||
|
@ -53,7 +57,6 @@ class _Card extends StatelessWidget {
|
|||
String title;
|
||||
IconData iconData;
|
||||
String description;
|
||||
var isFullyInitilized = context.watch<InitializingCubit>().state.isFullyInitilized;
|
||||
|
||||
switch (service.type) {
|
||||
case ServiceTypes.messanger:
|
||||
|
@ -85,11 +88,10 @@ class _Card extends StatelessWidget {
|
|||
break;
|
||||
}
|
||||
return BrandCard(
|
||||
isBlocked: !isFullyInitilized,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
IconStatusMaks(
|
||||
IconStatusMask(
|
||||
status: service.state,
|
||||
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:selfprivacy/config/brand_colors.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/models/user.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_modal_sheet/brand_modal_sheet.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';
|
||||
|
||||
part 'fab.dart';
|
||||
part 'new_user.dart';
|
||||
part 'user_details.dart';
|
||||
part 'user.dart';
|
||||
part 'empty.dart';
|
||||
|
||||
class UsersPage extends StatelessWidget {
|
||||
const UsersPage({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final usersCubit = context.watch<UsersCubit>();
|
||||
var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized;
|
||||
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(
|
||||
appBar: PreferredSize(
|
||||
child: BrandHeader(title: 'Пользователи'),
|
||||
preferredSize: Size.fromHeight(52),
|
||||
),
|
||||
floatingActionButton: 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();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
...users.map((user) => _User(user: user)),
|
||||
],
|
||||
),
|
||||
floatingActionButton: isReady ? _Fab() : null,
|
||||
body: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
],
|
||||
Widget isNotReady() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: NotReadyCard(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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),
|
||||
],
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: Center(
|
||||
child: _NoUsers(
|
||||
text:
|
||||
'Подключите сервер, домен и DNS в разеде Провайдеры, чтобы добавить первого пользователя',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -36,6 +50,62 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -50,6 +120,20 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -57,6 +141,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -72,7 +163,7 @@ packages:
|
|||
source: hosted
|
||||
version: "2.1.1"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: crypto
|
||||
url: "https://pub.dartlang.org"
|
||||
|
@ -84,7 +175,7 @@ packages:
|
|||
name: cubit_form
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.0.14"
|
||||
version: "0.0.15"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -92,6 +183,27 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -134,6 +246,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.2.1"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.10.11"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
|
@ -158,6 +277,13 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
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:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
|
@ -168,6 +294,13 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
google_fonts:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -175,6 +308,34 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -182,6 +343,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -203,6 +371,41 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -224,6 +427,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0-nullsafety.3"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.9.7"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -231,6 +441,27 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -308,6 +539,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pool
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -322,6 +560,27 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -364,6 +623,20 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -376,6 +649,13 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -397,6 +677,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -418,6 +705,20 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -481,6 +782,20 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -510,5 +825,5 @@ packages:
|
|||
source: hosted
|
||||
version: "2.2.1"
|
||||
sdks:
|
||||
dart: ">=2.10.0-110 <2.11.0"
|
||||
dart: ">=2.10.0 <2.11.0"
|
||||
flutter: ">=1.22.0 <2.0.0"
|
||||
|
|
14
pubspec.yaml
|
@ -9,12 +9,18 @@ environment:
|
|||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
cubit_form: ^0.0.14
|
||||
crypto: ^2.1.5
|
||||
cubit_form: ^0.0.15
|
||||
cupertino_icons: ^1.0.0
|
||||
dio: ^3.0.10
|
||||
easy_localization: ^2.3.3
|
||||
equatable: ^1.2.5
|
||||
flutter_bloc: ^6.1.1
|
||||
flutter_secure_storage: ^3.3.5
|
||||
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
|
||||
provider: ^4.3.2+2
|
||||
url_launcher: ^5.7.10
|
||||
|
@ -22,12 +28,16 @@ dependencies:
|
|||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
build_runner: ^1.10.11
|
||||
flutter_launcher_icons: ^0.8.1
|
||||
hive_generator: ^0.8.2
|
||||
json_serializable: ^3.5.1
|
||||
|
||||
flutter_icons:
|
||||
android: "launcher_icon"
|
||||
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:
|
||||
uses-material-design: true
|
||||
|
|