fdroid
Kherel 2021-02-15 19:58:29 +01:00
parent a07a7247f5
commit 20166647ea
29 changed files with 707 additions and 367 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -49,4 +49,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c
COCOAPODS: 1.10.0
COCOAPODS: 1.10.1

View File

@ -1,16 +1,17 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:selfprivacy/ui/components/error/error.dart';
import 'package:selfprivacy/utils/route_transitions/basic.dart';
class SimpleBlocObserver extends BlocObserver {
final GlobalKey<NavigatorState> navigatorKey;
import 'get_it_config.dart';
SimpleBlocObserver({this.navigatorKey});
class SimpleBlocObserver extends BlocObserver {
SimpleBlocObserver();
@override
void onError(Cubit cubit, Object error, StackTrace stackTrace) {
navigatorKey.currentState.push(
final navigator = getIt.get<NavigationService>().navigator;
navigator.push(
materialRoute(
BrandError(
error: error,

View File

@ -56,6 +56,7 @@ var darkTheme = ligtTheme.copyWith(
scaffoldBackgroundColor: Color(0xFF202120),
iconTheme: IconThemeData(color: BrandColors.gray3),
cardColor: BrandColors.gray1,
dialogBackgroundColor: Color(0xFF202120),
textTheme: GoogleFonts.interTextTheme(
TextTheme(
headline1: headline1Style.copyWith(color: BrandColors.white),

View File

@ -1,10 +1,17 @@
import 'package:get_it/get_it.dart';
import 'package:selfprivacy/logic/get_it/console.dart';
import 'package:selfprivacy/logic/get_it/navigation.dart';
import 'package:selfprivacy/logic/get_it/timer.dart';
export 'package:selfprivacy/logic/get_it/console.dart';
export 'package:selfprivacy/logic/get_it/navigation.dart';
export 'package:selfprivacy/logic/get_it/timer.dart';
final getIt = GetIt.instance;
void getItSetup() {
getIt.registerSingleton<NavigationService>(NavigationService());
getIt.registerSingleton<ConsoleModel>(ConsoleModel());
getIt.registerSingleton<TimerModel>(TimerModel());
}

View File

@ -50,7 +50,7 @@ class BNames {
static String cloudFlareKey = 'cloudFlareKey';
static String rootUser = 'rootUser';
static String hetznerServer = 'hetznerServer';
static String isDkimSetted = 'isDkimSetted';
// static String isDkimSetted = 'isDkimSetted';
static String isDnsChecked = 'isDnsChecked';
static String isServerStarted = 'isServerStarted';
static String backblazeKey = 'backblazeKey';

View File

@ -46,6 +46,13 @@ final body2Style = defaultTextStyle.copyWith(
color: BrandColors.textColor2,
);
final buttonTitleText = defaultTextStyle.copyWith(
color: BrandColors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
height: 1,
);
final mediumStyle = defaultTextStyle.copyWith(fontSize: 13, height: 1.53);
final smallStyle = defaultTextStyle.copyWith(fontSize: 11, height: 1.45);

View File

@ -27,7 +27,6 @@ class BackblazeApi extends ApiMap {
Response response = await loggedClient.get(rootAddress, options: options);
if (response.statusCode == HttpStatus.ok) {
print(response);
return true;
} else if (response.statusCode == HttpStatus.unauthorized) {
return false;

View File

@ -105,20 +105,20 @@ class CloudflareApi extends ApiMap {
await Future.wait(allCreateFutures);
}
setDkim(String dkimRecordString, String domainZoneId) {
var txt3 = DnsRecords(
type: 'TXT',
name: 'selector._domainkey',
content: dkimRecordString,
ttl: 18000,
);
// setDkim(String dkimRecordString, String domainZoneId) {
// var txt3 = DnsRecords(
// type: 'TXT',
// name: 'selector._domainkey',
// content: dkimRecordString,
// ttl: 18000,
// );
var url = '$rootAddress/zones/$domainZoneId/dns_records';
loggedClient.post(
url,
data: txt3.toJson(),
);
}
// var url = '$rootAddress/zones/$domainZoneId/dns_records';
// loggedClient.post(
// url,
// data: txt3.toJson(),
// );
// }
List<DnsRecords> projectDnsRecords(String domainName, String ip4) {
var domainA = DnsRecords(type: 'A', name: domainName, content: ip4);
@ -160,4 +160,16 @@ class CloudflareApi extends ApiMap {
vpn
];
}
Future<List<String>> domainList() async {
var url = '$rootAddress/zones?per_page=50';
var response = await loggedClient.get(
url,
queryParameters: {'per_page': 50},
);
return response.data['result']
.map<String>((el) => el['name'] as String)
.toList();
}
}

View File

@ -60,6 +60,16 @@ class HetznerApi extends ApiMap {
);
}
Future<void> deleteSelfprivacyServer({
@required String cloudFlareKey,
}) async {
Response response = await loggedClient.get(rootAddress);
List list = response.data['servers'];
var server = list.firstWhere((el) => el['name'] == 'selfprivacy-server');
return await loggedClient.delete('$rootAddress/${server['id']}');
}
Future<HetznerServerDetails> startServer({
HetznerServerDetails server,
}) async {

View File

@ -1,4 +1,3 @@
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
@ -26,22 +25,22 @@ class ServerApi extends ApiMap {
return res;
}
Future<String> getDkim(String domainName) async {
var response = await loggedClient.get(
'/getDKIM',
options: Options(responseType: ResponseType.plain),
);
return _decodeAndCutData(response.data, domainName);
}
// Future<String> getDkim(String domainName) async {
// var response = await loggedClient.get(
// '/getDKIM',
// options: Options(responseType: ResponseType.plain),
// );
// return _decodeAndCutData(response.data, domainName);
// }
}
String _decodeAndCutData(String text, String domainName) {
var decodedTextString = text.substring(1, text.length - 1);
var stringToBase64 = utf8.fuse(base64);
// String _decodeAndCutData(String text, String domainName) {
// var decodedTextString = text.substring(1, text.length - 1);
// var stringToBase64 = utf8.fuse(base64);
return stringToBase64
.decode(decodedTextString)
.replaceAll("selector._domainkey IN TXT ( ", "")
.replaceAll("\"\n \"", "")
.replaceAll(' ) ; ----- DKIM key selector for $domainName\n', '');
}
// return stringToBase64
// .decode(decodedTextString)
// .replaceAll("selector._domainkey IN TXT ( ", "")
// .replaceAll("\"\n \"", "")
// .replaceAll(' ) ; ----- DKIM key selector for $domainName\n', '');
// }

View File

@ -14,22 +14,30 @@ import 'app_config_repository.dart';
part 'app_config_state.dart';
/// Initializing steps:
/// 1. Hetzner key |setHetznerKey
/// 2. Cloudflare key |setCloudflareKey
/// 3. Backblaze Id + Key |setBackblazeKey
/// 4. Set Domain address |setDomain
/// 5. Set Root user name password |setRootUser
/// 6. Set Create server ans set DNS-Records |createServerAndSetDnsRecords
///
/// The set phase.
/// 1.1. Hetzner key |setHetznerKey
/// 1.2. Cloudflare key |setCloudflareKey
/// 1.3. Backblaze Id + Key |setBackblazeKey
/// 1.4. Set Domain address |setDomain
/// 1.5. Set Root user name password |setRootUser
/// 1.6. Set Create server ans set DNS-Records |createServerAndSetDnsRecords
/// (without start)
/// 7. ChecksAndSets:
/// 7.1 checkDnsAndStartServer |checkDnsAndStartServer
/// 7.2 setDkim |setDkim
/// a. checkServer
/// b. getDkim
/// c. Set DKIM
/// d. server restart
///
/// The check phase.
///
/// 2.1. a. wait 60sec checkDnsAndStartServer |startServerIfDnsIsOkay
/// b. checkDns
/// c. if dns is okay start server
///
/// 2.2. a. wait 60sec |resetServerIfServerIsOkay
/// b. checkServer
/// c. if server is ok wait 30 sec
/// d. reset server
///
/// 2.3. a. wait 60sec |finishCheckIfServerIsOkay
/// b. checkServer
/// c. if server is okay set that fully checked
class AppConfigCubit extends Cubit<AppConfigState> {
AppConfigCubit() : super(InitialAppConfigState());
@ -66,34 +74,22 @@ class AppConfigCubit extends Cubit<AppConfigState> {
emit(state.copyWith(rootUser: rootUser));
}
void setDkim() async {
void serverReset() async {
var callBack = () async {
var isServerWorking = await repository.isHttpServerWorking(
state.cloudFlareDomain.domainName,
);
if (!isServerWorking) {
var last = DateTime.now();
print(last);
emit(state.copyWith(lastServerStatusCheckTime: last));
return;
}
await repository.setDkim(
state.cloudFlareDomain.domainName,
state.cloudFlareKey,
state.cloudFlareDomain.zoneId,
);
var hetznerServerDetails = await repository.restart(
state.hetznerKey,
state.hetznerServer,
);
emit(
state.copyWith(
isDkimSetted: true,
hetznerServer: hetznerServerDetails,
),
);
emit(state.copyWith(hetznerServer: hetznerServerDetails));
};
_tryOrAddError(state, callBack);
@ -125,14 +121,7 @@ class AppConfigCubit extends Cubit<AppConfigState> {
}
void createServerAndSetDnsRecords() async {
var callback = () async {
var serverDetails = await repository.createServer(
state.hetznerKey,
state.rootUser,
state.cloudFlareDomain.domainName,
state.cloudFlareKey,
);
var onSuccess = (serverDetails) async {
await repository.createDnsRecords(
state.cloudFlareKey,
serverDetails.ip4,
@ -144,6 +133,19 @@ class AppConfigCubit extends Cubit<AppConfigState> {
hetznerServer: serverDetails,
));
};
var onCancel = () => emit(state.copyWith(isLoading: false));
var callback = () async {
await repository.createServer(
state.hetznerKey,
state.rootUser,
state.cloudFlareDomain.domainName,
state.cloudFlareKey,
onCancel: onCancel,
onSuccess: onSuccess,
);
};
_tryOrAddError(state, callback);
}

View File

@ -1,3 +1,4 @@
import 'package:dio/dio.dart';
import 'package:hive/hive.dart';
import 'package:selfprivacy/config/hive_config.dart';
import 'package:selfprivacy/logic/api_maps/cloudflare.dart';
@ -11,6 +12,8 @@ import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/get_it/console.dart';
import 'package:selfprivacy/logic/models/message.dart';
import 'package:basic_utils/basic_utils.dart';
import 'package:selfprivacy/ui/components/action_button/action_button.dart';
import 'package:selfprivacy/ui/components/brand_alert/brand_alert.dart';
import 'app_config_cubit.dart';
class AppConfigRepository {
@ -26,7 +29,6 @@ class AppConfigRepository {
hetznerServer: box.get(BNames.hetznerServer),
isServerStarted: box.get(BNames.isServerStarted, defaultValue: false),
isDnsChecked: box.get(BNames.isDnsChecked, defaultValue: false),
isDkimSetted: box.get(BNames.isDkimSetted, defaultValue: false),
);
}
@ -110,19 +112,64 @@ class AppConfigRepository {
return true;
}
Future<HetznerServerDetails> createServer(String hetznerKey, User rootUser,
String domainName, String cloudFlareKey) async {
Future<void> createServer(
String hetznerKey,
User rootUser,
String domainName,
String cloudFlareKey, {
void Function() onCancel,
Future<void> Function(HetznerServerDetails serverDetails) onSuccess,
}) async {
var hetznerApi = HetznerApi(hetznerKey);
try {
var serverDetails = await hetznerApi.createServer(
cloudFlareKey: cloudFlareKey,
rootUser: rootUser,
domainName: domainName,
);
await box.put(BNames.hetznerServer, serverDetails);
hetznerApi.close();
onSuccess(serverDetails);
} on DioError catch (e) {
if (e.response.data['error']['code'] == 'uniqueness_error') {
var nav = getIt.get<NavigationService>();
nav.showPopUpDialog(
BrandAlert(
title: 'Сервер с таким именем уже существует',
contentText: 'Уничтожить сервер и создать новый?',
acitons: [
ActionButton(
text: 'Удалить',
isRed: true,
onPressed: () async {
await hetznerApi.deleteSelfprivacyServer(
cloudFlareKey: cloudFlareKey,
);
var serverDetails = await hetznerApi.createServer(
cloudFlareKey: cloudFlareKey,
rootUser: rootUser,
domainName: domainName,
);
hetznerApi.close();
return serverDetails;
await box.put(BNames.hetznerServer, serverDetails);
onSuccess(serverDetails);
},
),
ActionButton(
text: 'Отменить',
onPressed: () {
hetznerApi.close();
onCancel();
},
),
],
),
);
}
}
}
Future<void> createDnsRecords(
@ -152,21 +199,6 @@ class AppConfigRepository {
return isHttpServerWorking;
}
Future<void> setDkim(
String domainName,
String cloudFlareKey,
String zoneId,
) async {
var api = ServerApi(domainName);
var dkimRecordString = await api.getDkim(domainName);
var cloudflareApi = CloudflareApi(cloudFlareKey);
await cloudflareApi.setDkim(dkimRecordString, zoneId);
box.put(BNames.isDkimSetted, true);
cloudflareApi.close();
}
Future<HetznerServerDetails> restart(
String hetznerKey,
HetznerServerDetails server,

View File

@ -10,11 +10,10 @@ class AppConfigState extends Equatable {
this.hetznerServer,
this.isLoading = false,
this.error,
this.lastDnsCheckTime,
this.lastServerStatusCheckTime,
// this.lastDnsCheckTime,
// this.lastServerStatusCheckTime,
this.isDnsChecked = false,
this.isServerStarted = false,
this.isDkimSetted = false,
});
@override
@ -28,9 +27,8 @@ class AppConfigState extends Equatable {
isDnsCheckedAndServerStarted,
isLoading,
error,
lastDnsCheckTime,
lastServerStatusCheckTime,
isDkimSetted,
// lastDnsCheckTime,
// lastServerStatusCheckTime,
];
final String hetznerKey;
@ -39,11 +37,10 @@ class AppConfigState extends Equatable {
final CloudFlareDomain cloudFlareDomain;
final User rootUser;
final HetznerServerDetails hetznerServer;
final bool isDkimSetted;
final bool isServerStarted;
final bool isDnsChecked;
final DateTime lastDnsCheckTime;
final DateTime lastServerStatusCheckTime;
// final DateTime lastDnsCheckTime;
// final DateTime lastServerStatusCheckTime;
final bool isLoading;
final Exception error;
@ -58,7 +55,6 @@ class AppConfigState extends Equatable {
Exception error,
DateTime lastDnsCheckTime,
DateTime lastServerStatusCheckTime,
bool isDkimSetted,
bool isServerStarted,
bool isDnsChecked,
}) =>
@ -73,10 +69,9 @@ class AppConfigState extends Equatable {
isDnsChecked: isDnsChecked ?? this.isDnsChecked,
isLoading: isLoading ?? this.isLoading,
error: error ?? this.error,
lastDnsCheckTime: lastDnsCheckTime ?? this.lastDnsCheckTime,
lastServerStatusCheckTime:
lastServerStatusCheckTime ?? this.lastServerStatusCheckTime,
isDkimSetted: isDkimSetted ?? this.isDkimSetted,
// lastDnsCheckTime: lastDnsCheckTime ?? this.lastDnsCheckTime,
// lastServerStatusCheckTime:
// lastServerStatusCheckTime ?? this.lastServerStatusCheckTime,
);
bool get isHetznerFilled => hetznerKey != null;
@ -85,7 +80,7 @@ class AppConfigState extends Equatable {
bool get isDomainFilled => cloudFlareDomain != null;
bool get isUserFilled => rootUser != null;
bool get isServerFilled => hetznerServer != null;
bool get hasFinalChecked => isDnsCheckedAndServerStarted && isDkimSetted;
bool get hasFinalChecked => isDnsCheckedAndServerStarted;
bool get isDnsCheckedAndServerStarted => isDnsChecked && isServerStarted;
@ -107,3 +102,22 @@ class AppConfigState extends Equatable {
class InitialAppConfigState extends AppConfigState {
InitialAppConfigState() : super();
}
class TimerState extends AppConfigState {
TimerState({
this.dataState,
this.timerStart,
this.duration,
}) : super();
final AppConfigState dataState;
final DateTime timerStart;
final Duration duration;
@override
List<Object> get props => [
dataState,
timerStart,
duration,
];
}

View File

@ -0,0 +1,78 @@
import 'package:cubit_form/cubit_form.dart';
import 'package:selfprivacy/logic/api_maps/cloudflare.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
class DomainSetupCubit extends Cubit<DomainSetupState> {
DomainSetupCubit(this.initializingCubit) : super(Initial()) {
var token = (initializingCubit.state.cloudFlareKey);
assert(token != null, 'no cloudflare token');
api = CloudflareApi(token);
}
AppConfigCubit initializingCubit;
CloudflareApi api;
Future<void> load() async {
emit(Loading(LoadingTypes.loadingDomain));
var list = await api.domainList();
if (list.isEmpty) {
emit(Empty());
} else if (list.length == 1) {
emit(Loaded(list.first));
} else {
emit(MoreThenOne());
}
}
@override
Future<void> close() {
api.close();
return super.close();
}
Future<void> saveDomain() async {
assert(state is Loaded, 'wrong state');
var domainName = (state as Loaded).domain;
emit(Loading(LoadingTypes.saving));
var zoneId = await api.getZoneId(
initializingCubit.state.cloudFlareKey,
domainName,
);
var domain = CloudFlareDomain(
domainName: domainName,
zoneId: zoneId,
);
initializingCubit.setDomain(domain);
emit(DomainSetted());
}
}
abstract class DomainSetupState {}
class Initial extends DomainSetupState {}
class Empty extends DomainSetupState {}
class MoreThenOne extends DomainSetupState {}
class Loading extends DomainSetupState {
Loading(this.type);
final LoadingTypes type;
}
enum LoadingTypes { loadingDomain, saving }
class Loaded extends DomainSetupState {
final String domain;
Loaded(this.domain);
}
class DomainSetted extends DomainSetupState {}

View File

@ -1,68 +1,68 @@
import 'dart:async';
// import 'dart:async';
import 'package:cubit_form/cubit_form.dart';
import 'package:selfprivacy/logic/api_maps/cloudflare.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/models/cloudflare_domain.dart';
// import 'package:cubit_form/cubit_form.dart';
// import 'package:selfprivacy/logic/api_maps/cloudflare.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();
// 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',
),
],
);
// 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]);
}
// super.setFields([domainName]);
// }
@override
FutureOr<void> onSubmit() async {
var domain = CloudFlareDomain(
domainName: domainName.state.value,
zoneId: zoneId,
);
initializingCubit.setDomain(domain);
}
// @override
// FutureOr<void> onSubmit() async {
// var domain = CloudFlareDomain(
// domainName: domainName.state.value,
// zoneId: zoneId,
// );
// initializingCubit.setDomain(domain);
// }
final AppConfigCubit initializingCubit;
// final AppConfigCubit initializingCubit;
FieldCubit<String> domainName;
String zoneId;
// FieldCubit<String> domainName;
// String zoneId;
@override
FutureOr<bool> asyncValidation() async {
var key = initializingCubit.state.cloudFlareKey;
// @override
// FutureOr<bool> asyncValidation() async {
// var key = initializingCubit.state.cloudFlareKey;
String zoneId;
// String zoneId;
try {
zoneId = await apiClient.getZoneId(key, domainName.state.value);
} catch (e) {
addError(e);
}
// try {
// zoneId = await apiClient.getZoneId(key, domainName.state.value);
// } catch (e) {
// addError(e);
// }
if (zoneId == null) {
domainName.setError('Domain not in the list');
return false;
}
this.zoneId = zoneId;
return true;
}
// if (zoneId == null) {
// domainName.setError('Domain not in the list');
// return false;
// }
// this.zoneId = zoneId;
// return true;
// }
@override
Future<void> close() async {
apiClient.close();
// @override
// Future<void> close() async {
// apiClient.close();
return super.close();
}
}
// return super.close();
// }
// }

View File

@ -30,7 +30,9 @@ class RootUserFormCubit extends FormCubit {
],
);
super.setFields([userName, password]);
isVisible = FieldCubit(initalValue: false);
super.setFields([userName, password, isVisible]);
}
@override
@ -46,6 +48,7 @@ class RootUserFormCubit extends FormCubit {
FieldCubit<String> userName;
FieldCubit<String> password;
FieldCubit<bool> isVisible;
@override
Future<void> close() async {

View File

@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class NavigationService {
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
NavigatorState get navigator => navigatorKey.currentState;
void showPopUpDialog(AlertDialog dialog) {
final context = navigatorKey.currentState.overlay.context;
showDialog(
context: context,
builder: (_) => dialog,
);
}
}

View File

@ -15,11 +15,10 @@ import 'config/get_it_config.dart';
import 'config/localization.dart';
import 'logic/cubit/app_settings/app_settings_cubit.dart';
final navigatorKey = GlobalKey<NavigatorState>();
void main() async {
await HiveConfig.init();
Bloc.observer = SimpleBlocObserver(navigatorKey: navigatorKey);
Bloc.observer = SimpleBlocObserver();
Wakelock.enable();
getItSetup();
WidgetsFlutterBinding.ensureInitialized();
@ -41,7 +40,7 @@ class MyApp extends StatelessWidget {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.light, // Manually changnig appbar color
child: MaterialApp(
navigatorKey: navigatorKey,
navigatorKey: getIt.get<NavigationService>().navigatorKey,
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,

View File

@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.dart';
class ActionButton extends StatelessWidget {
const ActionButton({
Key key,
this.text,
this.onPressed,
this.isRed = false,
}) : super(key: key);
final VoidCallback onPressed;
final String text;
final bool isRed;
@override
Widget build(BuildContext context) {
var navigator = Navigator.of(context);
return TextButton(
child: Text(
text,
style: isRed ? TextStyle(color: BrandColors.red1) : null,
),
onPressed: () {
navigator.pop();
if (onPressed != null) onPressed();
},
);
}
}

View File

@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
class BrandAlert extends AlertDialog {
BrandAlert({
Key key,
String title,
String contentText,
List<Widget> acitons,
}) : super(
key: key,
title: title != null ? Text(title) : null,
content: title != null ? Text(contentText) : null,
actions: acitons,
);
}

View File

@ -6,42 +6,32 @@ import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
enum BrandButtonTypes { rised, text, iconText }
class BrandButton extends StatelessWidget {
const BrandButton({
Key key,
this.onPressed,
this.type,
this.title,
this.icon,
}) : super(key: key);
final VoidCallback onPressed;
final BrandButtonTypes type;
final String title;
final Icon icon;
class BrandButton {
static rised({
Key key,
@required VoidCallback onPressed,
@required String title,
}) =>
BrandButton(
String title,
Widget child,
}) {
assert(title == null || child == null, 'required title or child');
assert(title != null || child != null, 'required title or child');
return _RisedButton(
key: key,
onPressed: onPressed,
title: title,
type: BrandButtonTypes.rised,
onPressed: onPressed,
child: child,
);
}
static text({
Key key,
@required VoidCallback onPressed,
@required String title,
}) =>
BrandButton(
_TextButton(
key: key,
onPressed: onPressed,
title: title,
type: BrandButtonTypes.text,
onPressed: onPressed,
);
static iconText({
@ -50,38 +40,12 @@ class BrandButton extends StatelessWidget {
@required String title,
@required Icon icon,
}) =>
BrandButton(
_IconTextButton(
key: key,
onPressed: onPressed,
title: title,
type: BrandButtonTypes.iconText,
icon: icon,
);
@override
Widget build(BuildContext context) {
switch (type) {
case BrandButtonTypes.rised:
return _RisedButton(
title: title,
onPressed: onPressed,
);
case BrandButtonTypes.text:
return _TextButton(
title: title,
onPressed: onPressed,
);
break;
case BrandButtonTypes.iconText:
return _IconTextButton(
title: title,
onPressed: onPressed,
icon: icon,
);
break;
}
return null;
}
}
class _RisedButton extends StatelessWidget {
@ -89,10 +53,12 @@ class _RisedButton extends StatelessWidget {
Key key,
this.onPressed,
this.title,
this.child,
}) : super(key: key);
final VoidCallback onPressed;
final String title;
final Widget child;
@override
Widget build(BuildContext context) {
@ -111,15 +77,7 @@ class _RisedButton extends StatelessWidget {
width: double.infinity,
alignment: Alignment.center,
padding: EdgeInsets.all(12),
child: Text(
title,
style: TextStyle(
color: BrandColors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
height: 1,
),
),
child: child ?? BrandText.buttonTitleText(title),
),
),
),

View File

@ -10,18 +10,20 @@ enum TextType {
body2, // with opacity
medium,
small,
onboardingTitle
onboardingTitle,
buttonTitleText // risen button title text,
}
class BrandText extends StatelessWidget {
const BrandText(this.text,
{Key key,
const BrandText(
this.text, {
Key key,
this.style,
@required this.type,
this.overflow,
this.softWrap,
this.textAlign})
: super(key: key);
this.textAlign,
}) : super(key: key);
final String text;
final TextStyle style;
@ -53,10 +55,13 @@ class BrandText extends StatelessWidget {
type: TextType.h2,
style: style,
);
factory BrandText.h3(String text, {TextStyle style}) => BrandText(
factory BrandText.h3(String text, {TextStyle style, TextAlign textAlign}) =>
BrandText(
text,
type: TextType.h3,
style: style,
textAlign: textAlign,
overflow: TextOverflow.ellipsis,
);
factory BrandText.h4(String text, {TextStyle style}) => BrandText(
text,
@ -75,13 +80,23 @@ class BrandText extends StatelessWidget {
);
factory BrandText.medium(String text,
{TextStyle style, TextAlign textAlign}) =>
BrandText(text,
type: TextType.medium, style: style, textAlign: textAlign);
BrandText(
text,
type: TextType.medium,
style: style,
textAlign: textAlign,
);
factory BrandText.small(String text, {TextStyle style}) => BrandText(
text,
type: TextType.small,
style: style,
);
factory BrandText.buttonTitleText(String text, {TextStyle style}) =>
BrandText(
text,
type: TextType.buttonTitleText,
style: style,
);
@override
Text build(BuildContext context) {
TextStyle style;
@ -128,6 +143,11 @@ class BrandText extends StatelessWidget {
style =
isDark ? mediumStyle.copyWith(color: Colors.white) : mediumStyle;
break;
case TextType.buttonTitleText:
style = !isDark
? buttonTitleText.copyWith(color: Colors.white)
: buttonTitleText;
break;
}
if (this.style != null) {
style = style.merge(this.style);

View File

@ -9,12 +9,10 @@ class BrandTimer extends StatefulWidget {
Key key,
@required this.startDateTime,
@required this.duration,
@required this.callback,
}) : super(key: key);
final DateTime startDateTime;
final Duration duration;
final VoidCallback callback;
@override
_BrandTimerState createState() => _BrandTimerState();
@ -36,7 +34,6 @@ class _BrandTimerState extends State<BrandTimer> {
var timePassed = DateTime.now().difference(widget.startDateTime);
if (timePassed > widget.duration) {
t.cancel();
widget.callback();
} else {
_getTime();
}

View File

@ -6,7 +6,7 @@ import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/config/text_themes.dart';
import 'package:selfprivacy/logic/cubit/forms/initializing/backblaze_form_cubit.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/domain_cloudflare.dart';
import 'package:selfprivacy/logic/cubit/forms/initializing/hetzner_form_cubit.dart';
import 'package:selfprivacy/logic/cubit/forms/initializing/root_user_form_cubit.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
@ -16,7 +16,6 @@ import 'package:selfprivacy/ui/components/brand_card/brand_card.dart';
import 'package:selfprivacy/ui/components/brand_modal_sheet/brand_modal_sheet.dart';
import 'package:selfprivacy/ui/components/brand_span_button/brand_span_button.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
import 'package:selfprivacy/ui/components/brand_timer/brand_timer.dart';
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';
@ -34,7 +33,8 @@ class InitializingPage extends StatelessWidget {
_stepServer(cubit),
_stepCheck(cubit),
Container(child: Text('Everythigng is initialized'))
][2];
][cubit.state.progress];
return BlocListener<AppConfigCubit, AppConfigState>(
listener: (context, state) {
if (state.isFullyInitilized) {
@ -96,8 +96,10 @@ class InitializingPage extends StatelessWidget {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Spacer(),
Image.asset('assets/images/logos/hetzner.png'),
Image.asset(
'assets/images/logos/hetzner.png',
width: 150,
),
SizedBox(height: 10),
BrandText.h2('Подключите сервер Hetzner'),
SizedBox(height: 10),
@ -149,8 +151,11 @@ class InitializingPage extends StatelessWidget {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Spacer(),
Image.asset('assets/images/logos/cloudflare.png'),
Image.asset(
'assets/images/logos/cloudflare.png',
width: 150,
),
SizedBox(height: 10),
BrandText.h2('Подключите CloudFlare'),
SizedBox(height: 10),
BrandText.body2('Для управления DNS вашего домена'),
@ -188,12 +193,13 @@ class InitializingPage extends StatelessWidget {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Spacer(),
Image.asset('assets/images/logos/backblaze.png'),
Image.asset(
'assets/images/logos/backblaze.png',
height: 50,
),
SizedBox(height: 10),
BrandText.h2('Подключите облачное хранилище Backblaze'),
SizedBox(height: 10),
BrandText.body2('Здесь будут храниться данные'),
Spacer(),
CubitFormTextField(
formFieldCubit: formCubit.keyId,
@ -231,30 +237,90 @@ class InitializingPage extends StatelessWidget {
Widget _stepDomain(AppConfigCubit initializingCubit) {
return BlocProvider(
create: (context) => DomainFormCubit(initializingCubit),
create: (context) => DomainSetupCubit(initializingCubit)..load(),
child: Builder(builder: (context) {
var formCubit = context.watch<DomainFormCubit>();
var domainSetup = context.watch<DomainSetupCubit>();
var state = domainSetup.state;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Spacer(),
BrandText.h2('Введите домен:'),
Image.asset(
'assets/images/logos/cloudflare.png',
width: 150,
),
SizedBox(height: 30),
BrandText.h2('Домен'),
SizedBox(height: 10),
CubitFormTextField(
keyboardType: TextInputType.emailAddress,
formFieldCubit: formCubit.domainName,
if (state is Empty)
BrandText.body2('На данный момент подлюченных доменов нет'),
if (state is Loading)
BrandText.body2(
state.type == LoadingTypes.loadingDomain
? 'Загружаем список доменов'
: 'Сохранение..',
),
if (state is MoreThenOne)
BrandText.body2(
'Найдено больше одного домена, для вашей безопастности, просим вам удалить не нужные домены',
),
if (state is Loaded) ...[
SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: BrandText.h3(
'${state.domain}',
textAlign: TextAlign.center,
scrollPadding: EdgeInsets.only(bottom: 70),
decoration: InputDecoration(
hintText: 'Домен',
),
),
Spacer(),
Container(
width: 50,
child: BrandButton.rised(
onPressed: () => domainSetup.load(),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.refresh,
color: Colors.white,
),
],
),
),
),
],
)
],
if (state is Empty) ...[
SizedBox(height: 30),
BrandButton.rised(
onPressed:
formCubit.state.isSubmitting ? null : formCubit.trySubmit,
title: 'Подключить',
onPressed: () => domainSetup.load(),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.refresh,
color: Colors.white,
),
SizedBox(width: 10),
BrandText.buttonTitleText('Обновить cписок'),
],
),
),
],
if (state is Loaded) ...[
SizedBox(height: 30),
BrandButton.rised(
onPressed: () => domainSetup.saveDomain(),
title: 'Сохранить домен',
),
],
SizedBox(height: 10),
Spacer(),
SizedBox(height: 10),
BrandButton.text(
onPressed: () => _showModal(context, _HowHetzner()),
@ -286,13 +352,29 @@ class InitializingPage extends StatelessWidget {
),
),
SizedBox(height: 10),
CubitFormTextField(
BlocBuilder<FieldCubit<bool>, FieldCubitState<bool>>(
cubit: formCubit.isVisible,
builder: (context, state) {
var isVisible = state.value;
return CubitFormTextField(
obscureText: !isVisible,
formFieldCubit: formCubit.password,
textAlign: TextAlign.center,
scrollPadding: EdgeInsets.only(bottom: 70),
decoration: InputDecoration(
hintText: 'Пароль',
suffixIcon: IconButton(
icon: Icon(
isVisible ? Icons.visibility : Icons.visibility_off,
),
onPressed: () => formCubit.isVisible.setValue(!isVisible),
),
suffixIconConstraints: BoxConstraints(minWidth: 60),
prefixIconConstraints: BoxConstraints(maxWidth: 85),
prefixIcon: Container(),
),
);
},
),
Spacer(),
BrandButton.rised(
@ -338,52 +420,40 @@ class InitializingPage extends StatelessWidget {
}
Widget _stepCheck(AppConfigCubit appConfigCubit) {
var state = appConfigCubit.state;
var isDnsChecked = state.isDnsCheckedAndServerStarted;
return Builder(builder: (context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Spacer(flex: 2),
SizedBox(height: 10),
BrandText.body2(
isDnsChecked
? 'Dns сервера вступили в силу, мы стартанули сервер, как только он поднимется, мы закончим инициализацию.'
: 'Мы начали процесс инциализации сервера, раз в минуту мы будем проверять наличие DNS записей, как только они вступят в силу мы продолжим инциализацию',
),
SizedBox(height: 10),
Row(
children: [
BrandText.body2('До следующей проверки: '),
isDnsChecked
? BrandTimer(
startDateTime: state.lastServerStatusCheckTime ??
state.hetznerServer.startTime,
duration: Duration(minutes: 1),
callback: () {
appConfigCubit.setDkim();
},
)
: BrandTimer(
startDateTime: state.lastDnsCheckTime ??
state.hetznerServer.createTime,
duration: Duration(minutes: 1),
callback: () {
appConfigCubit.checkDnsAndStartServer();
},
)
],
),
Spacer(
flex: 2,
),
BrandButton.text(
onPressed: () => _showModal(context, _HowHetzner()),
title: 'Что это значит?',
),
],
);
});
return Text('step check');
// var state = appConfigCubit.state as TimerState;
// var isDnsChecked = state.dataState.isDnsChecked;
// return Builder(builder: (context) {
// return Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Spacer(flex: 2),
// SizedBox(height: 10),
// BrandText.body2(
// isDnsChecked
// ? 'Dns сервера вступили в силу, мы стартанули сервер, как только он поднимется, мы закончим инициализацию.'
// : 'Мы начали процесс инциализации сервера, раз в минуту мы будем проверять наличие DNS записей, как только они вступят в силу мы продолжим инциализацию',
// ),
// SizedBox(height: 10),
// Row(
// children: [
// BrandText.body2('До следующей проверки: '),
// BrandTimer(
// startDateTime: state.timerStart,
// duration: state.duration,
// )
// ],
// ),
// Spacer(
// flex: 2,
// ),
// BrandButton.text(
// onPressed: () => _showModal(context, _HowHetzner()),
// title: 'Что это значит?',
// ),
// ],
// );
// });
}
Widget _addCard(Widget child) {

View File

@ -3,6 +3,8 @@ 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/action_button/action_button.dart';
import 'package:selfprivacy/ui/components/brand_alert/brand_alert.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';
@ -90,36 +92,25 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
onPressed: () {
showDialog(
context: context,
child: AlertDialog(
title: Text('Are you sure?'),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text('Reset all keys?'),
child: BrandAlert(
title: 'Вы уверенны',
contentText: 'Сбросить все ключи?',
acitons: [
ActionButton(
text: 'Да, сбросить',
isRed: true,
onPressed: () {
context
.read<AppConfigCubit>()
.clearAppConfig();
Navigator.of(context).pop();
}),
ActionButton(
text: 'Отмена',
),
],
),
),
actions: <Widget>[
TextButton(
child: Text(
'Reset',
style: TextStyle(
color: BrandColors.red1,
),
),
onPressed: () {
context.read<AppConfigCubit>().clearAppConfig();
Navigator.of(context)..pop()..pop();
},
),
TextButton(
child: Text('Cancel'),
onPressed: () {
Navigator.of(context)..pop();
},
),
],
));
);
},
)
],

View File

@ -7,14 +7,14 @@ packages:
name: _fe_analyzer_shared
url: "https://pub.dartlang.org"
source: hosted
version: "14.0.0"
version: "12.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
url: "https://pub.dartlang.org"
source: hosted
version: "0.41.1"
version: "0.40.6"
archive:
dependency: transitive
description:
@ -169,6 +169,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
coverage:
dependency: transitive
description:
name: coverage
url: "https://pub.dartlang.org"
source: hosted
version: "0.14.2"
crypto:
dependency: "direct main"
description:
@ -225,6 +232,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.3"
either_option:
dependency: "direct main"
description:
name: either_option
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.6"
email_validator:
dependency: transitive
description:
@ -419,7 +433,7 @@ packages:
name: js
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.2"
version: "0.6.3-nullsafety.2"
json_annotation:
dependency: "direct main"
description:
@ -490,6 +504,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
node_preamble:
dependency: transitive
description:
name: node_preamble
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.13"
package_config:
dependency: transitive
description:
@ -552,7 +573,7 @@ packages:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.2"
version: "1.10.0-nullsafety.2"
petitparser:
dependency: transitive
description:
@ -587,7 +608,7 @@ packages:
name: pool
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.0"
version: "1.5.0-nullsafety.2"
process:
dependency: transitive
description:
@ -672,6 +693,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.7.9"
shelf_packages_handler:
dependency: transitive
description:
name: shelf_packages_handler
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
shelf_static:
dependency: transitive
description:
name: shelf_static
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.9+2"
shelf_web_socket:
dependency: transitive
description:
@ -698,6 +733,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.10+1"
source_map_stack_trace:
dependency: transitive
description:
name: source_map_stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0-nullsafety.3"
source_maps:
dependency: transitive
description:
name: source_maps
url: "https://pub.dartlang.org"
source: hosted
version: "0.10.10-nullsafety.2"
source_span:
dependency: transitive
description:
@ -740,6 +789,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0-nullsafety.1"
test:
dependency: transitive
description:
name: test
url: "https://pub.dartlang.org"
source: hosted
version: "1.16.0-nullsafety.5"
test_api:
dependency: transitive
description:
@ -747,6 +803,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.19-nullsafety.2"
test_core:
dependency: transitive
description:
name: test_core
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.12-nullsafety.5"
time:
dependency: transitive
description:
@ -824,6 +887,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0-nullsafety.3"
vm_service:
dependency: transitive
description:
name: vm_service
url: "https://pub.dartlang.org"
source: hosted
version: "5.5.0"
wakelock:
dependency: "direct main"
description:
@ -859,6 +929,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
webkit_inspection_protocol:
dependency: transitive
description:
name: webkit_inspection_protocol
url: "https://pub.dartlang.org"
source: hosted
version: "0.7.5"
win32:
dependency: transitive
description:

View File

@ -14,6 +14,7 @@ dependencies:
cupertino_icons: ^1.0.0
dio: ^3.0.10
easy_localization: ^2.3.3
either_option: ^1.0.6
equatable: ^1.2.5
flutter_bloc: ^6.1.1
flutter_secure_storage: ^3.3.5