add charts

master
Kherel 2021-04-10 05:04:23 +02:00
parent e4d5a4e01f
commit cd49f9fb45
21 changed files with 948 additions and 233 deletions

View File

@ -61,6 +61,11 @@
"1": "It's a virtual computer, where all your services live.",
"2": "General information",
"3": "Location"
},
"chart": {
"month": "Month",
"day": "Day",
"hour": "Hour"
}
},
"domain": {

View File

@ -61,6 +61,11 @@
"1": "Это виртульный компьютер на котором работают все ваши сервисы.",
"2": "Общая информация",
"3": "Размещение"
},
"chart": {
"month": "Месяц",
"day": "День",
"hour": "Час"
}
},
"domain": {

View File

@ -164,11 +164,21 @@ class HetznerApi extends ApiMap {
return server.copyWith(startTime: DateTime.now());
}
metrics() async {
Future<Map<String, dynamic>> getMetrics(DateTime start, DateTime end, String type) async {
var hetznerServer = getIt<ApiConfigModel>().hetznerServer;
var client = await getClient();
await client.post('/servers/${hetznerServer!.id}/metrics');
Map<String, dynamic> queryParameters = {
"start": start.toUtc().toIso8601String(),
"end": end.toUtc().toIso8601String(),
"type": type
};
var res = await client.get(
'/servers/${hetznerServer!.id}/metrics',
queryParameters: queryParameters,
);
close(client);
return res.data;
}
Future<HetznerServerInfo> getInfo() async {

View File

@ -8,3 +8,5 @@ enum InitializingSteps {
startServer,
checkSystemDnsAndDkimSet,
}
enum Period { hour, day, month }

View File

@ -0,0 +1,49 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
import 'package:selfprivacy/logic/models/hetzner_metrics.dart';
import 'hetzner_metrics_repository.dart';
part 'hetzner_metrics_state.dart';
class HetznerMetricsCubit extends Cubit<HetznerMetricsState> {
HetznerMetricsCubit() : super(HetznerMetricsLoading(Period.day));
final repository = HetznerMetricsRepository();
Timer? timer;
close() {
closeTimer();
return super.close();
}
void closeTimer() {
if (timer != null && timer!.isActive) {
timer!.cancel();
}
}
void changePeriod(Period period) async {
closeTimer();
emit(HetznerMetricsLoading(period));
load(period);
}
void restart() async {
load(state.period);
}
void load(Period period) async {
var newState = await repository.getMetrics(state.period);
timer = Timer(
Duration(seconds: newState.stepInSeconds.toInt()),
() => load(newState.period),
);
emit(newState);
}
}

View File

@ -0,0 +1,56 @@
import 'package:selfprivacy/logic/api_maps/hetzner.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
import 'package:selfprivacy/logic/models/hetzner_metrics.dart';
import 'hetzner_metrics_cubit.dart';
class HetznerMetricsRepository {
Future<HetznerMetricsLoaded> getMetrics(Period period) async {
var end = DateTime.now();
DateTime start;
switch (period) {
case Period.hour:
start = end.subtract(Duration(hours: 1));
break;
case Period.day:
start = end.subtract(Duration(days: 1));
break;
case Period.month:
start = end.subtract(Duration(days: 15));
break;
}
var api = HetznerApi();
var results = await Future.wait([
api.getMetrics(start, end, 'cpu'),
api.getMetrics(start, end, 'network'),
]);
var cpuMetricsData = results[0]["metrics"];
var networkMetricsData = results[1]["metrics"];
return HetznerMetricsLoaded(
period: period,
start: start,
end: end,
stepInSeconds: cpuMetricsData["step"],
cpu: timeSeriesSerializer(cpuMetricsData, 'cpu'),
ppsIn: timeSeriesSerializer(networkMetricsData, 'network.0.pps.in'),
ppsOut: timeSeriesSerializer(networkMetricsData, 'network.0.pps.out'),
bandwidthIn:
timeSeriesSerializer(networkMetricsData, 'network.0.bandwidth.in'),
bandwidthOut: timeSeriesSerializer(
networkMetricsData,
'network.0.bandwidth.out',
),
);
}
}
List<TimeSeriesData> timeSeriesSerializer(
Map<String, dynamic> json, String type) {
List list = json["time_series"][type]["values"];
return list.map((el) => TimeSeriesData(el[0], double.parse(el[1]))).toList();
}

View File

@ -0,0 +1,43 @@
part of 'hetzner_metrics_cubit.dart';
abstract class HetznerMetricsState extends Equatable {
const HetznerMetricsState();
abstract final Period period;
}
class HetznerMetricsLoading extends HetznerMetricsState {
HetznerMetricsLoading(this.period);
final Period period;
@override
List<Object?> get props => [period];
}
class HetznerMetricsLoaded extends HetznerMetricsState {
HetznerMetricsLoaded({
required this.period,
required this.start,
required this.end,
required this.stepInSeconds,
required this.cpu,
required this.ppsIn,
required this.ppsOut,
required this.bandwidthIn,
required this.bandwidthOut,
});
final Period period;
final DateTime start;
final DateTime end;
final num stepInSeconds;
final List<TimeSeriesData> cpu;
final List<TimeSeriesData> ppsIn;
final List<TimeSeriesData> ppsOut;
final List<TimeSeriesData> bandwidthIn;
final List<TimeSeriesData> bandwidthOut;
@override
List<Object?> get props => [period, start, end];
}

View File

@ -0,0 +1,11 @@
class TimeSeriesData {
TimeSeriesData(
this.secondsSinceEpoch,
this.value,
);
final int secondsSinceEpoch;
DateTime get time =>
DateTime.fromMillisecondsSinceEpoch(secondsSinceEpoch * 1000);
final double value;
}

View File

@ -0,0 +1,42 @@
import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.dart';
class BrandRadio extends StatelessWidget {
BrandRadio({
Key? key,
required this.isChecked,
}) : super(key: key);
final bool isChecked;
@override
Widget build(BuildContext context) {
return Container(
height: 20,
width: 20,
alignment: Alignment.center,
padding: EdgeInsets.all(2),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: _getBorder(),
),
child: isChecked
? Container(
height: 10,
width: 10,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: BrandColors.primary,
),
)
: null,
);
}
BoxBorder? _getBorder() {
return Border.all(
color: isChecked ? BrandColors.primary : BrandColors.gray1,
width: 2,
);
}
}

View File

@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import 'package:selfprivacy/ui/components/brand_radio/brand_radio.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.dart';
class BrandRadioTile extends StatelessWidget {
const BrandRadioTile({
Key? key,
required this.isChecked,
required this.text,
required this.onPress,
}) : super(key: key);
final bool isChecked;
final String text;
final VoidCallback onPress;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onPress,
behavior: HitTestBehavior.translucent,
child: Padding(
padding: EdgeInsets.all(2),
child: Row(
children: [
BrandRadio(
isChecked: isChecked,
),
SizedBox(width: 9),
BrandText.h5(text)
],
),
),
);
}
}

View File

@ -70,9 +70,9 @@ class _BrandTimerState extends State<BrandTimer> {
_durationToString(DateTime.now().difference(widget.startDateTime));
String _durationToString(Duration duration) {
var timeLeft = widget.duration - duration;
String twoDigits(int n) => n.toString().padLeft(2, "0");
String twoDigitSeconds =
twoDigits(widget.duration.inSeconds - duration.inSeconds.remainder(60));
String twoDigitSeconds = twoDigits(timeLeft.inSeconds);
return "timer.sec".tr(args: [twoDigitSeconds]);
}

View File

@ -82,8 +82,6 @@ class _Card extends StatelessWidget {
switch (provider.type) {
case ProviderType.server:
title = 'providers.server.card_title'.tr();
stableText = 'providers.domain.status'.tr();
stableText = 'providers.server.status'.tr();
onTap = () => Navigator.of(context).push(
SlideBottomRoute(

View File

@ -0,0 +1,166 @@
part of 'server_details.dart';
class _Chart extends StatelessWidget {
const _Chart({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
var cubit = context.watch<HetznerMetricsCubit>();
var period = cubit.state.period;
var state = cubit.state;
List<Widget> charts;
if (state is HetznerMetricsLoading) {
charts = [
Container(
height: 200,
alignment: Alignment.center,
child: Text('basis.loading'.tr()),
)
];
} else if (state is HetznerMetricsLoaded) {
charts = [
Legend(color: Colors.red, text: 'CPU %'),
getCpuChart(state),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
BrandText.small('Public Network interface packets per sec'),
SizedBox(width: 10),
Legend(color: Colors.red, text: 'IN'),
SizedBox(width: 5),
Legend(color: Colors.green, text: 'OUT'),
],
),
getPpsChart(state),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
BrandText.small('Public Network interface bytes per sec'),
SizedBox(width: 10),
Legend(color: Colors.red, text: 'IN'),
SizedBox(width: 5),
Legend(color: Colors.green, text: 'OUT'),
],
),
getBandwidthChart(state),
];
} else {
throw 'wrong state';
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 5),
child: Column(
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
BrandRadioTile(
isChecked: period == Period.month,
text: 'providers.server.chart.month'.tr(),
onPress: () => cubit.changePeriod(Period.month),
),
BrandRadioTile(
isChecked: period == Period.day,
text: 'providers.server.chart.day'.tr(),
onPress: () => cubit.changePeriod(Period.day),
),
BrandRadioTile(
isChecked: period == Period.hour,
text: 'providers.server.chart.hour'.tr(),
onPress: () => cubit.changePeriod(Period.hour),
),
],
),
),
...charts,
],
),
);
}
Widget getCpuChart(HetznerMetricsLoaded state) {
var data = state.cpu;
return Container(
height: 200,
child: CpuChart(data, state.period, state.start),
);
}
Widget getPpsChart(HetznerMetricsLoaded state) {
var ppsIn = state.ppsIn;
var ppsOut = state.ppsOut;
return Container(
height: 200,
child: NetworkChart(
[ppsIn, ppsOut],
state.period,
state.start,
),
);
}
Widget getBandwidthChart(HetznerMetricsLoaded state) {
var ppsIn = state.bandwidthIn;
var ppsOut = state.bandwidthOut;
return Container(
height: 200,
child: NetworkChart(
[ppsIn, ppsOut],
state.period,
state.start,
),
);
}
}
class Legend extends StatelessWidget {
const Legend({
Key? key,
required this.color,
required this.text,
}) : super(key: key);
final String text;
final Color color;
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
_ColoredBox(color: color),
SizedBox(width: 5),
BrandText.small(text),
],
);
}
}
class _ColoredBox extends StatelessWidget {
const _ColoredBox({
Key? key,
required this.color,
}) : super(key: key);
final Color color;
@override
Widget build(BuildContext context) {
return Container(
width: 10,
height: 10,
decoration: BoxDecoration(
color: color.withOpacity(0.3),
border: Border.all(
color: color,
)),
);
}
}

View File

@ -0,0 +1,109 @@
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
import 'package:selfprivacy/logic/models/hetzner_metrics.dart';
import 'package:intl/intl.dart';
class CpuChart extends StatelessWidget {
CpuChart(this.data, this.period, this.start);
final List<TimeSeriesData> data;
final Period period;
final DateTime start;
List<FlSpot> getSpots() {
var i = 0;
List<FlSpot> res = [];
for (var d in data) {
res.add(FlSpot(i.toDouble(), d.value));
i++;
}
return res;
}
@override
Widget build(BuildContext context) {
return LineChart(
LineChartData(
lineTouchData: LineTouchData(enabled: false),
lineBarsData: [
LineChartBarData(
spots: getSpots(),
isCurved: true,
barWidth: 1,
colors: [
Colors.red,
],
dotData: FlDotData(
show: false,
),
),
],
minY: 0,
maxY: 100,
minX: data.length - 200,
titlesData: FlTitlesData(
bottomTitles: SideTitles(
interval: 20,
rotateAngle: 90.0,
showTitles: true,
getTextStyles: (value) => const TextStyle(
fontSize: 10,
color: Colors.purple,
fontWeight: FontWeight.bold,
),
getTitles: (value) {
return bottomTitle(value.toInt());
}),
leftTitles: SideTitles(
margin: 15,
interval: 25,
showTitles: true,
),
),
gridData: FlGridData(show: true),
),
);
}
bool checkToShowTitle(
double minValue,
double maxValue,
SideTitles sideTitles,
double appliedInterval,
double value,
) {
print(value);
if (value < 0) {
return false;
} else if (value == 0) {
return true;
}
var _value = value - minValue;
var v = _value / 20;
return v - v.floor() == 0;
}
String bottomTitle(int value) {
final hhmm = DateFormat('HH:mm');
var day = DateFormat('MMMd');
String res;
if (value <= 0) {
return '';
}
var time = data[value].time;
switch (period) {
case Period.hour:
case Period.day:
res = hhmm.format(time);
break;
case Period.month:
res = day.format(time);
}
return res;
}
}

View File

@ -0,0 +1,61 @@
part of 'server_details.dart';
class _Header extends StatelessWidget {
const _Header({
Key? key,
required this.providerState,
required this.tabController,
}) : super(key: key);
final StateType providerState;
final TabController tabController;
@override
Widget build(BuildContext context) {
return Row(
children: [
IconStatusMask(
status: providerState,
child: Icon(
BrandIcons.server,
size: 40,
color: Colors.white,
),
),
SizedBox(width: 10),
BrandText.h2('providers.server.card_title'.tr()),
Spacer(),
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.setting:
tabController.animateTo(1);
break;
}
},
icon: Icon(Icons.more_vert),
itemBuilder: (BuildContext context) => [
PopupMenuItem<_PopupMenuItemType>(
value: _PopupMenuItemType.setting,
child: Container(
padding: EdgeInsets.only(left: 5),
child: Text('basis.settings'.tr()),
),
),
],
),
),
],
);
}
}
enum _PopupMenuItemType { setting }

View File

@ -0,0 +1,134 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
import 'package:selfprivacy/logic/models/hetzner_metrics.dart';
import 'package:intl/intl.dart';
class NetworkChart extends StatelessWidget {
NetworkChart(
this.listData,
this.period,
this.start,
);
final List<List<TimeSeriesData>> listData;
final Period period;
final DateTime start;
List<FlSpot> getSpots(data) {
var i = 0;
List<FlSpot> res = [];
for (var d in data) {
res.add(FlSpot(i.toDouble(), d.value));
i++;
}
return res;
}
@override
Widget build(BuildContext context) {
return SizedBox(
height: 150,
width: MediaQuery.of(context).size.width * 0.90,
child: LineChart(
LineChartData(
lineTouchData: LineTouchData(enabled: false),
lineBarsData: [
LineChartBarData(
spots: getSpots(listData[0]),
isCurved: true,
barWidth: 1,
colors: [Colors.red],
dotData: FlDotData(
show: false,
),
),
LineChartBarData(
spots: getSpots(listData[1]),
isCurved: true,
barWidth: 1,
colors: [Colors.green],
dotData: FlDotData(
show: false,
),
),
],
minY: 0,
maxY: [
...listData[0].map((e) => e.value),
...listData[1].map((e) => e.value)
].reduce(max) *
1.2,
minX: listData[0].length - 200,
titlesData: FlTitlesData(
bottomTitles: SideTitles(
interval: 20,
rotateAngle: 90.0,
showTitles: true,
getTextStyles: (value) => const TextStyle(
fontSize: 10,
color: Colors.purple,
fontWeight: FontWeight.bold,
),
getTitles: (value) {
return bottomTitle(value.toInt());
}),
leftTitles: SideTitles(
margin: 15,
interval: [
...listData[0].map((e) => e.value),
...listData[1].map((e) => e.value)
].reduce(max) *
1.2 /
10,
showTitles: true,
),
),
gridData: FlGridData(show: true),
),
),
);
}
bool checkToShowTitle(
double minValue,
double maxValue,
SideTitles sideTitles,
double appliedInterval,
double value,
) {
if (value < 0) {
return false;
} else if (value == 0) {
return true;
}
var _value = value - minValue;
var v = _value / 20;
return v - v.floor() == 0;
}
String bottomTitle(int value) {
final hhmm = DateFormat('HH:mm');
var day = DateFormat('MMMd');
String res;
if (value <= 0) {
return '';
}
var time = listData[0][value].time;
switch (period) {
case Period.hour:
case Period.day:
res = hhmm.format(time);
break;
case Period.month:
res = day.format(time);
}
return res;
}
}

View File

@ -2,18 +2,26 @@ import 'package:cubit_form/cubit_form.dart';
import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.dart';
import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/hetzner_metrics/hetzner_metrics_cubit.dart';
import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart';
import 'package:selfprivacy/logic/models/state_types.dart';
import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
import 'package:selfprivacy/ui/components/brand_radio_tile/brand_radio_tile.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:easy_localization/easy_localization.dart';
import 'package:selfprivacy/ui/components/switch_block/switch_bloc.dart';
import 'package:selfprivacy/utils/named_font_weight.dart';
import 'cpu_chart.dart';
import 'network_charts.dart';
part 'server_settings.dart';
part 'text_details.dart';
part 'chart.dart';
part 'header.dart';
var navigatorKey = GlobalKey<NavigatorState>();
@ -48,240 +56,41 @@ class _ServerDetailsState extends State<ServerDetails>
var isReady = context.watch<AppConfigCubit>().state.isFullyInitilized;
var providerState = isReady ? StateType.stable : StateType.uninitialized;
late String title = 'providers.server.card_title'.tr();
return TabBarView(
physics: NeverScrollableScrollPhysics(),
controller: tabController,
children: [
BlocProvider(
create: (context) => ServerDetailsCubit()..check(),
child: Builder(builder: (context) {
var details = context.watch<ServerDetailsCubit>().state;
if (details is ServerDetailsLoading ||
details is ServerDetailsInitial) {
return _TempMessage(message: 'basis.loading'.tr());
} else if (details is ServerDetailsNotReady) {
return _TempMessage(message: 'basis.no_data'.tr());
} else if (details is Loaded) {
var data = details.serverInfo;
var checkTime = details.checkTime;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: brandPagePadding2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
IconStatusMask(
status: providerState,
child: Icon(
BrandIcons.server,
size: 40,
color: Colors.white,
),
),
SizedBox(width: 10),
BrandText.h2(title),
Spacer(),
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.setting:
tabController.animateTo(1);
break;
}
},
icon: Icon(Icons.more_vert),
itemBuilder: (BuildContext context) => [
PopupMenuItem<_PopupMenuItemType>(
value: _PopupMenuItemType.setting,
child: Container(
padding: EdgeInsets.only(left: 5),
child: Text('basis.settings'.tr()),
),
),
],
),
),
],
),
SizedBox(height: 10),
BrandText.body1('providers.server.bottom_sheet.1'.tr()),
SizedBox(height: 30),
Center(child: BrandText.h2('providers.server.2'.tr())),
SizedBox(height: 10),
Table(
columnWidths: {
0: FractionColumnWidth(.5),
1: FractionColumnWidth(.5),
},
defaultVerticalAlignment:
TableCellVerticalAlignment.middle,
children: [
TableRow(
children: [
getRowTitle('Last check'),
getRowValue(formater.format(checkTime)),
],
),
TableRow(
children: [
getRowTitle('Server Id'),
getRowValue(data.id.toString()),
],
),
TableRow(
children: [
getRowTitle('Status:'),
getRowValue(
'${data.status.toString().split('.')[1].toUpperCase()}',
isBold: true,
),
],
),
TableRow(
children: [
getRowTitle('CPU'),
getRowValue(
data.serverType.cores.toString(),
),
],
),
TableRow(
children: [
getRowTitle('Memory'),
getRowValue(
'${data.serverType.memory.toString()} GB',
),
],
),
TableRow(
children: [
getRowTitle('Disk Local'),
getRowValue(
'${data.serverType.disk.toString()} GB',
),
],
),
TableRow(
children: [
getRowTitle('Price monthly:'),
getRowValue(
'${data.serverType.prices[1].monthly.toString()}',
),
],
),
TableRow(
children: [
getRowTitle('Price hourly:'),
getRowValue(
'${data.serverType.prices[1].hourly.toString()}',
),
],
),
],
),
SizedBox(height: 30),
Center(child: BrandText.h2('providers.server.3'.tr())),
SizedBox(height: 10),
Table(
columnWidths: {
0: FractionColumnWidth(.5),
1: FractionColumnWidth(.5),
},
defaultVerticalAlignment:
TableCellVerticalAlignment.middle,
children: [
TableRow(
children: [
getRowTitle('Country'),
getRowValue(
'${data.location.country}',
),
],
),
TableRow(
children: [
getRowTitle('City'),
getRowValue(data.location.city),
],
),
TableRow(
children: [
getRowTitle('Description'),
getRowValue(data.location.description),
],
),
],
),
],
SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: brandPagePadding2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_Header(
providerState: providerState,
tabController: tabController),
BrandText.body1('providers.server.bottom_sheet.1'.tr()),
SizedBox(height: 10),
BlocProvider(
create: (context) => HetznerMetricsCubit()..restart(),
child: _Chart(),
),
),
],
);
} else {
throw Exception('wrong state');
}
}),
SizedBox(height: 20),
BlocProvider(
create: (context) => ServerDetailsCubit()..check(),
child: _TextDetails(),
),
],
),
),
],
),
),
_ServerSettings(tabController: tabController),
],
);
}
Widget getRowTitle(String title) {
return Padding(
padding: const EdgeInsets.only(right: 10),
child: BrandText.h5(
title,
textAlign: TextAlign.right,
),
);
}
Widget getRowValue(String title, {bool isBold = false}) {
return BrandText.body1(
title,
style: isBold
? TextStyle(
fontWeight: NamedFontWeight.demiBold,
)
: null,
);
}
}
enum _PopupMenuItemType { setting }
class _TempMessage extends StatelessWidget {
const _TempMessage({
Key? key,
required this.message,
}) : super(key: key);
final String message;
@override
Widget build(BuildContext context) {
return SizedBox(
height: MediaQuery.of(context).size.height - 100,
child: Center(
child: BrandText.body2(message),
),
);
}
}
final DateFormat formater = DateFormat('HH:mm:ss');

View File

@ -0,0 +1,171 @@
part of 'server_details.dart';
class _TextDetails extends StatelessWidget {
const _TextDetails({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
var details = context.watch<ServerDetailsCubit>().state;
if (details is ServerDetailsLoading || details is ServerDetailsInitial) {
return _TempMessage(message: 'basis.loading'.tr());
} else if (details is ServerDetailsNotReady) {
return _TempMessage(message: 'basis.no_data'.tr());
} else if (details is Loaded) {
var data = details.serverInfo;
var checkTime = details.checkTime;
return Column(
children: [
Center(child: BrandText.h3('providers.server.bottom_sheet.2'.tr())),
SizedBox(height: 10),
Table(
columnWidths: {
0: FractionColumnWidth(.5),
1: FractionColumnWidth(.5),
},
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
children: [
TableRow(
children: [
getRowTitle('Last check'),
getRowValue(formater.format(checkTime)),
],
),
TableRow(
children: [
getRowTitle('Server Id'),
getRowValue(data.id.toString()),
],
),
TableRow(
children: [
getRowTitle('Status:'),
getRowValue(
'${data.status.toString().split('.')[1].toUpperCase()}',
isBold: true,
),
],
),
TableRow(
children: [
getRowTitle('CPU'),
getRowValue(
data.serverType.cores.toString(),
),
],
),
TableRow(
children: [
getRowTitle('Memory'),
getRowValue(
'${data.serverType.memory.toString()} GB',
),
],
),
TableRow(
children: [
getRowTitle('Disk Local'),
getRowValue(
'${data.serverType.disk.toString()} GB',
),
],
),
TableRow(
children: [
getRowTitle('Price monthly:'),
getRowValue(
'${data.serverType.prices[1].monthly.toString()}',
),
],
),
TableRow(
children: [
getRowTitle('Price hourly:'),
getRowValue(
'${data.serverType.prices[1].hourly.toString()}',
),
],
),
],
),
SizedBox(height: 30),
Center(child: BrandText.h3('providers.server.bottom_sheet.3'.tr())),
SizedBox(height: 10),
Table(
columnWidths: {
0: FractionColumnWidth(.5),
1: FractionColumnWidth(.5),
},
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
children: [
TableRow(
children: [
getRowTitle('Country'),
getRowValue(
'${data.location.country}',
),
],
),
TableRow(
children: [
getRowTitle('City'),
getRowValue(data.location.city),
],
),
TableRow(
children: [
getRowTitle('Description'),
getRowValue(data.location.description),
],
),
],
),
SizedBox(height: 20),
],
);
} else {
throw Exception('wrong state');
}
}
Widget getRowTitle(String title) {
return Padding(
padding: const EdgeInsets.only(right: 10),
child: BrandText.h5(
title,
textAlign: TextAlign.right,
),
);
}
Widget getRowValue(String title, {bool isBold = false}) {
return BrandText.body1(
title,
style: isBold
? TextStyle(
fontWeight: NamedFontWeight.demiBold,
)
: null,
);
}
}
class _TempMessage extends StatelessWidget {
const _TempMessage({
Key? key,
required this.message,
}) : super(key: key);
final String message;
@override
Widget build(BuildContext context) {
return SizedBox(
height: MediaQuery.of(context).size.height - 100,
child: Center(
child: BrandText.body2(message),
),
);
}
}
final DateFormat formater = DateFormat('HH:mm:ss');

View File

@ -397,7 +397,6 @@ class _ServiceDetails extends StatelessWidget {
try {
await launch(
url,
forceSafariVC: true,
enableJavaScript: true,
);
} catch (e) {

View File

@ -274,6 +274,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
fl_chart:
dependency: "direct main"
description:
name: fl_chart
url: "https://pub.dartlang.org"
source: hosted
version: "0.35.0"
flutter:
dependency: "direct main"
description: flutter

View File

@ -17,6 +17,7 @@ dependencies:
easy_localization: ^3.0.0
either_option: ^2.0.1-dev.1
equatable: ^2.0.0
fl_chart: ^0.35.0
flutter_bloc: ^7.0.0
flutter_markdown: ^0.6.0
flutter_secure_storage: ^4.1.0