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 {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "pro.kherel.selfprivacy"
minSdkVersion 16
minSdkVersion 18
targetSdkVersion 29
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName

View File

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

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

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

View File

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

View File

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

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(
fontSize: 24,
fontWeight: NamedFontWeight.extraBold,
color: BrandColors.headlineColor,
);
final onboardingTitle = GoogleFonts.inter(
fontSize: 30,
fontWeight: NamedFontWeight.extraBold,
color: BrandColors.headlineColor,
@ -40,6 +46,8 @@ final body2Style = defaultTextStyle.copyWith(
color: BrandColors.textColor2,
);
final mediumStyle = defaultTextStyle.copyWith(fontSize: 13, height: 1.53);
final smallStyle = defaultTextStyle.copyWith(fontSize: 11, height: 1.45);
final linkStyle = defaultTextStyle.copyWith(color: BrandColors.blue);

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

View File

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

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

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

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_text/brand_text.dart';
import 'package:selfprivacy/ui/pages/initializing/initializing.dart';
import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart';
import 'package:selfprivacy/ui/pages/rootRoute.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart';
import 'about/about.dart';
@ -49,6 +51,11 @@ class MorePage extends StatelessWidget {
iconData: BrandIcons.help,
goTo: InfoPage(),
),
_NavItem(
title: 'Onboarding',
iconData: BrandIcons.triangle,
goTo: OnboardingPage(nextPage: RootPage()),
),
],
),
)

View File

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

View File

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

View File

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

View File

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

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

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

View File

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