#29 opt. for material 3
@@ -48,7 +48,7 @@ If ServerBox app has any bug, please open an [issue](https://github.com/lollipop
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<img width="200px" src="imgs/server.jpg">
|
||||
<img width="200px" src="imgs/server.jpeg">
|
||||
</td>
|
||||
<td>
|
||||
<img width="200px" src="imgs/detail.jpg">
|
||||
@@ -67,10 +67,10 @@ If ServerBox app has any bug, please open an [issue](https://github.com/lollipop
|
||||
<img width="200px" src="imgs/ping.png">
|
||||
</td>
|
||||
<td>
|
||||
<img width="200px" src="imgs/sftp.jpg">
|
||||
<img width="200px" src="imgs/sftp.jpeg">
|
||||
</td>
|
||||
<td>
|
||||
<img width="200px" src="imgs/docker.jpg">
|
||||
<img width="200px" src="imgs/docker.jpeg">
|
||||
</td>
|
||||
<td>
|
||||
<img width="200px" src="imgs/convert.png">
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<img width="200px" src="imgs/server.jpg">
|
||||
<img width="200px" src="imgs/server.jpeg">
|
||||
</td>
|
||||
<td>
|
||||
<img width="200px" src="imgs/detail.jpg">
|
||||
@@ -67,10 +67,10 @@
|
||||
<img width="200px" src="imgs/ping.png">
|
||||
</td>
|
||||
<td>
|
||||
<img width="200px" src="imgs/sftp.jpg">
|
||||
<img width="200px" src="imgs/sftp.jpeg">
|
||||
</td>
|
||||
<td>
|
||||
<img width="200px" src="imgs/docker.jpg">
|
||||
<img width="200px" src="imgs/docker.jpeg">
|
||||
</td>
|
||||
<td>
|
||||
<img width="200px" src="imgs/convert.png">
|
||||
|
||||
BIN
imgs/detail.jpg
|
Before Width: | Height: | Size: 235 KiB After Width: | Height: | Size: 297 KiB |
BIN
imgs/docker.jpeg
Normal file
|
After Width: | Height: | Size: 190 KiB |
BIN
imgs/docker.jpg
|
Before Width: | Height: | Size: 133 KiB |
BIN
imgs/server.jpeg
Normal file
|
After Width: | Height: | Size: 273 KiB |
BIN
imgs/server.jpg
|
Before Width: | Height: | Size: 392 KiB |
BIN
imgs/sftp.jpeg
Normal file
|
After Width: | Height: | Size: 323 KiB |
BIN
imgs/sftp.jpg
|
Before Width: | Height: | Size: 240 KiB |
@@ -8,4 +8,4 @@ Color primaryColor = Color(locator<SettingStore>().primaryColor.fetch()!);
|
||||
|
||||
final contentColor = DynamicColor(Colors.black87, Colors.white70);
|
||||
final bgColor = DynamicColor(Colors.white, Colors.black);
|
||||
final progressColor = DynamicColor(Colors.grey.shade100, Colors.white10);
|
||||
final progressColor = DynamicColor(Colors.black12, Colors.white10);
|
||||
|
||||
@@ -24,6 +24,8 @@ const height13 = SizedBox(height: 13);
|
||||
const width13 = SizedBox(width: 13);
|
||||
const width7 = SizedBox(width: 7);
|
||||
|
||||
/// Misc
|
||||
|
||||
const popMenuChild = Padding(
|
||||
padding: EdgeInsets.only(left: 7),
|
||||
child: Icon(
|
||||
|
||||
@@ -5,7 +5,6 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import '../../core/utils/ui.dart';
|
||||
import '../../data/res/color.dart';
|
||||
import '../widget/input_field.dart';
|
||||
import '../widget/round_rect_card.dart';
|
||||
|
||||
@@ -21,7 +20,6 @@ class _ConvertPageState extends State<ConvertPage>
|
||||
late TextEditingController _textEditingController;
|
||||
late TextEditingController _textEditingControllerResult;
|
||||
late MediaQueryData _media;
|
||||
late ThemeData _theme;
|
||||
late S _s;
|
||||
|
||||
int _typeOptionIndex = 0;
|
||||
@@ -37,7 +35,6 @@ class _ConvertPageState extends State<ConvertPage>
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_media = MediaQuery.of(context);
|
||||
_theme = Theme.of(context);
|
||||
_s = S.of(context)!;
|
||||
}
|
||||
|
||||
@@ -105,10 +102,14 @@ class _ConvertPageState extends State<ConvertPage>
|
||||
'URL $encode',
|
||||
'URL $decode'
|
||||
];
|
||||
final items = typeOption
|
||||
.map(
|
||||
(e) => PopupMenuItem(value: typeOption.indexOf(e), child: Text(e)),
|
||||
)
|
||||
.toList();
|
||||
return RoundRectCard(
|
||||
ExpansionTile(
|
||||
tilePadding: const EdgeInsets.only(left: 7, right: 27),
|
||||
childrenPadding: EdgeInsets.zero,
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.only(right: 17),
|
||||
title: Row(
|
||||
children: [
|
||||
TextButton(
|
||||
@@ -133,43 +134,31 @@ class _ConvertPageState extends State<ConvertPage>
|
||||
),
|
||||
trailing: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: _media.size.width * 0.35),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
child: buildPopuopMenu<int>(
|
||||
items: items,
|
||||
onSelected: (p0) {
|
||||
setState(() {
|
||||
_typeOptionIndex = p0;
|
||||
});
|
||||
},
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
typeOption[_typeOptionIndex],
|
||||
textScaleFactor: 1.0,
|
||||
textAlign: TextAlign.left,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: primaryColor,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
_s.currentMode,
|
||||
textScaleFactor: 1.0,
|
||||
textAlign: TextAlign.right,
|
||||
style: const TextStyle(fontSize: 9.0, color: Colors.grey),
|
||||
)
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.grey
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
children: typeOption
|
||||
.map(
|
||||
(e) => ListTile(
|
||||
title: Text(
|
||||
e,
|
||||
style: TextStyle(
|
||||
color: _theme.textTheme.bodyMedium?.color?.withAlpha(177),
|
||||
),
|
||||
),
|
||||
trailing: _buildRadio(typeOption.indexOf(e)),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -180,18 +169,6 @@ class _ConvertPageState extends State<ConvertPage>
|
||||
);
|
||||
}
|
||||
|
||||
Radio _buildRadio(int index) {
|
||||
return Radio<int>(
|
||||
value: index,
|
||||
groupValue: _typeOptionIndex,
|
||||
onChanged: (int? value) {
|
||||
setState(() {
|
||||
_typeOptionIndex = value ?? 0;
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
||||
|
||||
@@ -271,6 +271,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
||||
_buildPsItems(),
|
||||
_buildImages(),
|
||||
_buildEditHost(),
|
||||
const SizedBox(height: 37),
|
||||
].map((e) => RoundRectCard(e)).toList(),
|
||||
);
|
||||
}
|
||||
@@ -279,18 +280,14 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
||||
if (_docker.images == null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
return ExpansionTile(
|
||||
title: Text(_s.imagesList),
|
||||
subtitle: Text(
|
||||
_s.dockerImagesFmt(_docker.images!.length),
|
||||
style: grey,
|
||||
),
|
||||
children: _docker.images!
|
||||
final items = _docker.images!
|
||||
.map(
|
||||
(e) => ListTile(
|
||||
title: Text(e.repo),
|
||||
subtitle: Text('${e.tag} - ${e.size}'),
|
||||
subtitle: Text('${e.tag} - ${e.size}', style: grey),
|
||||
trailing: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
alignment: Alignment.centerRight,
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () async {
|
||||
showRoundDialog(
|
||||
@@ -325,13 +322,22 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
.toList();
|
||||
items.insert(
|
||||
0,
|
||||
ListTile(
|
||||
title: Text(_s.imagesList),
|
||||
subtitle: Text(
|
||||
_s.dockerImagesFmt(_docker.images!.length),
|
||||
style: grey,
|
||||
),
|
||||
),
|
||||
);
|
||||
return Column(children: items);
|
||||
}
|
||||
|
||||
Widget _buildLoading() {
|
||||
if (!_docker.isBusy) return const SizedBox();
|
||||
final haveLog = _docker.runLog != null;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(17),
|
||||
child: Column(
|
||||
@@ -339,8 +345,8 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
||||
const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
haveLog ? const SizedBox(height: 17) : const SizedBox(),
|
||||
haveLog ? Text(_docker.runLog!) : const SizedBox()
|
||||
const SizedBox(height: 17),
|
||||
Text(_docker.runLog ?? '...'),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -439,18 +445,25 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
||||
}
|
||||
|
||||
Widget _buildPsItems() {
|
||||
return ExpansionTile(
|
||||
title: Text(_s.containerStatus),
|
||||
subtitle: Text(_buildSubtitle(_docker.items!), style: grey),
|
||||
children: _docker.items!.map(
|
||||
final items = _docker.items!.map(
|
||||
(item) {
|
||||
return ListTile(
|
||||
title: Text(item.name),
|
||||
subtitle: Text('${item.image} - ${item.status}'),
|
||||
subtitle: Text('${item.image} - ${item.status}',
|
||||
style: grey.copyWith(fontSize: 11)),
|
||||
trailing: _buildMoreBtn(item, _docker.isBusy),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
).toList();
|
||||
items.insert(
|
||||
0,
|
||||
ListTile(
|
||||
title: Text(_s.containerStatus),
|
||||
subtitle: Text(_buildSubtitle(_docker.items!), style: grey),
|
||||
),
|
||||
);
|
||||
return Column(
|
||||
children: items,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -185,7 +185,7 @@ class _MyHomePageState extends State<MyHomePage>
|
||||
height: MediaQuery.of(context).size.height * 0.07,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 13),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 17),
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
@@ -215,24 +215,6 @@ class _MyHomePageState extends State<MyHomePage>
|
||||
onTap: () =>
|
||||
AppRoute(BackupPage(), 'backup page').go(context),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.info),
|
||||
title: Text(_s.feedback),
|
||||
onTap: () => showRoundDialog(
|
||||
context: context,
|
||||
child: Text(_s.feedbackOnGithub),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => openUrl(issueUrl),
|
||||
child: Text(_s.feedback),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(_s.close),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.snippet_folder),
|
||||
title: Text(_s.snippet),
|
||||
@@ -241,7 +223,7 @@ class _MyHomePageState extends State<MyHomePage>
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.text_snippet),
|
||||
title: Text(_s.about),
|
||||
title: Text('${_s.about} & ${_s.feedback}'),
|
||||
onTap: () {
|
||||
showRoundDialog(
|
||||
context: context,
|
||||
@@ -266,6 +248,10 @@ class _MyHomePageState extends State<MyHomePage>
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => openUrl(issueUrl),
|
||||
child: Text(_s.feedback),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => showLicensePage(context: context),
|
||||
child: Text(_s.license),
|
||||
|
||||
@@ -240,7 +240,6 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
||||
child: Text(_s.cancel),
|
||||
)
|
||||
],
|
||||
barrierDismiss: false,
|
||||
);
|
||||
if (cancel ?? true) {
|
||||
return;
|
||||
@@ -250,10 +249,10 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
||||
showSnackBar(context, Text(_s.plzSelectKey));
|
||||
return;
|
||||
}
|
||||
if (_usernameController.text == '') {
|
||||
if (_usernameController.text.isEmpty) {
|
||||
_usernameController.text = 'root';
|
||||
}
|
||||
if (_portController.text == '') {
|
||||
if (_portController.text.isEmpty) {
|
||||
_portController.text = '22';
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:flutter_material_color_picker/flutter_material_color_picker.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:toolbox/core/utils/navigator.dart';
|
||||
import 'package:toolbox/data/model/app/tab.dart';
|
||||
|
||||
import '../../core/utils/misc.dart';
|
||||
@@ -427,6 +428,7 @@ class _SettingPageState extends State<SettingPage> {
|
||||
TextButton(
|
||||
onPressed: () => setState(() {
|
||||
_setting.fontPath.delete();
|
||||
context.pop();
|
||||
_showRestartSnackbar();
|
||||
}),
|
||||
child: Text(_s.clear),
|
||||
@@ -452,6 +454,7 @@ class _SettingPageState extends State<SettingPage> {
|
||||
_setting.fontPath.put(newPath);
|
||||
}
|
||||
|
||||
context.pop();
|
||||
setState(() {});
|
||||
_showRestartSnackbar();
|
||||
return;
|
||||
|
||||
@@ -36,7 +36,6 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
final SftpBrowserStatus _status = SftpBrowserStatus();
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
late MediaQueryData _media;
|
||||
late S _s;
|
||||
|
||||
Server? _si;
|
||||
@@ -45,7 +44,6 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_media = MediaQuery.of(context);
|
||||
_s = S.of(context)!;
|
||||
}
|
||||
|
||||
@@ -121,7 +119,16 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
)),
|
||||
icon: const Icon(Icons.add),
|
||||
),
|
||||
IconButton(
|
||||
_buildGotoBtn(),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildGotoBtn() {
|
||||
return IconButton(
|
||||
padding: const EdgeInsets.all(0),
|
||||
onPressed: () async {
|
||||
final p = await showRoundDialog<String?>(
|
||||
@@ -157,38 +164,22 @@ class _SFTPPageState extends State<SFTPPage> {
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.gps_fixed),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Widget get centerCircleLoading => Center(
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: _media.size.height * 0.4,
|
||||
),
|
||||
const CircularProgressIndicator(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFileView() {
|
||||
if (_client == null || _si?.state != ServerState.connected) {
|
||||
return centerCircleLoading;
|
||||
return centerLoading;
|
||||
}
|
||||
|
||||
if (_status.isBusy) {
|
||||
return centerCircleLoading;
|
||||
return centerLoading;
|
||||
}
|
||||
|
||||
if (_status.files == null) {
|
||||
_status.path = AbsolutePath('/');
|
||||
listDir(path: '/', client: _client);
|
||||
return centerCircleLoading;
|
||||
return centerLoading;
|
||||
} else {
|
||||
return RefreshIndicator(
|
||||
child: FadeIn(
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:toolbox/view/widget/round_rect_card.dart';
|
||||
|
||||
Widget buildInput(BuildContext context, TextEditingController controller,
|
||||
{int maxLines = 20,
|
||||
Widget buildInput(
|
||||
BuildContext context,
|
||||
TextEditingController controller, {
|
||||
int maxLines = 20,
|
||||
String? hint,
|
||||
Function(String)? onSubmitted,
|
||||
bool? obscureText}) {
|
||||
bool obscureText = false,
|
||||
IconData? icon,
|
||||
}) {
|
||||
return RoundRectCard(
|
||||
TextField(
|
||||
maxLines: maxLines,
|
||||
onSubmitted: onSubmitted,
|
||||
decoration: InputDecoration(
|
||||
fillColor: Theme.of(context).cardColor,
|
||||
hintText: hint,
|
||||
filled: true,
|
||||
border: InputBorder.none),
|
||||
icon: icon != null ? Icon(icon) : null,
|
||||
border: InputBorder.none,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 13, vertical: 7)
|
||||
),
|
||||
controller: controller,
|
||||
obscureText: obscureText ?? false,
|
||||
obscureText: obscureText,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||