fdroid
Kherel 2021-01-06 18:35:57 +01:00
parent 6613949d12
commit 9a749cf006
80 changed files with 2190 additions and 1022 deletions

View File

@ -39,7 +39,7 @@ android {
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "pro.kherel.selfprivacy" applicationId "pro.kherel.selfprivacy"
minSdkVersion 16 minSdkVersion 18
targetSdkVersion 29 targetSdkVersion 29
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName

View File

@ -15,11 +15,9 @@
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize"
<!-- Specifies an Android theme to apply to this Activity as soon as android:allowBackup="false" >
the Android process has started. This theme is visible to the user <!-- https://github.com/mogol/flutter_secure_storage allowBackup="false" -->
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data <meta-data
android:name="io.flutter.embedding.android.NormalTheme" android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" android:resource="@style/NormalTheme"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 885 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -1,5 +1,7 @@
PODS: PODS:
- Flutter (1.0.0) - Flutter (1.0.0)
- flutter_secure_storage (3.3.1):
- Flutter
- package_info (0.0.1): - package_info (0.0.1):
- Flutter - Flutter
- path_provider (0.0.1): - path_provider (0.0.1):
@ -11,6 +13,7 @@ PODS:
DEPENDENCIES: DEPENDENCIES:
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- package_info (from `.symlinks/plugins/package_info/ios`) - package_info (from `.symlinks/plugins/package_info/ios`)
- path_provider (from `.symlinks/plugins/path_provider/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`)
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
@ -19,6 +22,8 @@ DEPENDENCIES:
EXTERNAL SOURCES: EXTERNAL SOURCES:
Flutter: Flutter:
:path: Flutter :path: Flutter
flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
package_info: package_info:
:path: ".symlinks/plugins/package_info/ios" :path: ".symlinks/plugins/package_info/ios"
path_provider: path_provider:
@ -30,6 +35,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS: SPEC CHECKSUMS:
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 670 B

After

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 608 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 906 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 992 B

After

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 907 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 608 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -1,8 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
import 'package:selfprivacy/logic/cubit/initializing/initializing_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart'; import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart'; import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
@ -14,14 +13,22 @@ class BlocAndProviderConfig extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var platformBrightness = // var platformBrightness =
SchedulerBinding.instance.window.platformBrightness; // SchedulerBinding.instance.window.platformBrightness;
var isDark = platformBrightness == Brightness.dark; // var isDark = platformBrightness == Brightness.dark;
// var platformBrightness = Brightness.dark; var isDark = false;
return MultiProvider( return MultiProvider(
providers: [ providers: [
BlocProvider(create: (_) => AppSettingsCubit(isDarkModeOn: isDark)), BlocProvider(
BlocProvider(create: (_) => InitializingCubit()), create: (_) => AppSettingsCubit(
isDarkModeOn: isDark,
isOnbordingShowing: true,
)..load(),
),
BlocProvider(
create: (_) => AppConfigCubit()..load(),
),
BlocProvider(create: (_) => ServicesCubit()), BlocProvider(create: (_) => ServicesCubit()),
BlocProvider(create: (_) => ProvidersCubit()), BlocProvider(create: (_) => ProvidersCubit()),
BlocProvider(create: (_) => UsersCubit()), BlocProvider(create: (_) => UsersCubit()),

View File

@ -1,33 +1,23 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class BrandColors { class BrandColors {
/// ![](https://www.colorhexa.com/093CEF.png)
static const Color blue = Color(0xFF093CEF); static const Color blue = Color(0xFF093CEF);
static const Color white = Colors.white; static const Color white = Colors.white;
static const Color black = Colors.black; static const Color black = Colors.black;
/// ![](https://www.colorhexa.com/555555.png)
static const Color gray1 = Color(0xFF555555); static const Color gray1 = Color(0xFF555555);
/// ![](https://www.colorhexa.com/7C7C7C.png)
static const Color gray2 = Color(0xFF7C7C7C); static const Color gray2 = Color(0xFF7C7C7C);
/// ![](https://www.colorhexa.com/fafafa.png)
static const Color gray3 = Color(0xFFFAFAFA); static const Color gray3 = Color(0xFFFAFAFA);
/// ![](https://www.colorhexa.com/DDDDDD.png)
static const Color gray4 = Color(0xFFDDDDDD); static const Color gray4 = Color(0xFFDDDDDD);
/// ![](https://www.colorhexa.com/EDEEF1.png)
static const Color gray5 = Color(0xFFEDEEF1); static const Color gray5 = Color(0xFFEDEEF1);
static Color gray6 = Color(0xFF181818).withOpacity(0.7);
static const Color grey7 = Color(0xFFABABAB);
/// ![](https://www.colorhexa.com/FA0E0E.png) static const Color red1 = Color(0xFFFA0E0E);
static const Color red = Color(0xFFFA0E0E); static const Color red2 = Color(0xFFE65527);
/// ![](https://www.colorhexa.com/00AF54.png)
static const Color green1 = Color(0xFF00AF54); static const Color green1 = Color(0xFF00AF54);
/// ![](https://www.colorhexa.com/0F8849.png)
static const Color green2 = Color(0xFF0F8849); static const Color green2 = Color(0xFF0F8849);
static get navBackgroundLight => white.withOpacity(0.8); static get navBackgroundLight => white.withOpacity(0.8);
@ -60,5 +50,5 @@ class BrandColors {
static const textColor1 = black; static const textColor1 = black;
static const textColor2 = gray1; static const textColor2 = gray1;
static const dividerColor = gray5; static const dividerColor = gray5;
static const warning = red; static const warning = red1;
} }

View File

@ -23,20 +23,20 @@ final ligtTheme = ThemeData(
borderRadius: BorderRadius.all(Radius.circular(4)), borderRadius: BorderRadius.all(Radius.circular(4)),
borderSide: BorderSide( borderSide: BorderSide(
width: 1, width: 1,
color: BrandColors.red, color: BrandColors.red1,
), ),
), ),
focusedErrorBorder: OutlineInputBorder( focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(4)), borderRadius: BorderRadius.all(Radius.circular(4)),
borderSide: BorderSide( borderSide: BorderSide(
width: 1, width: 1,
color: BrandColors.red, color: BrandColors.red1,
), ),
), ),
errorStyle: GoogleFonts.inter( errorStyle: GoogleFonts.inter(
textStyle: TextStyle( textStyle: TextStyle(
fontSize: 12, fontSize: 12,
color: BrandColors.red, color: BrandColors.red1,
), ),
), ),
), ),

View 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';
}

View File

@ -18,6 +18,12 @@ final headline1Style = GoogleFonts.inter(
); );
final headline2Style = GoogleFonts.inter( final headline2Style = GoogleFonts.inter(
fontSize: 24,
fontWeight: NamedFontWeight.extraBold,
color: BrandColors.headlineColor,
);
final onboardingTitle = GoogleFonts.inter(
fontSize: 30, fontSize: 30,
fontWeight: NamedFontWeight.extraBold, fontWeight: NamedFontWeight.extraBold,
color: BrandColors.headlineColor, color: BrandColors.headlineColor,
@ -40,6 +46,8 @@ final body2Style = defaultTextStyle.copyWith(
color: BrandColors.textColor2, color: BrandColors.textColor2,
); );
final mediumStyle = defaultTextStyle.copyWith(fontSize: 13, height: 1.53);
final smallStyle = defaultTextStyle.copyWith(fontSize: 11, height: 1.45); final smallStyle = defaultTextStyle.copyWith(fontSize: 11, height: 1.45);
final linkStyle = defaultTextStyle.copyWith(color: BrandColors.blue); final linkStyle = defaultTextStyle.copyWith(color: BrandColors.blue);

View File

@ -0,0 +1,11 @@
import 'package:dio/dio.dart';
abstract class ApiMap {
String rootAddress;
Dio client = Dio();
void close() {
client.close();
}
}

View 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,
// );
// }
}

View 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(),
);
}
}

View 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,
));
}
}

View 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();
}

View File

@ -1,18 +1,44 @@
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:hive/hive.dart';
import 'package:selfprivacy/config/hive_config.dart';
export 'package:provider/provider.dart'; export 'package:provider/provider.dart';
part 'app_settings_state.dart'; part 'app_settings_state.dart';
class AppSettingsCubit extends Cubit<AppSettingsState> { class AppSettingsCubit extends Cubit<AppSettingsState> {
AppSettingsCubit({ AppSettingsCubit({
bool isDarkModeOn, @required bool isDarkModeOn,
@required bool isOnbordingShowing,
}) : super( }) : super(
AppSettingsState(isDarkModeOn: isDarkModeOn), AppSettingsState(
isDarkModeOn: isDarkModeOn,
isOnbordingShowing: isOnbordingShowing,
),
); );
void update({@required bool isDarkModeOn}) { Box box = Hive.box(BNames.appSettings);
emit(AppSettingsState(isDarkModeOn: isDarkModeOn));
void load() {
bool isDarkModeOn = box.get(BNames.isDarkModeOn);
bool isOnbordingShowing = box.get(BNames.isOnbordingShowing);
emit(state.copyWith(
isDarkModeOn: isDarkModeOn,
isOnbordingShowing: isOnbordingShowing,
));
}
void updateDarkMode({@required bool isDarkModeOn}) {
box.put(BNames.isDarkModeOn, isDarkModeOn);
emit(state.copyWith(isDarkModeOn: isDarkModeOn));
}
void turnOffOnboarding() {
box.put(BNames.isOnbordingShowing, false);
emit(state.copyWith(isOnbordingShowing: false));
} }
} }

View File

@ -3,10 +3,18 @@ part of 'app_settings_cubit.dart';
class AppSettingsState extends Equatable { class AppSettingsState extends Equatable {
const AppSettingsState({ const AppSettingsState({
@required this.isDarkModeOn, @required this.isDarkModeOn,
@required this.isOnbordingShowing,
}); });
final bool isDarkModeOn; final bool isDarkModeOn;
final bool isOnbordingShowing;
AppSettingsState copyWith({isDarkModeOn, isOnbordingShowing}) =>
AppSettingsState(
isDarkModeOn: isDarkModeOn ?? this.isDarkModeOn,
isOnbordingShowing: isOnbordingShowing ?? this.isOnbordingShowing,
);
@override @override
List<Object> get props => [isDarkModeOn]; List<Object> get props => [isDarkModeOn, isOnbordingShowing];
} }

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -1,18 +1,22 @@
import 'dart:async'; import 'dart:async';
import 'package:cubit_form/cubit_form.dart'; import 'package:cubit_form/cubit_form.dart';
import 'package:selfprivacy/logic/cubit/initializing/initializing_cubit.dart'; import 'package:selfprivacy/logic/api_maps/hetzner.dart';
import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
class HetznerFormCubit extends FormCubit { class HetznerFormCubit extends FormCubit {
HetznerApi apiClient = HetznerApi();
HetznerFormCubit(this.initializingCubit) { HetznerFormCubit(this.initializingCubit) {
var regExp = RegExp(r"\s+|[-!$%^&*()_@+|~=`{}\[\]:" ";<>?,.\/]"); var regExp = RegExp(r"\s+|[-!$%^&*()_@+|~=`{}\[\]:<>?,.\/]");
apiKey = FieldCubit( apiKey = FieldCubit(
initalValue: '', initalValue: '',
validations: [ validations: [
RequiredStringValidation('required'), RequiredStringValidation('required'),
ValidationModel<String>( ValidationModel<String>(
(s) => regExp.hasMatch(s), 'invalid key format'), (s) => regExp.hasMatch(s), 'invalid key format'),
LegnthStringValidation(11, 'length is [] shoud be 11') LegnthStringValidationWithLenghShowing(64, 'length is [] shoud be 64')
], ],
); );
@ -21,24 +25,28 @@ class HetznerFormCubit extends FormCubit {
@override @override
FutureOr<void> onSubmit() async { FutureOr<void> onSubmit() async {
print(apiKey.state.value); initializingCubit.setHetznerKey(apiKey.state.value);
await Future.delayed(const Duration(milliseconds: 300));
// initializingCubit.setHetznerKey(apiKey.state.value);
} }
final InitializingCubit initializingCubit; final AppConfigCubit initializingCubit;
FieldCubit<String> apiKey; FieldCubit<String> apiKey;
}
class LegnthStringValidation extends ValidationModel<String> {
LegnthStringValidation(int length, String errorText)
: super((n) => n.length != length, errorText);
@override @override
String check(String val) { FutureOr<bool> asyncValidation() async {
var length = val.length; var isKeyValid = await apiClient.isValid(apiKey.state.value);
var errorMassage = this.errorMassage.replaceAll("[]", length.toString());
return test(val) ? errorMassage : null; if (!isKeyValid) {
apiKey.setError('bad key');
return false;
}
return true;
}
@override
Future<void> close() async {
apiClient.close();
return super.close();
} }
} }

View File

@ -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();
}
}

View 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;
}

View 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;
}
}

View File

@ -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));
}
}

View File

@ -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());
}

View File

@ -1,13 +1,12 @@
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:selfprivacy/logic/models/user.dart'; import 'package:selfprivacy/logic/models/user.dart';
import 'package:selfprivacy/utils/password_generator.dart';
export 'package:provider/provider.dart'; export 'package:provider/provider.dart';
part 'users_state.dart'; part 'users_state.dart';
class UsersCubit extends Cubit<UsersState> { class UsersCubit extends Cubit<UsersState> {
UsersCubit() : super(UsersState(initMockUsers)); UsersCubit() : super(UsersState([]));
void add(User user) { void add(User user) {
var users = state.users; var users = state.users;
@ -24,20 +23,20 @@ class UsersCubit extends Cubit<UsersState> {
} }
} }
final initMockUsers = <User>[ // final initMockUsers = <User>[
User(login: 'Heartbreaking.Goose', password: genPass()), // User(login: 'Heartbreaking.Goose', password: genPass()),
User(login: 'Alma.lawson', password: genPass()), // User(login: 'Alma.lawson', password: genPass()),
User(login: 'Bee.gees', password: genPass()), // User(login: 'Bee.gees', password: genPass()),
User(login: 'Bim.jennings', password: genPass()), // User(login: 'Bim.jennings', password: genPass()),
User(login: 'Debra.holt', password: genPass()), // User(login: 'Debra.holt', password: genPass()),
User(login: 'Georgia.young', password: genPass()), // User(login: 'Georgia.young', password: genPass()),
User(login: 'Kenzi.lawson', password: genPass()), // User(login: 'Kenzi.lawson', password: genPass()),
User(login: 'Le.jennings', password: genPass()), // User(login: 'Le.jennings', password: genPass()),
User(login: 'Kirill.Zh', password: genPass()), // User(login: 'Kirill.Zh', password: genPass()),
User(login: 'Tina.Bolton', password: genPass()), // User(login: 'Tina.Bolton', password: genPass()),
User(login: 'Rebekah.Lynn', password: genPass()), // User(login: 'Rebekah.Lynn', password: genPass()),
User(login: 'Aleena.Armstrong', password: genPass()), // User(login: 'Aleena.Armstrong', password: genPass()),
User(login: 'Rosemary.Williams', password: genPass()), // User(login: 'Rosemary.Williams', password: genPass()),
User(login: 'Sullivan.Nixon', password: genPass()), // User(login: 'Sullivan.Nixon', password: genPass()),
User(login: 'Aleena.Armstrong', password: genPass()), // User(login: 'Aleena.Armstrong', password: genPass()),
]; // ];

View File

@ -7,4 +7,6 @@ class UsersState extends Equatable {
@override @override
List<Object> get props => users; List<Object> get props => users;
bool get isEmpty => users.isEmpty;
} }

View 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';
}
}

View 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;
}

View File

@ -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];
}

View 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);
}

View 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,
};

View 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();
}

View 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;
}

View File

@ -3,18 +3,32 @@ import 'dart:ui';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:selfprivacy/utils/color_utils.dart'; import 'package:selfprivacy/utils/color_utils.dart';
import 'package:hive/hive.dart';
import 'package:selfprivacy/utils/crypto.dart';
part 'user.g.dart';
@HiveType(typeId: 1)
class User extends Equatable { class User extends Equatable {
User({ User({
@required this.login, @required this.login,
@required this.password, @required this.password,
}); });
@HiveField(0)
final String login; final String login;
@HiveField(1)
final String password; final String password;
@override @override
List<Object> get props => [login, password]; List<Object> get props => [login, password];
Color get color => stringToColor(login); Color get color => stringToColor(login);
String get hashPassword => convertToSha512Hash(password);
String toString() {
return login;
}
} }

View 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;
}

View File

@ -1,15 +1,18 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:selfprivacy/config/hive_config.dart';
import 'package:selfprivacy/ui/pages/initializing/initializing.dart';
import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart'; import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:selfprivacy/ui/pages/rootRoute.dart'; import 'package:selfprivacy/ui/pages/rootRoute.dart';
import 'config/bloc_config.dart'; import 'config/bloc_config.dart';
import 'config/brand_theme.dart'; import 'config/brand_theme.dart';
import 'config/localization.dart'; import 'config/localization.dart';
import 'logic/cubit/app_settings/app_settings_cubit.dart'; import 'logic/cubit/app_settings/app_settings_cubit.dart';
void main() { void main() async {
await HiveConfig.init();
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
runApp( runApp(
@ -21,11 +24,11 @@ void main() {
); );
} }
var _showOnbording = true;
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var appSettings = context.watch<AppSettingsCubit>().state;
return AnnotatedRegion<SystemUiOverlayStyle>( return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.light, // Manually changnig appbar color value: SystemUiOverlayStyle.light, // Manually changnig appbar color
child: MaterialApp( child: MaterialApp(
@ -34,10 +37,10 @@ class MyApp extends StatelessWidget {
locale: context.locale, locale: context.locale,
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
title: 'SelfPrivacy', title: 'SelfPrivacy',
theme: context.watch<AppSettingsCubit>().state.isDarkModeOn theme: appSettings.isDarkModeOn ? darkTheme : ligtTheme,
? darkTheme home: appSettings.isOnbordingShowing
: ligtTheme, ? OnboardingPage(nextPage: InitializingPage())
home: _showOnbording ? OnboardingPage() : RootPage(), : RootPage(),
), ),
); );
} }

View File

@ -1,21 +1,18 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.dart'; import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:selfprivacy/utils/extensions/elevation_extension.dart'; import 'package:selfprivacy/utils/extensions/elevation_extension.dart';
class BrandCard extends StatelessWidget { class BrandCard extends StatelessWidget {
const BrandCard({ const BrandCard({
Key key, Key key,
this.child, this.child,
this.isBlocked = false,
}) : super(key: key); }) : super(key: key);
final Widget child; final Widget child;
final bool isBlocked;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget res = Container( return Container(
margin: EdgeInsets.only(bottom: 30), margin: EdgeInsets.only(bottom: 30),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).brightness == Brightness.dark color: Theme.of(context).brightness == Brightness.dark
@ -29,44 +26,5 @@ class BrandCard extends StatelessWidget {
), ),
child: child, child: child,
); );
if (!isBlocked) {
return res;
}
return IgnorePointer(
child: Stack(
children: [
ColorFiltered(
colorFilter: ColorFilter.mode(
Colors.white,
BlendMode.saturation,
),
child: res,
),
Positioned(
top: 0,
left: 0,
right: 0,
bottom: 0,
child: Container(
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.white.withOpacity(0.8),
),
padding: EdgeInsets.all(10),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BrandText.h3('Blocked'),
BrandText.h4('finish initializing first')
],
),
),
)
],
),
);
} }
} }

View File

@ -64,7 +64,9 @@ class _BrandTabBarState extends State<BrandTabBar> {
var acitivColor = Theme.of(context).brightness == Brightness.dark var acitivColor = Theme.of(context).brightness == Brightness.dark
? BrandColors.white ? BrandColors.white
: BrandColors.black; : BrandColors.black;
var color = currentIndex == index ? acitivColor : BrandColors.inactive;
var isActive = currentIndex == index;
var color = isActive ? acitivColor : BrandColors.inactive;
return InkWell( return InkWell(
onTap: () => widget.controller.animateTo(index), onTap: () => widget.controller.animateTo(index),
child: Padding( child: Padding(
@ -75,7 +77,22 @@ class _BrandTabBarState extends State<BrandTabBar> {
children: [ children: [
Icon(iconData, color: color), Icon(iconData, color: color),
SizedBox(height: 3), SizedBox(height: 3),
Text(label, style: TextStyle(fontSize: 9, color: color)) Row(
children: [
if (isActive) ...[
Container(
height: 5,
width: 5,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: BrandColors.red2,
),
),
SizedBox(width: 5),
],
Text(label, style: TextStyle(fontSize: 9, color: color)),
],
)
], ],
), ),
), ),

View File

@ -8,24 +8,27 @@ enum TextType {
h4, // caption h4, // caption
body1, // normal body1, // normal
body2, // with opacity body2, // with opacity
small medium,
small,
onboardingTitle
} }
class BrandText extends StatelessWidget { class BrandText extends StatelessWidget {
const BrandText( const BrandText(this.text,
this.text, { {Key key,
Key key, this.style,
this.style, @required this.type,
@required this.type, this.overflow,
this.overflow, this.softWrap,
this.softWrap, this.textAlign})
}) : super(key: key); : super(key: key);
final String text; final String text;
final TextStyle style; final TextStyle style;
final TextType type; final TextType type;
final TextOverflow overflow; final TextOverflow overflow;
final bool softWrap; final bool softWrap;
final TextAlign textAlign;
factory BrandText.h1( factory BrandText.h1(
String text, { String text, {
@ -38,6 +41,13 @@ class BrandText extends StatelessWidget {
type: TextType.h1, type: TextType.h1,
style: style, style: style,
); );
factory BrandText.onboardingTitle(String text, {TextStyle style}) =>
BrandText(
text,
type: TextType.onboardingTitle,
style: style,
);
factory BrandText.h2(String text, {TextStyle style}) => BrandText( factory BrandText.h2(String text, {TextStyle style}) => BrandText(
text, text,
type: TextType.h2, type: TextType.h2,
@ -63,6 +73,10 @@ class BrandText extends StatelessWidget {
type: TextType.body2, type: TextType.body2,
style: style, style: style,
); );
factory BrandText.medium(String text,
{TextStyle style, TextAlign textAlign}) =>
BrandText(text,
type: TextType.medium, style: style, textAlign: textAlign);
factory BrandText.small(String text, {TextStyle style}) => BrandText( factory BrandText.small(String text, {TextStyle style}) => BrandText(
text, text,
type: TextType.small, type: TextType.small,
@ -105,6 +119,15 @@ class BrandText extends StatelessWidget {
case TextType.small: case TextType.small:
style = isDark ? smallStyle.copyWith(color: Colors.white) : smallStyle; style = isDark ? smallStyle.copyWith(color: Colors.white) : smallStyle;
break; break;
case TextType.onboardingTitle:
style = isDark
? onboardingTitle.copyWith(color: Colors.white)
: onboardingTitle;
break;
case TextType.medium:
style =
isDark ? mediumStyle.copyWith(color: Colors.white) : mediumStyle;
break;
} }
if (this.style != null) { if (this.style != null) {
style = style.merge(this.style); style = style.merge(this.style);
@ -114,6 +137,7 @@ class BrandText extends StatelessWidget {
style: style, style: style,
overflow: overflow, overflow: overflow,
softWrap: softWrap, softWrap: softWrap,
textAlign: textAlign,
); );
} }
} }

View File

@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.dart'; import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/logic/models/state_types.dart'; import 'package:selfprivacy/logic/models/state_types.dart';
class IconStatusMaks extends StatelessWidget { class IconStatusMask extends StatelessWidget {
IconStatusMaks({this.child, this.status}); IconStatusMask({this.child, this.status});
final Icon child; final Icon child;
final StateType status; final StateType status;

View 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),
),
);
}
}

View File

@ -24,54 +24,37 @@ class _ProgressBarState extends State<ProgressBar> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
double progress = 1 / widget.steps.length * (widget.activeIndex + 0.3); double progress = 1 / widget.steps.length * (widget.activeIndex + 0.3);
var isDark = context.watch<AppSettingsCubit>().state.isDarkModeOn;
var style = isDark ? progressTextStyleDark : progressTextStyleLight;
var allSteps = widget.steps.asMap().map(
(i, step) {
var value = _stepTitle(index: i, style: style, step: step);
return MapEntry(i, value);
},
).values;
List<Widget> odd = [];
List<Widget> even = [];
var i = 0;
for (var step in allSteps) {
if (i.isEven) {
even.add(step);
} else {
odd.add(step);
}
i++;
}
even.add(Spacer());
odd.insert(0, Spacer());
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
BrandText.h4('Progress'), BrandText.h2('Progress'),
SizedBox(height: 10), SizedBox(height: 10),
Row( Row(children: even),
children: widget.steps
.asMap()
.map(
(i, step) {
var isActive = i == widget.activeIndex;
var checked = i < widget.activeIndex;
var isDark =
context.watch<AppSettingsCubit>().state.isDarkModeOn;
var style =
isDark ? progressTextStyleDark : progressTextStyleLight;
style = isActive
? style.copyWith(fontWeight: FontWeight.w700)
: style;
return MapEntry(
i,
Expanded(
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: progressTextStyleLight,
children: [
checked
? WidgetSpan(
child: Padding(
padding: const EdgeInsets.only(
bottom: 1, right: 2),
child: Icon(BrandIcons.check, size: 14),
))
: TextSpan(text: '${i + 1}.', style: style),
TextSpan(text: step, style: style)
],
),
),
));
},
)
.values
.toList(),
),
SizedBox(height: 3), SizedBox(height: 3),
Container( Container(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
@ -98,8 +81,42 @@ class _ProgressBarState extends State<ProgressBar> {
); );
}, },
), ),
) ),
SizedBox(height: 3),
Row(
children: odd,
),
], ],
); );
} }
Expanded _stepTitle({
int index,
TextStyle style,
String step,
}) {
var isActive = index == widget.activeIndex;
var checked = index < widget.activeIndex;
style = isActive ? style.copyWith(fontWeight: FontWeight.w700) : style;
return Expanded(
flex: 2,
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: progressTextStyleLight,
children: [
checked
? WidgetSpan(
child: Padding(
padding: const EdgeInsets.only(bottom: 1, right: 2),
child: Icon(BrandIcons.check, size: 14),
))
: TextSpan(text: '${index + 1}.', style: style),
TextSpan(text: step, style: style)
],
),
),
);
}
} }

View File

@ -3,10 +3,12 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/config/text_themes.dart'; import 'package:selfprivacy/config/text_themes.dart';
import 'package:selfprivacy/logic/cubit/forms/initializing/cloudflare_form_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/initializing/domain_form_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/initializing/hetzner_form_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/initializing/hetzner_form_cubit.dart';
import 'package:selfprivacy/logic/cubit/initializing/initializing_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/initializing/user_form_cubit.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart'; import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
import 'package:selfprivacy/logic/models/user.dart';
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
import 'package:selfprivacy/ui/components/brand_card/brand_card.dart'; import 'package:selfprivacy/ui/components/brand_card/brand_card.dart';
import 'package:selfprivacy/ui/components/brand_modal_sheet/brand_modal_sheet.dart'; import 'package:selfprivacy/ui/components/brand_modal_sheet/brand_modal_sheet.dart';
@ -16,96 +18,71 @@ import 'package:selfprivacy/ui/components/progress_bar/progress_bar.dart';
import 'package:selfprivacy/ui/pages/rootRoute.dart'; import 'package:selfprivacy/ui/pages/rootRoute.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart';
class InitializingPage extends StatefulWidget { class InitializingPage extends StatelessWidget {
const InitializingPage({Key key}) : super(key: key);
@override
_InitializingPageState createState() => _InitializingPageState();
}
class _InitializingPageState extends State<InitializingPage> {
PageController pageController = PageController(viewportFraction: 1);
@override
void dispose() {
pageController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var cubit = context.watch<InitializingCubit>(); var cubit = context.watch<AppConfigCubit>();
var actualPage = [
return SafeArea( _stepHetzner(cubit),
child: Scaffold( _stepCloudflare(cubit),
body: ListView( _stepDomain(cubit),
children: [ _stepUser(cubit),
Padding( _stepServer(cubit),
padding: brandPagePadding1, Container(child: Text('Everythigng is initialized'))
child: Column( ][cubit.state.progress];
crossAxisAlignment: CrossAxisAlignment.start, return BlocListener<AppConfigCubit, AppConfigState>(
children: [ listener: (context, state) {
BrandText.h4('Начало'), if (state.isFullyInitilized) {
BrandText.h1('SelfPrivacy'), Navigator.of(context).pushReplacement(materialRoute(RootPage()));
SizedBox( }
height: 10, },
), child: SafeArea(
RichText( child: Scaffold(
text: TextSpan( body: ListView(
children: [ children: [
TextSpan( Padding(
text: padding: brandPagePadding1,
'Для устойчивости и приватности требует много учёток. Полная инструкция на ', child: Column(
style: body2Style, crossAxisAlignment: CrossAxisAlignment.start,
), children: [
BrandSpanButton.link( ProgressBar(
text: steps: [
'https://selfprivacy.org/posts/getting_started/', 'Hetzner',
urlString: 'CloudFlare',
'https://selfprivacy.org/posts/getting_started/', 'Domain',
), 'User',
'Server',
'Check'
], ],
activeIndex: cubit.state.progress,
), ),
), ],
SizedBox(height: 10), ),
ProgressBar(
steps: ['Server', 'DNS', 'Domain', 'User'],
// progress: cubit.state.progress,
activeIndex: cubit.state.progress,
),
SizedBox(height: 20),
],
), ),
), _addCard(
Container( AnimatedSwitcher(
height: 500, duration: Duration(milliseconds: 300),
child: PageView( child: actualPage,
// physics: NeverScrollableScrollPhysics(), ),
controller: pageController,
children: [
_addCard(_stepOne(cubit)),
_addCard(_stepTwo(cubit)),
_addCard(_stepThree(cubit)),
_addCard(_stepFour(cubit)),
],
), ),
), BrandButton.text(
BrandButton.text(title: 'Настрою потом', onPressed: _goToMainPage), title:
SizedBox(height: 30), cubit.state.isFullyInitilized ? 'Close' : 'Настрою потом',
], onPressed: () {
Navigator.of(context).pushAndRemoveUntil(
materialRoute(RootPage()),
(predicate) => predicate == null,
);
}),
SizedBox(height: 30),
],
),
), ),
), ),
); );
} }
void _goToMainPage() { Widget _stepHetzner(AppConfigCubit initializingCubit) {
Navigator.of(context).pushAndRemoveUntil(
materialRoute(RootPage()),
(predicate) => predicate == null,
);
}
Widget _stepOne(InitializingCubit initializingCubit) {
return BlocProvider( return BlocProvider(
create: (context) => HetznerFormCubit(initializingCubit), create: (context) => HetznerFormCubit(initializingCubit),
child: Builder(builder: (context) { child: Builder(builder: (context) {
@ -124,19 +101,17 @@ class _InitializingPageState extends State<InitializingPage> {
CubitFormTextField( CubitFormTextField(
formFieldCubit: formCubit.apiKey, formFieldCubit: formCubit.apiKey,
textAlign: TextAlign.center, textAlign: TextAlign.center,
keyboardType: TextInputType.number,
scrollPadding: EdgeInsets.only(bottom: 70), scrollPadding: EdgeInsets.only(bottom: 70),
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Hetzner API Token', hintText: 'Hetzner API Token',
), ),
), ),
SizedBox(height: 20), Spacer(),
BrandButton.rised( BrandButton.rised(
onPressed: onPressed:
formCubit.state.isSubmitting ? null : formCubit.trySubmit, formCubit.state.isSubmitting ? null : formCubit.trySubmit,
title: 'Подключить', title: 'Подключить',
), ),
Spacer(),
SizedBox(height: 10), SizedBox(height: 10),
BrandButton.text( BrandButton.text(
onPressed: () => _showModal(context, _HowHetzner()), onPressed: () => _showModal(context, _HowHetzner()),
@ -159,110 +134,161 @@ class _InitializingPageState extends State<InitializingPage> {
); );
} }
Widget _stepTwo(InitializingCubit cubit) { Widget _stepCloudflare(AppConfigCubit initializingCubit) {
return Column( return BlocProvider(
crossAxisAlignment: CrossAxisAlignment.start, create: (context) => CloudFlareFormCubit(initializingCubit),
children: [ child: Builder(builder: (context) {
Image.asset('assets/images/logos/cloudflare.png'), var formCubit = context.watch<CloudFlareFormCubit>();
BrandText.h2('Подключите CloudFlare DNS'),
SizedBox(height: 10), return Column(
BrandText.body2('Для управления DNS вашего домена'), crossAxisAlignment: CrossAxisAlignment.start,
Expanded( children: [
child: _MockForm( Spacer(),
hintText: 'CloudFlare API Token', Image.asset('assets/images/logos/cloudflare.png'),
length: 64, BrandText.h2('Подключите CloudFlare'),
onPressed: () { SizedBox(height: 10),
cubit.setCloudFlare('key'); BrandText.body2('Для управления DNS вашего домена'),
pageController.animateToPage( Spacer(),
2, CubitFormTextField(
curve: Curves.easeIn, formFieldCubit: formCubit.apiKey,
duration: Duration(milliseconds: 200), textAlign: TextAlign.center,
); scrollPadding: EdgeInsets.only(bottom: 70),
}, decoration: InputDecoration(
), hintText: 'CloudFlare API Token',
), ),
SizedBox(height: 20), ),
BrandButton.text( Spacer(),
onPressed: () {}, BrandButton.rised(
title: 'Как получить API Token', onPressed:
), formCubit.state.isSubmitting ? null : formCubit.trySubmit,
], title: 'Подключить',
),
SizedBox(height: 10),
BrandButton.text(
onPressed: () {},
title: 'Как получить API Token',
),
],
);
}),
); );
} }
Widget _stepThree(InitializingCubit cubit) { Widget _stepDomain(AppConfigCubit initializingCubit) {
return Column( return BlocProvider(
crossAxisAlignment: CrossAxisAlignment.start, create: (context) => DomainFormCubit(initializingCubit),
children: [ child: Builder(builder: (context) {
SizedBox(height: 10), var formCubit = context.watch<DomainFormCubit>();
BrandText.h2('Введите домен:'),
Expanded( return Column(
child: _MockForm( crossAxisAlignment: CrossAxisAlignment.start,
hintText: 'домен', children: [
length: 10, Spacer(),
onPressed: () { BrandText.h2('Введите домен:'),
cubit.setDomain('domain'); SizedBox(height: 10),
pageController.animateToPage( CubitFormTextField(
3, keyboardType: TextInputType.emailAddress,
curve: Curves.easeIn, formFieldCubit: formCubit.domainName,
duration: Duration(milliseconds: 200), textAlign: TextAlign.center,
); scrollPadding: EdgeInsets.only(bottom: 70),
}, decoration: InputDecoration(
), hintText: 'Домен',
), ),
SizedBox(height: 10), ),
BrandButton.text( Spacer(),
onPressed: () => _showModal(context, _HowHetzner()), BrandButton.rised(
title: 'Как получить API Token', onPressed:
), formCubit.state.isSubmitting ? null : formCubit.trySubmit,
], title: 'Подключить',
),
SizedBox(height: 10),
BrandButton.text(
onPressed: () => _showModal(context, _HowHetzner()),
title: 'Как получить API Token',
),
],
);
}),
); );
} }
Widget _stepFour(InitializingCubit cubit) { Widget _stepUser(AppConfigCubit initializingCubit) {
return Column( return BlocProvider(
crossAxisAlignment: CrossAxisAlignment.start, create: (context) => UserFormCubit(initializingCubit),
children: [ child: Builder(builder: (context) {
SizedBox(height: 10), var formCubit = context.watch<UserFormCubit>();
Expanded(
child: Column( return Column(
children: [ crossAxisAlignment: CrossAxisAlignment.start,
TextField( children: [
decoration: InputDecoration( Spacer(),
hintText: 'нинейм', SizedBox(height: 10),
), CubitFormTextField(
formFieldCubit: formCubit.userName,
textAlign: TextAlign.center,
scrollPadding: EdgeInsets.only(bottom: 70),
decoration: InputDecoration(
hintText: 'Никнейм',
), ),
SizedBox(height: 10), ),
TextField( SizedBox(height: 10),
obscureText: true, CubitFormTextField(
decoration: InputDecoration( formFieldCubit: formCubit.password,
hintText: 'пароль', textAlign: TextAlign.center,
), scrollPadding: EdgeInsets.only(bottom: 70),
decoration: InputDecoration(
hintText: 'Пароль',
), ),
Spacer(), ),
BrandButton.rised( Spacer(),
onPressed: () { BrandButton.rised(
cubit.setRootUser( onPressed:
User(login: 'aa', password: 'bbb'), formCubit.state.isSubmitting ? null : formCubit.trySubmit,
); title: 'Подключить',
_goToMainPage(); ),
}, SizedBox(height: 10),
title: 'some text', BrandButton.text(
), onPressed: () => _showModal(context, _HowHetzner()),
], title: 'Как получить API Token',
), ),
), ],
SizedBox(height: 10), );
BrandButton.text( }),
onPressed: () => _showModal(context, _HowHetzner()),
title: 'Как получить API Token',
),
],
); );
} }
Widget _stepServer(AppConfigCubit appConfigCubit) {
var isLoading = appConfigCubit.state.isLoading;
return Builder(builder: (context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Spacer(
flex: 2,
),
BrandText.h2('Создать сервер'),
SizedBox(height: 10),
BrandText.body2('Создать сервер'),
Spacer(),
BrandButton.rised(
onPressed: isLoading ? null : appConfigCubit.createServer,
title: isLoading ? 'loading' : 'Создать сервер',
),
Spacer(
flex: 2,
),
BrandButton.text(
onPressed: () => _showModal(context, _HowHetzner()),
title: 'Что это значит?',
),
],
);
});
}
Widget _addCard(Widget child) { Widget _addCard(Widget child) {
return Padding( return Container(
height: 500,
padding: brandPagePadding2, padding: brandPagePadding2,
child: BrandCard( child: BrandCard(
child: child, child: child,
@ -322,235 +348,3 @@ class _HowHetzner extends StatelessWidget {
); );
} }
} }
// class _MockSuccess extends StatelessWidget {
// const _MockSuccess({Key key, this.type}) : super(key: key);
// final ProviderType type;
// @override
// Widget build(BuildContext context) {
// String text;
// switch (type) {
// case ProviderType.server:
// text = '1. Cервер подключен';
// break;
// case ProviderType.domain:
// text = '2. Домен настроен';
// break;
// case ProviderType.backup:
// text = '3. Резервное копирование настроенно';
// break;
// }
// return BrandCard(
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// BrandText.h3(text),
// Icon(
// Icons.check,
// color: BrandColors.green1,
// ),
// ],
// ),
// );
// }
// }
class _MockForm extends StatefulWidget {
const _MockForm({
Key key,
@required this.hintText,
this.submitButtonText = 'Подключить',
@required this.onPressed,
@required this.length,
}) : super(key: key);
final String hintText;
final String submitButtonText;
final int length;
final VoidCallback onPressed;
@override
__MockFormState createState() => __MockFormState();
}
class __MockFormState extends State<_MockForm> {
String text = '';
bool _valid = true;
bool _touched = false;
onPressed() {
if (text.length == widget.length) {
setState(() {
_touched = true;
_valid = true;
widget.onPressed();
});
} else {
setState(() {
_touched = true;
_valid = false;
});
}
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 20),
TextField(
onChanged: (value) {
if (_touched) {
if (value.length == widget.length) {
setState(() {
_valid = true;
text = value;
});
} else {
setState(() {
_valid = false;
text = value;
});
}
} else {
setState(() {
text = value;
});
}
},
decoration: InputDecoration(
hintText: widget.hintText,
errorText:
_valid ? null : 'Длинна должна быть ${widget.length} символа',
),
),
Spacer(),
BrandButton.rised(
onPressed: _valid ? onPressed : null,
title: widget.submitButtonText,
),
],
);
}
}
// Widget getCard(BuildContext context, ProviderModel model) {
// var cubit = context.watch<ProvidersCubit>();
// if (model.state == StateType.stable) {
// return _MockSuccess(type: model.type);
// }
// switch (model.type) {
// case ProviderType.server:
// return BrandCard(
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Image.asset('assets/images/logos/hetzner.png'),
// SizedBox(height: 10),
// BrandText.h2('1. Подключите сервер Hetzner'),
// SizedBox(height: 10),
// BrandText.body2(
// 'Здесь будут жить наши данные и SelfPrivacy-сервисы'),
// _MockForm(
// hintText: 'Hetzner API Token',
// length: 48,
// onPressed: () {
// var provider = cubit.state.all
// .firstWhere((p) => p.type == ProviderType.server);
// cubit.connect(provider);
// },
// ),
// SizedBox(height: 20),
// BrandButton.text(
// onPressed: () => _showModal(context, _HowHetzner()),
// title: 'Как получить API Token',
// ),
// ],
// ),
// );
// break;
// case ProviderType.domain:
// return BrandCard(
// isBlocked: true,
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Image.asset('assets/images/logos/namecheap.png'),
// SizedBox(height: 10),
// BrandText.h2('2. Настройте домен'),
// SizedBox(height: 10),
// RichText(
// text: TextSpan(
// children: [
// TextSpan(
// text: 'Зарегистрируйте домен в ',
// style: body2Style,
// ),
// BrandSpanButton.link(
// text: 'NameCheap',
// urlString: 'https://www.namecheap.com',
// ),
// TextSpan(
// text:
// ' или у любого другого регистратора. После этого настройте его на DNS-сервер CloudFlare',
// style: body2Style,
// ),
// ],
// ),
// ),
// _MockForm(
// hintText: 'Домен, например, selfprivacy.org',
// submitButtonText: 'Проверить DNS',
// length: 2,
// onPressed: () {},
// ),
// SizedBox(height: 20),
// BrandButton.text(
// onPressed: () {},
// title: 'Как настроить DNS CloudFlare',
// ),
// SizedBox(height: 10),
// Image.asset('assets/images/logos/cloudflare.png'),
// SizedBox(height: 10),
// ],
// ),
// );
// break;
// case ProviderType.backup:
// return BrandCard(
// isBlocked: true,
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Image.asset('assets/images/logos/aws.png'),
// SizedBox(height: 10),
// BrandText.h2('4. Подключите Amazon AWS для бекапа'),
// SizedBox(height: 10),
// BrandText.body2(
// 'IaaS-провайдер, для бесплатного хранения резервных копии ваших данных в зашифрованном виде'),
// _MockForm(
// hintText: 'Amazon AWS Access Key',
// length: 2,
// onPressed: () {
// var provider = cubit.state.all
// .firstWhere((p) => p.type == ProviderType.backup);
// cubit.connect(provider);
// },
// ),
// SizedBox(height: 20),
// BrandButton.text(
// onPressed: () {},
// title: 'Как получить API Token',
// ),
// ],
// ),
// );
// }
// return null;
// }

View File

@ -2,9 +2,11 @@ import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.dart'; import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart'; import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart';
import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:selfprivacy/utils/named_font_weight.dart';
class AppSettingsPage extends StatefulWidget { class AppSettingsPage extends StatefulWidget {
const AppSettingsPage({Key key}) : super(key: key); const AppSettingsPage({Key key}) : super(key: key);
@ -53,11 +55,75 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
activeColor: BrandColors.green1, activeColor: BrandColors.green1,
activeTrackColor: BrandColors.green2, activeTrackColor: BrandColors.green2,
value: Theme.of(context).brightness == Brightness.dark, value: Theme.of(context).brightness == Brightness.dark,
onChanged: (value) => onChanged: (value) => appSettings.updateDarkMode(
appSettings.update(isDarkModeOn: !isDarkModeOn), isDarkModeOn: !isDarkModeOn),
), ),
], ],
), ),
),
Container(
padding: EdgeInsets.only(top: 20, bottom: 5),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(width: 1, color: BrandColors.dividerColor),
)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: _TextColumn(
title: 'Reset app config',
value: 'Reset api keys and root user',
),
),
SizedBox(width: 5),
RaisedButton(
color: BrandColors.red1,
child: Text(
'Reset',
style: TextStyle(
color: BrandColors.white,
fontWeight: NamedFontWeight.demiBold,
),
),
onPressed: () {
showDialog(
context: context,
child: AlertDialog(
title: Text('Are you sure?'),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text('Reset all keys?'),
],
),
),
actions: <Widget>[
TextButton(
child: Text(
'Reset',
style: TextStyle(
color: BrandColors.red1,
),
),
onPressed: () {
context.read<AppConfigCubit>().reset();
Navigator.of(context)..pop()..pop();
},
),
TextButton(
child: Text('Cancel'),
onPressed: () {
Navigator.of(context)..pop();
},
),
],
));
},
)
],
),
) )
], ],
), ),
@ -88,10 +154,6 @@ class _TextColumn extends StatelessWidget {
style: TextStyle(color: hasWarning ? BrandColors.warning : null), style: TextStyle(color: hasWarning ? BrandColors.warning : null),
), ),
SizedBox(height: 5), SizedBox(height: 5),
BrandText.body1(
title,
style: TextStyle(color: hasWarning ? BrandColors.warning : null),
),
BrandText.body1(value, BrandText.body1(value,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,

View File

@ -6,6 +6,8 @@ import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:selfprivacy/ui/pages/initializing/initializing.dart'; import 'package:selfprivacy/ui/pages/initializing/initializing.dart';
import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart';
import 'package:selfprivacy/ui/pages/rootRoute.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart';
import 'about/about.dart'; import 'about/about.dart';
@ -49,6 +51,11 @@ class MorePage extends StatelessWidget {
iconData: BrandIcons.help, iconData: BrandIcons.help,
goTo: InfoPage(), goTo: InfoPage(),
), ),
_NavItem(
title: 'Onboarding',
iconData: BrandIcons.triangle,
goTo: OnboardingPage(nextPage: RootPage()),
),
], ],
), ),
) )

View File

@ -1,12 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:selfprivacy/ui/pages/initializing/initializing.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart';
class OnboardingPage extends StatefulWidget { class OnboardingPage extends StatefulWidget {
const OnboardingPage({Key key}) : super(key: key); const OnboardingPage({Key key, @required this.nextPage}) : super(key: key);
final Widget nextPage;
@override @override
_OnboardingPageState createState() => _OnboardingPageState(); _OnboardingPageState createState() => _OnboardingPageState();
} }
@ -107,8 +108,9 @@ class _OnboardingPageState extends State<OnboardingPage> {
), ),
BrandButton.rised( BrandButton.rised(
onPressed: () { onPressed: () {
context.read<AppSettingsCubit>().turnOffOnboarding();
Navigator.of(context) Navigator.of(context)
.pushReplacement(materialRoute(InitializingPage())); .pushReplacement(materialRoute(widget.nextPage));
}, },
title: 'Понял', title: 'Понял',
), ),

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/logic/cubit/initializing/initializing_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart'; import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
import 'package:selfprivacy/logic/models/provider.dart'; import 'package:selfprivacy/logic/models/provider.dart';
import 'package:selfprivacy/logic/models/state_types.dart'; import 'package:selfprivacy/logic/models/state_types.dart';
@ -9,6 +9,7 @@ import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
import 'package:selfprivacy/ui/components/brand_modal_sheet/brand_modal_sheet.dart'; import 'package:selfprivacy/ui/components/brand_modal_sheet/brand_modal_sheet.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart'; import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart';
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
import 'package:selfprivacy/ui/pages/providers/settings/settings.dart'; import 'package:selfprivacy/ui/pages/providers/settings/settings.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart'; import 'package:selfprivacy/utils/route_transitions/basic.dart';
@ -22,9 +23,12 @@ class ProvidersPage extends StatefulWidget {
class _ProvidersPageState extends State<ProvidersPage> { class _ProvidersPageState extends State<ProvidersPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized;
final cards = ProviderType.values final cards = ProviderType.values
.map((type) => .map((type) => _Card(
_Card(provider: ProviderModel(state: StateType.stable, type: type))) provider:
ProviderModel(state: StateType.uninitialized, type: type)))
.toList(); .toList();
return Scaffold( return Scaffold(
appBar: PreferredSize( appBar: PreferredSize(
@ -33,7 +37,13 @@ class _ProvidersPageState extends State<ProvidersPage> {
), ),
body: ListView( body: ListView(
padding: brandPagePadding2, padding: brandPagePadding2,
children: cards, children: [
if (!isReady) ...[
NotReadyCard(),
SizedBox(height: 24),
],
...cards,
],
), ),
); );
} }
@ -48,8 +58,6 @@ class _Card extends StatelessWidget {
String title; String title;
String message; String message;
String stableText; String stableText;
var isFullyInitilized =
context.watch<InitializingCubit>().state.isFullyInitilized;
switch (provider.type) { switch (provider.type) {
case ProviderType.server: case ProviderType.server:
@ -80,11 +88,10 @@ class _Card extends StatelessWidget {
}, },
), ),
child: BrandCard( child: BrandCard(
isBlocked: !isFullyInitilized,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
IconStatusMaks( IconStatusMask(
status: provider.state, status: provider.state,
child: Icon(provider.icon, size: 30, color: Colors.white), child: Icon(provider.icon, size: 30, color: Colors.white),
), ),
@ -176,7 +183,7 @@ class _ProviderDetails extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox(height: 13), SizedBox(height: 13),
IconStatusMaks( IconStatusMask(
status: provider.state, status: provider.state,
child: child:
Icon(provider.icon, size: 40, color: Colors.white), Icon(provider.icon, size: 40, color: Colors.white),

View File

@ -1,9 +1,6 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:selfprivacy/logic/cubit/initializing/initializing_cubit.dart';
import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart';
import 'package:selfprivacy/ui/components/brand_tab_bar/brand_tab_bar.dart'; import 'package:selfprivacy/ui/components/brand_tab_bar/brand_tab_bar.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:selfprivacy/ui/pages/more/more.dart'; import 'package:selfprivacy/ui/pages/more/more.dart';
import 'package:selfprivacy/ui/pages/providers/providers.dart'; import 'package:selfprivacy/ui/pages/providers/providers.dart';
import 'package:selfprivacy/ui/pages/services/services.dart'; import 'package:selfprivacy/ui/pages/services/services.dart';
@ -34,9 +31,6 @@ class _RootPageState extends State<RootPage>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var isUserFilled =
context.watch<InitializingCubit>().state.isFullyInitilized;
return SafeArea( return SafeArea(
child: Scaffold( child: Scaffold(
body: TabBarView( body: TabBarView(
@ -44,7 +38,7 @@ class _RootPageState extends State<RootPage>
children: [ children: [
ProvidersPage(), ProvidersPage(),
ServicesPage(), ServicesPage(),
isUserFilled ? UsersPage() : _NotReady(), UsersPage(),
MorePage(), MorePage(),
], ],
), ),
@ -55,20 +49,3 @@ class _RootPageState extends State<RootPage>
); );
} }
} }
class _NotReady extends StatelessWidget {
const _NotReady({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BrandText.h3('Not ready'),
BrandText.body2('Finish providers initialization first'),
],
),
);
}
}

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/logic/cubit/initializing/initializing_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/cubit/services/services_cubit.dart';
import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/logic/models/service.dart';
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
@ -9,6 +9,7 @@ import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart'; import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart';
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
class ServicesPage extends StatefulWidget { class ServicesPage extends StatefulWidget {
ServicesPage({Key key}) : super(key: key); ServicesPage({Key key}) : super(key: key);
@ -23,6 +24,8 @@ class _ServicesPageState extends State<ServicesPage> {
final serviceCubit = context.watch<ServicesCubit>(); final serviceCubit = context.watch<ServicesCubit>();
final connected = serviceCubit.state.connected; final connected = serviceCubit.state.connected;
final uninitialized = serviceCubit.state.uninitialized; final uninitialized = serviceCubit.state.uninitialized;
var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized;
return Scaffold( return Scaffold(
appBar: PreferredSize( appBar: PreferredSize(
child: BrandHeader(title: 'Сервисы'), child: BrandHeader(title: 'Сервисы'),
@ -31,6 +34,7 @@ class _ServicesPageState extends State<ServicesPage> {
body: ListView( body: ListView(
padding: brandPagePadding2, padding: brandPagePadding2,
children: [ children: [
if (!isReady) NotReadyCard(),
SizedBox(height: 24), SizedBox(height: 24),
...connected.map((service) => _Card(service: service)).toList(), ...connected.map((service) => _Card(service: service)).toList(),
if (uninitialized.isNotEmpty) ...[ if (uninitialized.isNotEmpty) ...[
@ -53,7 +57,6 @@ class _Card extends StatelessWidget {
String title; String title;
IconData iconData; IconData iconData;
String description; String description;
var isFullyInitilized = context.watch<InitializingCubit>().state.isFullyInitilized;
switch (service.type) { switch (service.type) {
case ServiceTypes.messanger: case ServiceTypes.messanger:
@ -85,11 +88,10 @@ class _Card extends StatelessWidget {
break; break;
} }
return BrandCard( return BrandCard(
isBlocked: !isFullyInitilized,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
IconStatusMaks( IconStatusMask(
status: service.state, status: service.state,
child: Icon(iconData, size: 30, color: Colors.white), child: Icon(iconData, size: 30, color: Colors.white),
), ),

View 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,
),
),
],
),
);
}
}

View 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();
},
);
},
),
);
}
}

View 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),
],
),
),
],
),
),
);
}
}

View 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),
],
),
),
);
}
}

View 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,
}

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.dart'; import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/users/users_cubit.dart'; import 'package:selfprivacy/logic/cubit/users/users_cubit.dart';
import 'package:selfprivacy/logic/models/user.dart'; import 'package:selfprivacy/logic/models/user.dart';
import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart';
@ -9,318 +10,74 @@ import 'package:selfprivacy/ui/components/brand_header/brand_header.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
import 'package:selfprivacy/ui/components/brand_modal_sheet/brand_modal_sheet.dart'; import 'package:selfprivacy/ui/components/brand_modal_sheet/brand_modal_sheet.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart';
import 'package:selfprivacy/utils/password_generator.dart'; import 'package:selfprivacy/utils/password_generator.dart';
part 'fab.dart';
part 'new_user.dart';
part 'user_details.dart';
part 'user.dart';
part 'empty.dart';
class UsersPage extends StatelessWidget { class UsersPage extends StatelessWidget {
const UsersPage({Key key}) : super(key: key); const UsersPage({Key key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final usersCubit = context.watch<UsersCubit>(); final usersCubit = context.watch<UsersCubit>();
var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized;
final users = usersCubit.state.users; final users = usersCubit.state.users;
final isEmpty = usersCubit.state.isEmpty;
Widget child;
if (!isReady) {
child = isNotReady();
} else {
child = isEmpty
? Container(
alignment: Alignment.center,
child: _NoUsers(
text: 'Добавьте первого пользователя',
),
)
: ListView(
children: [
...users.map((user) => _User(user: user)),
],
);
}
return Scaffold( return Scaffold(
appBar: PreferredSize( appBar: PreferredSize(
child: BrandHeader(title: 'Пользователи'), child: BrandHeader(title: 'Пользователи'),
preferredSize: Size.fromHeight(52), preferredSize: Size.fromHeight(52),
), ),
floatingActionButton: Container( floatingActionButton: isReady ? _Fab() : null,
width: 48.0, body: child,
height: 48.0,
child: RawMaterialButton(
fillColor: BrandColors.blue,
shape: CircleBorder(),
elevation: 0.0,
highlightElevation: 2,
child: Icon(
Icons.add,
color: Colors.white,
size: 34,
),
onPressed: () {
showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (BuildContext context) {
return _NewUser();
},
);
},
),
),
body: ListView(
children: [
...users.map((user) => _User(user: user)),
],
),
); );
} }
}
class _User extends StatelessWidget { Widget isNotReady() {
const _User({Key key, this.user}) : super(key: key); return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
final User user; children: [
@override Padding(
Widget build(BuildContext context) { padding: const EdgeInsets.symmetric(horizontal: 15),
return InkWell( child: NotReadyCard(),
onTap: () {
showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (BuildContext context) {
return _UserDetails(user: user);
},
);
},
child: Container(
padding: brandPagePadding2,
height: 48,
child: Row(
children: [
Container(
width: 17,
height: 17,
decoration: BoxDecoration(
color: user.color,
shape: BoxShape.circle,
),
),
SizedBox(width: 20),
BrandText.h4(user.login),
],
), ),
), Expanded(
); child: Container(
} padding: const EdgeInsets.symmetric(horizontal: 15),
} child: Center(
child: _NoUsers(
class _NewUser extends StatefulWidget { text:
const _NewUser({Key key}) : super(key: key); 'Подключите сервер, домен и DNS в разеде Провайдеры, чтобы добавить первого пользователя',
@override
__NewUserState createState() => __NewUserState();
}
class __NewUserState extends State<_NewUser> {
var passController = TextEditingController(text: genPass());
@override
Widget build(BuildContext context) {
return BrandModalSheet(
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
BrandHeader(title: 'Новый пользователь'),
SizedBox(width: 14),
Padding(
padding: brandPagePadding2,
child: Column(
children: [
TextField(
decoration: InputDecoration(
labelText: 'Логин',
suffixText: '@example',
),
),
SizedBox(height: 20),
TextField(
controller: passController,
decoration: InputDecoration(
alignLabelWithHint: false,
labelText: 'Пароль',
suffixIcon: Padding(
padding: const EdgeInsets.only(right: 8),
child: IconButton(
icon: Icon(
BrandIcons.refresh,
color: BrandColors.blue,
),
onPressed: () {
passController.value =
TextEditingValue(text: genPass());
},
),
),
),
),
SizedBox(height: 30),
BrandButton.rised(
onPressed: () {
Navigator.pop(context);
},
title: 'Создать',
),
SizedBox(height: 40),
Text(
'Новый пользователь автоматически получит доступ ко всем сервисам. Ещё какое-то описание.'),
SizedBox(height: 30),
],
), ),
), ),
],
),
),
);
}
}
class _UserDetails extends StatelessWidget {
const _UserDetails({
Key key,
this.user,
}) : super(key: key);
final User user;
@override
Widget build(BuildContext context) {
return BrandModalSheet(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 200,
decoration: BoxDecoration(
color: user.color,
borderRadius: BorderRadius.vertical(
top: Radius.circular(20),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Align(
alignment: Alignment.centerRight,
child: Padding(
padding: EdgeInsets.symmetric(
vertical: 4,
horizontal: 2,
),
child: PopupMenuButton<PopupMenuItemType>(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
onSelected: (PopupMenuItemType result) {
switch (result) {
case PopupMenuItemType.reset:
break;
case PopupMenuItemType.delete:
showDialog(
context: context,
child: AlertDialog(
title: Text('Подтверждение '),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text('удалить учетную запись?'),
],
),
),
actions: <Widget>[
TextButton(
child: Text('Отменить'),
onPressed: () {
Navigator.of(context)..pop();
},
),
TextButton(
child: Text(
'Удалить',
style: TextStyle(
color: BrandColors.red,
),
),
onPressed: () {
Navigator.of(context)..pop()..pop();
},
),
],
));
break;
}
},
icon: Icon(Icons.more_vert),
itemBuilder: (BuildContext context) => [
PopupMenuItem<PopupMenuItemType>(
value: PopupMenuItemType.reset,
child: Container(
padding: EdgeInsets.only(left: 5),
child: Text('Сбросить пароль'),
),
),
PopupMenuItem<PopupMenuItemType>(
value: PopupMenuItemType.delete,
child: Container(
padding: EdgeInsets.only(left: 5),
child: Text(
'Удалить',
style: TextStyle(color: BrandColors.red),
),
),
),
],
),
),
),
Spacer(),
Padding(
padding: EdgeInsets.symmetric(
vertical: 20,
horizontal: 15,
),
child: BrandText.h1(
user.login,
softWrap: true,
overflow: TextOverflow.ellipsis,
)),
],
),
), ),
SizedBox(height: 20), )
Padding( ],
padding: brandPagePadding2.copyWith(bottom: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
BrandText.small('Учетная запись'),
Container(
height: 40,
alignment: Alignment.centerLeft,
child: BrandText.h4('${user.login}@example.com'),
),
SizedBox(height: 14),
BrandText.small('Пароль'),
Container(
height: 40,
alignment: Alignment.centerLeft,
child: BrandText.h4(user.password),
),
SizedBox(height: 24),
BrandDivider(),
SizedBox(height: 20),
BrandButton.iconText(
title: 'Отправить реквизиты для входа',
icon: Icon(BrandIcons.share),
onPressed: () {},
),
SizedBox(height: 20),
BrandDivider(),
SizedBox(height: 20),
Text(
'Вам был создан доступ к сервисам с логином <login> и паролем <password> к сервисам:- E-mail с адресом <username@domain.com>- Менеджер паролей: <pass.domain.com>- Файловое облако: <cloud.mydomain.com>- Видеоконференция <meet.domain.com>- Git сервер <git.mydomain.com>'),
],
),
)
],
),
); );
} }
} }
enum PopupMenuItemType {
reset,
delete,
}

10
lib/utils/crypto.dart Normal file
View 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();
}

View File

@ -1,6 +1,20 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
url: "https://pub.dartlang.org"
source: hosted
version: "14.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
url: "https://pub.dartlang.org"
source: hosted
version: "0.41.1"
archive: archive:
dependency: transitive dependency: transitive
description: description:
@ -36,6 +50,62 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0-nullsafety.1" version: "2.1.0-nullsafety.1"
build:
dependency: transitive
description:
name: build
url: "https://pub.dartlang.org"
source: hosted
version: "1.6.0"
build_config:
dependency: transitive
description:
name: build_config
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.5"
build_daemon:
dependency: transitive
description:
name: build_daemon
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.4"
build_resolvers:
dependency: transitive
description:
name: build_resolvers
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.1"
build_runner:
dependency: "direct dev"
description:
name: build_runner
url: "https://pub.dartlang.org"
source: hosted
version: "1.10.11"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
url: "https://pub.dartlang.org"
source: hosted
version: "6.1.5"
built_collection:
dependency: transitive
description:
name: built_collection
url: "https://pub.dartlang.org"
source: hosted
version: "4.3.2"
built_value:
dependency: transitive
description:
name: built_value
url: "https://pub.dartlang.org"
source: hosted
version: "7.1.0"
characters: characters:
dependency: transitive dependency: transitive
description: description:
@ -50,6 +120,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0-nullsafety.1" version: "1.2.0-nullsafety.1"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
cli_util:
dependency: transitive
description:
name: cli_util
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@ -57,6 +141,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0-nullsafety.1" version: "1.1.0-nullsafety.1"
code_builder:
dependency: transitive
description:
name: code_builder
url: "https://pub.dartlang.org"
source: hosted
version: "3.5.0"
collection: collection:
dependency: transitive dependency: transitive
description: description:
@ -72,7 +163,7 @@ packages:
source: hosted source: hosted
version: "2.1.1" version: "2.1.1"
crypto: crypto:
dependency: transitive dependency: "direct main"
description: description:
name: crypto name: crypto
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
@ -84,7 +175,7 @@ packages:
name: cubit_form name: cubit_form
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.0.14" version: "0.0.15"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
@ -92,6 +183,27 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.0" version: "1.0.0"
dart_style:
dependency: transitive
description:
name: dart_style
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.10"
dartx:
dependency: transitive
description:
name: dartx
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.0"
dio:
dependency: "direct main"
description:
name: dio
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.10"
easy_localization: easy_localization:
dependency: "direct main" dependency: "direct main"
description: description:
@ -134,6 +246,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "5.2.1" version: "5.2.1"
fixnum:
dependency: transitive
description:
name: fixnum
url: "https://pub.dartlang.org"
source: hosted
version: "0.10.11"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -158,6 +277,13 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_secure_storage:
dependency: "direct main"
description:
name: flutter_secure_storage
url: "https://pub.dartlang.org"
source: hosted
version: "3.3.5"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -168,6 +294,13 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
glob:
dependency: transitive
description:
name: glob
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
google_fonts: google_fonts:
dependency: "direct main" dependency: "direct main"
description: description:
@ -175,6 +308,34 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.1" version: "1.1.1"
graphs:
dependency: transitive
description:
name: graphs
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0"
hive:
dependency: "direct main"
description:
name: hive
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.4+1"
hive_flutter:
dependency: "direct main"
description:
name: hive_flutter
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.1"
hive_generator:
dependency: "direct dev"
description:
name: hive_generator
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.2"
http: http:
dependency: transitive dependency: transitive
description: description:
@ -182,6 +343,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.2" version: "0.12.2"
http_multi_server:
dependency: transitive
description:
name: http_multi_server
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.0"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
@ -203,6 +371,41 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.16.1" version: "0.16.1"
io:
dependency: transitive
description:
name: io
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.4"
js:
dependency: transitive
description:
name: js
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.2"
json_annotation:
dependency: "direct main"
description:
name: json_annotation
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.1"
json_serializable:
dependency: "direct dev"
description:
name: json_serializable
url: "https://pub.dartlang.org"
source: hosted
version: "3.5.1"
logging:
dependency: transitive
description:
name: logging
url: "https://pub.dartlang.org"
source: hosted
version: "0.11.4"
mask_text_input_formatter: mask_text_input_formatter:
dependency: transitive dependency: transitive
description: description:
@ -224,6 +427,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0-nullsafety.3" version: "1.3.0-nullsafety.3"
mime:
dependency: transitive
description:
name: mime
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.7"
nested: nested:
dependency: transitive dependency: transitive
description: description:
@ -231,6 +441,27 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.0.4" version: "0.0.4"
node_interop:
dependency: transitive
description:
name: node_interop
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.1"
node_io:
dependency: transitive
description:
name: node_io
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
package_config:
dependency: transitive
description:
name: package_config
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.3"
package_info: package_info:
dependency: "direct main" dependency: "direct main"
description: description:
@ -308,6 +539,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.3" version: "1.0.3"
pool:
dependency: transitive
description:
name: pool
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.0"
process: process:
dependency: transitive dependency: transitive
description: description:
@ -322,6 +560,27 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.3.2+2" version: "4.3.2+2"
pub_semver:
dependency: transitive
description:
name: pub_semver
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.4"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.7"
quiver:
dependency: transitive
description:
name: quiver
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.5"
shared_preferences: shared_preferences:
dependency: transitive dependency: transitive
description: description:
@ -364,6 +623,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.0.1+3" version: "0.0.1+3"
shelf:
dependency: transitive
description:
name: shelf
url: "https://pub.dartlang.org"
source: hosted
version: "0.7.9"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.3"
shortuuid: shortuuid:
dependency: transitive dependency: transitive
description: description:
@ -376,6 +649,13 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.99" version: "0.0.99"
source_gen:
dependency: transitive
description:
name: source_gen
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.10+1"
source_span: source_span:
dependency: transitive dependency: transitive
description: description:
@ -397,6 +677,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0-nullsafety.1" version: "2.1.0-nullsafety.1"
stream_transform:
dependency: transitive
description:
name: stream_transform
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
string_scanner: string_scanner:
dependency: transitive dependency: transitive
description: description:
@ -418,6 +705,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.19-nullsafety.2" version: "0.2.19-nullsafety.2"
time:
dependency: transitive
description:
name: time
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.0"
timing:
dependency: transitive
description:
name: timing
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.1+3"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -481,6 +782,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0-nullsafety.3" version: "2.1.0-nullsafety.3"
watcher:
dependency: transitive
description:
name: watcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.7+15"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
win32: win32:
dependency: transitive dependency: transitive
description: description:
@ -510,5 +825,5 @@ packages:
source: hosted source: hosted
version: "2.2.1" version: "2.2.1"
sdks: sdks:
dart: ">=2.10.0-110 <2.11.0" dart: ">=2.10.0 <2.11.0"
flutter: ">=1.22.0 <2.0.0" flutter: ">=1.22.0 <2.0.0"

View File

@ -9,12 +9,18 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
cubit_form: ^0.0.14 crypto: ^2.1.5
cubit_form: ^0.0.15
cupertino_icons: ^1.0.0 cupertino_icons: ^1.0.0
dio: ^3.0.10
easy_localization: ^2.3.3 easy_localization: ^2.3.3
equatable: ^1.2.5 equatable: ^1.2.5
flutter_bloc: ^6.1.1 flutter_bloc: ^6.1.1
flutter_secure_storage: ^3.3.5
google_fonts: ^1.1.1 google_fonts: ^1.1.1
hive: ^1.4.4+1
hive_flutter: ^0.3.1
json_annotation: ^3.1.1
package_info: ^0.4.3+2 package_info: ^0.4.3+2
provider: ^4.3.2+2 provider: ^4.3.2+2
url_launcher: ^5.7.10 url_launcher: ^5.7.10
@ -22,12 +28,16 @@ dependencies:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^1.10.11
flutter_launcher_icons: ^0.8.1 flutter_launcher_icons: ^0.8.1
hive_generator: ^0.8.2
json_serializable: ^3.5.1
flutter_icons: flutter_icons:
android: "launcher_icon" android: "launcher_icon"
ios: true ios: true
image_path: "assets/images/icon/logo.png" image_path_android: "assets/images/icon/logo_android.png"
image_path_ios: "assets/images/icon/logo_ios.png"
flutter: flutter:
uses-material-design: true uses-material-design: true