mirror of
https://mirror.ghproxy.com/https://github.com/StarCitizenToolBox/app.git
synced 2024-12-22 14:03:44 +08:00
feat: web 输入支持
This commit is contained in:
parent
1681e2407b
commit
472fdb08fb
53
assets/web/input_method/index.html
Normal file
53
assets/web/input_method/index.html
Normal file
@ -0,0 +1,53 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, shrink-to-fit=no"/>
|
||||
<meta name="renderer" content="webkit"/>
|
||||
|
||||
<link rel="stylesheet" href="style/mdui2.css">
|
||||
<link href="style/google_icons.css" rel="stylesheet">
|
||||
<script src="js/mdui2.global.js"></script>
|
||||
<script src="js/main.js"></script>
|
||||
<title>SCToolBox Community Input Method Web</title>
|
||||
<style>
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="mdui-theme-light" style="position: relative;overflow: hidden">
|
||||
<mdui-top-app-bar
|
||||
scroll-behavior="shrink"
|
||||
scroll-threshold="30"
|
||||
scroll-target=".scroll-behavior-shrink">
|
||||
|
||||
<mdui-top-app-bar-title>SC汉化盒子社区输入法</mdui-top-app-bar-title>
|
||||
<div style="flex-grow: 1"></div>
|
||||
|
||||
<mdui-button-icon icon="help" onclick="onShowHelp()"></mdui-button-icon>
|
||||
|
||||
|
||||
</mdui-top-app-bar>
|
||||
|
||||
<div class="scroll-behavior-shrink" style="overflow: auto;">
|
||||
|
||||
<mdui-text-field id="input_message" style="padding-top: 6pt;padding-bottom: 6pt" rows="6" variant="outlined"
|
||||
label="输入消息..."></mdui-text-field>
|
||||
|
||||
<div style="text-align: end">
|
||||
<mdui-checkbox id="auto_copy" checked>自动复制</mdui-checkbox>
|
||||
</div>
|
||||
|
||||
<mdui-button id="send_button" icon="send" onclick="onSendMessage()" full-width>发送</mdui-button>
|
||||
|
||||
</div>
|
||||
|
||||
<mdui-snackbar id="snackbar_message" auto-close-delay="1000">Text</mdui-snackbar>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
init();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
57
assets/web/input_method/js/main.js
Normal file
57
assets/web/input_method/js/main.js
Normal file
@ -0,0 +1,57 @@
|
||||
async function init() {
|
||||
try {
|
||||
let response = await fetch("/api");
|
||||
let responseJson = await response.json();
|
||||
if (responseJson.status === "ok") {
|
||||
showMessage("服务连接成功!");
|
||||
} else {
|
||||
showMessage("服务连接失败!" + responseJson);
|
||||
}
|
||||
} catch (e) {
|
||||
showMessage("服务连接失败!" + e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function onSendMessage() {
|
||||
let send_button = document.getElementById("send_button");
|
||||
let input = document.getElementById("input_message");
|
||||
let isAutoCopy = document.getElementById("auto_copy").checked;
|
||||
let isAutoSend = document.getElementById("auto_send").checked;
|
||||
let messageJson = {
|
||||
"text": input.value,
|
||||
"autoCopy": isAutoCopy,
|
||||
"autoInput": isAutoSend
|
||||
};
|
||||
send_button.loading = true;
|
||||
try {
|
||||
let response = await fetch("/api/send", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(messageJson)
|
||||
});
|
||||
let responseJson = await response.json();
|
||||
console.log(responseJson);
|
||||
showMessage(responseJson.message);
|
||||
if (response.ok) {
|
||||
input.value = "";
|
||||
}
|
||||
} catch (e) {
|
||||
showMessage("发送失败!" + e);
|
||||
}
|
||||
send_button.loading = false;
|
||||
}
|
||||
|
||||
function showMessage(message) {
|
||||
let snack = document.getElementById("snackbar_message");
|
||||
snack.open = false;
|
||||
snack.innerText = message;
|
||||
snack.open = true;
|
||||
}
|
||||
|
||||
function onShowHelp() {
|
||||
alert("在浏览器中输入文本,将发送给汉化盒子转码。" +
|
||||
"\n\n自动复制:勾选后自动复制转码结果到剪贴板。");
|
||||
}
|
22
assets/web/input_method/js/mdui2.global.js
Normal file
22
assets/web/input_method/js/mdui2.global.js
Normal file
File diff suppressed because one or more lines are too long
BIN
assets/web/input_method/style/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2
Normal file
BIN
assets/web/input_method/style/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2
Normal file
Binary file not shown.
23
assets/web/input_method/style/google_icons.css
Normal file
23
assets/web/input_method/style/google_icons.css
Normal file
@ -0,0 +1,23 @@
|
||||
/* fallback */
|
||||
@font-face {
|
||||
font-family: 'Material Icons';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2) format('woff2');
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-family: 'Material Icons';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
word-wrap: normal;
|
||||
direction: ltr;
|
||||
-webkit-font-feature-settings: 'liga';
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
1
assets/web/input_method/style/mdui2.css
Normal file
1
assets/web/input_method/style/mdui2.css
Normal file
File diff suppressed because one or more lines are too long
@ -6,4 +6,5 @@ class ConstConf {
|
||||
static const isMSE =
|
||||
String.fromEnvironment("MSE", defaultValue: "false") == "true";
|
||||
static const dohAddress = "https://223.6.6.6/resolve";
|
||||
static const inputMethodServerPort = 59399;
|
||||
}
|
||||
|
@ -4,8 +4,11 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/input_method/input_method_dialog_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/input_method/server.dart';
|
||||
import 'package:starcitizen_doctor/widgets/widgets.dart';
|
||||
|
||||
import 'server_qr_dialog_ui.dart';
|
||||
|
||||
class InputMethodDialogUI extends HookConsumerWidget {
|
||||
const InputMethodDialogUI({super.key});
|
||||
|
||||
@ -13,9 +16,16 @@ class InputMethodDialogUI extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final state = ref.watch(inputMethodDialogUIModelProvider);
|
||||
final model = ref.read(inputMethodDialogUIModelProvider.notifier);
|
||||
final serverState = ref.watch(inputMethodServerProvider);
|
||||
final serverModel = ref.read(inputMethodServerProvider.notifier);
|
||||
final srcTextCtrl = useTextEditingController();
|
||||
final destTextCtrl = useTextEditingController();
|
||||
|
||||
useEffect(() {
|
||||
model.setUpController(srcTextCtrl, destTextCtrl);
|
||||
return null;
|
||||
}, const []);
|
||||
|
||||
return ContentDialog(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * .8,
|
||||
@ -67,7 +77,36 @@ class InputMethodDialogUI extends HookConsumerWidget {
|
||||
// }
|
||||
},
|
||||
),
|
||||
SizedBox(height: 32),
|
||||
SizedBox(height: 24),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Text("远程输入服务:"),
|
||||
SizedBox(width: 6),
|
||||
if (serverState.isServerStartup)
|
||||
Button(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) =>
|
||||
ServerQrDialogUI(),
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
serverState.serverAddressText ?? "...",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 14),
|
||||
ToggleSwitch(
|
||||
checked: serverState.isServerStartup,
|
||||
onChanged: (b) =>
|
||||
_onSwitchServer(context, b, serverModel)),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
@ -130,4 +169,27 @@ class InputMethodDialogUI extends HookConsumerWidget {
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onSwitchServer(
|
||||
BuildContext context, bool value, InputMethodServer serverModel) async {
|
||||
if (value) {
|
||||
final userOK = await showConfirmDialogs(
|
||||
context,
|
||||
"确认启用远程输入?",
|
||||
Text(
|
||||
"开启此功能后,可通过手机访问远程服务地址,快捷输入文字,省去切换窗口的麻烦,游戏流程不中断。\n\n若弹出防火墙提示,请展开弹窗,手动勾选所有网络类型并允许,否则可能无法正常访问此功能。"),
|
||||
);
|
||||
if (userOK) {
|
||||
// ignore: use_build_context_synchronously
|
||||
await serverModel.startServer().unwrap(context: context);
|
||||
if (!context.mounted) return;
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) => ServerQrDialogUI(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await serverModel.stopServer().unwrap(context: context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ class InputMethodDialogUIModel extends _$InputMethodDialogUIModel {
|
||||
state = state.copyWith(enableAutoCopy: value);
|
||||
}
|
||||
|
||||
String? onTextChange(String type, String str) {
|
||||
String? onTextChange(String type, String str, {formWeb = false}) {
|
||||
if (state.keyMaps == null || state.worldMaps == null) return null;
|
||||
StringBuffer sb = StringBuffer();
|
||||
final r = RegExp(r'^[a-zA-Z0-9\p{P}\p{S}]+$');
|
||||
@ -93,7 +93,9 @@ class InputMethodDialogUIModel extends _$InputMethodDialogUIModel {
|
||||
return "";
|
||||
}
|
||||
final text = "[zh] ${sb.toString()}";
|
||||
_handleAutoCopy(text);
|
||||
if (!formWeb) {
|
||||
_handleAutoCopy(text);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
@ -114,4 +116,26 @@ class InputMethodDialogUIModel extends _$InputMethodDialogUIModel {
|
||||
});
|
||||
}
|
||||
|
||||
TextEditingController? _srcTextCtrl;
|
||||
TextEditingController? _destTextCtrl;
|
||||
|
||||
void setUpController(
|
||||
TextEditingController srcTextCtrl, TextEditingController destTextCtrl) {
|
||||
_srcTextCtrl = srcTextCtrl;
|
||||
_destTextCtrl = destTextCtrl;
|
||||
}
|
||||
|
||||
Future<void> onSendText(
|
||||
String text, {
|
||||
bool autoCopy = false,
|
||||
bool autoInput = false,
|
||||
}) async {
|
||||
debugPrint("[InputMethodDialogUIState] onSendText: $text");
|
||||
_srcTextCtrl?.text = text;
|
||||
_destTextCtrl?.text = onTextChange("src", text) ?? "";
|
||||
if (_destTextCtrl?.text.isEmpty ?? true) return;
|
||||
if (autoCopy) {
|
||||
Clipboard.setData(ClipboardData(text: _destTextCtrl?.text ?? ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ part of 'input_method_dialog_ui_model.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$inputMethodDialogUIModelHash() =>
|
||||
r'48955b06db0b5fdc8ae5e59b93fdd9a95b391487';
|
||||
r'93440d8f9c5372d5350ceaa8cb00a1b0d3b0046e';
|
||||
|
||||
/// See also [InputMethodDialogUIModel].
|
||||
@ProviderFor(InputMethodDialogUIModel)
|
||||
|
212
lib/ui/home/input_method/server.dart
Normal file
212
lib/ui/home/input_method/server.dart
Normal file
@ -0,0 +1,212 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:shelf/shelf.dart';
|
||||
import 'package:shelf/shelf_io.dart' as shelf_io;
|
||||
import 'package:starcitizen_doctor/common/conf/const_conf.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/localization/localization_ui_model.dart';
|
||||
|
||||
import 'input_method_dialog_ui_model.dart';
|
||||
|
||||
part 'server.g.dart';
|
||||
|
||||
part 'server.freezed.dart';
|
||||
|
||||
@freezed
|
||||
class InputMethodServerState with _$InputMethodServerState {
|
||||
const factory InputMethodServerState({
|
||||
@Default(false) bool isServerStartup,
|
||||
String? serverAddressText,
|
||||
}) = _InputMethodServerState;
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class InputMethodServer extends _$InputMethodServer {
|
||||
@override
|
||||
InputMethodServerState build() {
|
||||
state = InputMethodServerState(isServerStartup: false);
|
||||
ref.onDispose(() {
|
||||
stopServer();
|
||||
});
|
||||
return state;
|
||||
}
|
||||
|
||||
LocalizationUIState get _localizationUIState =>
|
||||
ref.read(localizationUIModelProvider);
|
||||
|
||||
InputMethodDialogUIModel get _inputMethodDialogUIModel =>
|
||||
ref.read(inputMethodDialogUIModelProvider.notifier);
|
||||
|
||||
HttpServer? _httpServer;
|
||||
|
||||
Future<void> stopServer() async {
|
||||
if (_httpServer != null) {
|
||||
await _httpServer!.close(force: true);
|
||||
_httpServer = null;
|
||||
state = state.copyWith(
|
||||
isServerStartup: false,
|
||||
);
|
||||
dPrint("[InputMethodServer] stopServer");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> startServer() async {
|
||||
dPrint("[InputMethodServer] startServer");
|
||||
|
||||
var handler =
|
||||
const Pipeline().addMiddleware(logRequests()).addHandler(_onHandler);
|
||||
|
||||
var server = await shelf_io.serve(
|
||||
handler, "0.0.0.0", ConstConf.inputMethodServerPort);
|
||||
|
||||
// Enable content compression
|
||||
server.autoCompress = true;
|
||||
|
||||
dPrint('Serving at http://${server.address.host}:${server.port}');
|
||||
|
||||
server.autoCompress = true;
|
||||
_httpServer = server;
|
||||
final address = await _findAddress();
|
||||
state = state.copyWith(
|
||||
isServerStartup: true,
|
||||
serverAddressText: address,
|
||||
);
|
||||
}
|
||||
|
||||
Future<String> _findAddress() async {
|
||||
final list = <String>[];
|
||||
final List<NetworkInterface> address = await NetworkInterface.list();
|
||||
bool has192168 = false;
|
||||
for (var value in address) {
|
||||
for (var addr in value.addresses) {
|
||||
if (addr.type == InternetAddressType.IPv4) {
|
||||
list.add("http://${addr.address}:${ConstConf.inputMethodServerPort}");
|
||||
if (addr.address.startsWith('192.168.')) {
|
||||
has192168 = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (has192168) {
|
||||
list.removeWhere((element) => !element.contains('192.168.'));
|
||||
}
|
||||
if (list.isEmpty) {
|
||||
list.add("获取地址失败,请手动查看电脑IP");
|
||||
}
|
||||
return list.join(", ");
|
||||
}
|
||||
|
||||
Future<Response> _onHandler(Request request) async {
|
||||
final path = request.url.path;
|
||||
dPrint("[InputMethodServer] path: $path");
|
||||
Uint8List? contentByte;
|
||||
String mimeType;
|
||||
try {
|
||||
if (path.startsWith('api')) {
|
||||
return _onHandlerApi(request);
|
||||
}
|
||||
if (path == '/' || path == '') {
|
||||
contentByte =
|
||||
(await rootBundle.load('assets/web/input_method/index.html'))
|
||||
.buffer
|
||||
.asUint8List();
|
||||
mimeType = 'text/html; charset=utf-8';
|
||||
} else {
|
||||
var dotOffset = path.lastIndexOf('.');
|
||||
if (path.substring(dotOffset) == '.png' ||
|
||||
path.substring(dotOffset) == '.ttf' ||
|
||||
path.substring(dotOffset) == '.otf') {
|
||||
contentByte = (await rootBundle.load('assets/web/input_method/$path'))
|
||||
.buffer
|
||||
.asUint8List();
|
||||
} else {
|
||||
contentByte = (await rootBundle.load('assets/web/input_method/$path'))
|
||||
.buffer
|
||||
.asUint8List();
|
||||
}
|
||||
|
||||
mimeType = dotOffset == -1
|
||||
? 'text/plain; charset=utf-8'
|
||||
: {
|
||||
'.html': 'text/html; charset=utf-8',
|
||||
'.css': 'text/css; charset=utf-8',
|
||||
'.js': 'text/javascript; charset=utf-8',
|
||||
'.csv': 'text/csv; charset=utf-8',
|
||||
'.txt': 'text/plain; charset=utf-8',
|
||||
'.ico': 'image/x-icon',
|
||||
'.jpg': 'image/jpg',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.png': 'image/png',
|
||||
'.gif': 'image/gif',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.json': 'application/json',
|
||||
'.xml': 'application/xml',
|
||||
'.ttf': 'font/ttf',
|
||||
'.otf': 'font/otf'
|
||||
}[path.substring(dotOffset)] ??
|
||||
"application/octet-stream";
|
||||
}
|
||||
return Response.ok(
|
||||
contentByte,
|
||||
headers: {
|
||||
'Content-Type': mimeType,
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint(e.toString());
|
||||
return Response.internalServerError();
|
||||
}
|
||||
}
|
||||
|
||||
Future<Response> _onHandlerApi(Request request) async {
|
||||
final path = request.url.path;
|
||||
if (path == "api") {
|
||||
return Response.ok(json.encode({
|
||||
"status": "ok",
|
||||
"appVersion": ConstConf.appVersion,
|
||||
"appVersionCode": ConstConf.appVersionCode,
|
||||
"appVersionDate": ConstConf.appVersionDate,
|
||||
"isMSE": ConstConf.isMSE,
|
||||
"installedCommunityInputMethodSupportVersion":
|
||||
_localizationUIState.installedCommunityInputMethodSupportVersion,
|
||||
}));
|
||||
} else if (path.startsWith("api/send") && request.method == "POST") {
|
||||
final body = await request.readAsString();
|
||||
final data = json.decode(body);
|
||||
final text = data["text"] ?? "";
|
||||
if (text.isEmpty) {
|
||||
return Response.badRequest(
|
||||
body: json.encode({
|
||||
"result": "error",
|
||||
"message": "文本不能为空!",
|
||||
}));
|
||||
}
|
||||
final autoCopy = data["autoCopy"] ?? false;
|
||||
final autoInput = data["autoInput"] ?? false;
|
||||
try {
|
||||
await _inputMethodDialogUIModel.onSendText(
|
||||
text,
|
||||
autoCopy: autoCopy,
|
||||
autoInput: autoInput,
|
||||
);
|
||||
return Response.ok(json.encode({
|
||||
"result": "ok",
|
||||
"message": "发送成功!",
|
||||
}));
|
||||
} catch (e) {
|
||||
return Response.internalServerError(
|
||||
body: json.encode({
|
||||
"result": "error",
|
||||
"message": e.toString(),
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
return Response.notFound("Not Found");
|
||||
}
|
||||
}
|
||||
}
|
171
lib/ui/home/input_method/server.freezed.dart
Normal file
171
lib/ui/home/input_method/server.freezed.dart
Normal file
@ -0,0 +1,171 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'server.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$InputMethodServerState {
|
||||
bool get isServerStartup => throw _privateConstructorUsedError;
|
||||
String? get serverAddressText => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of InputMethodServerState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$InputMethodServerStateCopyWith<InputMethodServerState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $InputMethodServerStateCopyWith<$Res> {
|
||||
factory $InputMethodServerStateCopyWith(InputMethodServerState value,
|
||||
$Res Function(InputMethodServerState) then) =
|
||||
_$InputMethodServerStateCopyWithImpl<$Res, InputMethodServerState>;
|
||||
@useResult
|
||||
$Res call({bool isServerStartup, String? serverAddressText});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$InputMethodServerStateCopyWithImpl<$Res,
|
||||
$Val extends InputMethodServerState>
|
||||
implements $InputMethodServerStateCopyWith<$Res> {
|
||||
_$InputMethodServerStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of InputMethodServerState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? isServerStartup = null,
|
||||
Object? serverAddressText = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
isServerStartup: null == isServerStartup
|
||||
? _value.isServerStartup
|
||||
: isServerStartup // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
serverAddressText: freezed == serverAddressText
|
||||
? _value.serverAddressText
|
||||
: serverAddressText // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$InputMethodServerStateImplCopyWith<$Res>
|
||||
implements $InputMethodServerStateCopyWith<$Res> {
|
||||
factory _$$InputMethodServerStateImplCopyWith(
|
||||
_$InputMethodServerStateImpl value,
|
||||
$Res Function(_$InputMethodServerStateImpl) then) =
|
||||
__$$InputMethodServerStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({bool isServerStartup, String? serverAddressText});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$InputMethodServerStateImplCopyWithImpl<$Res>
|
||||
extends _$InputMethodServerStateCopyWithImpl<$Res,
|
||||
_$InputMethodServerStateImpl>
|
||||
implements _$$InputMethodServerStateImplCopyWith<$Res> {
|
||||
__$$InputMethodServerStateImplCopyWithImpl(
|
||||
_$InputMethodServerStateImpl _value,
|
||||
$Res Function(_$InputMethodServerStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of InputMethodServerState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? isServerStartup = null,
|
||||
Object? serverAddressText = freezed,
|
||||
}) {
|
||||
return _then(_$InputMethodServerStateImpl(
|
||||
isServerStartup: null == isServerStartup
|
||||
? _value.isServerStartup
|
||||
: isServerStartup // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
serverAddressText: freezed == serverAddressText
|
||||
? _value.serverAddressText
|
||||
: serverAddressText // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$InputMethodServerStateImpl implements _InputMethodServerState {
|
||||
const _$InputMethodServerStateImpl(
|
||||
{this.isServerStartup = false, this.serverAddressText});
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isServerStartup;
|
||||
@override
|
||||
final String? serverAddressText;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'InputMethodServerState(isServerStartup: $isServerStartup, serverAddressText: $serverAddressText)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$InputMethodServerStateImpl &&
|
||||
(identical(other.isServerStartup, isServerStartup) ||
|
||||
other.isServerStartup == isServerStartup) &&
|
||||
(identical(other.serverAddressText, serverAddressText) ||
|
||||
other.serverAddressText == serverAddressText));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, isServerStartup, serverAddressText);
|
||||
|
||||
/// Create a copy of InputMethodServerState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$InputMethodServerStateImplCopyWith<_$InputMethodServerStateImpl>
|
||||
get copyWith => __$$InputMethodServerStateImplCopyWithImpl<
|
||||
_$InputMethodServerStateImpl>(this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _InputMethodServerState implements InputMethodServerState {
|
||||
const factory _InputMethodServerState(
|
||||
{final bool isServerStartup,
|
||||
final String? serverAddressText}) = _$InputMethodServerStateImpl;
|
||||
|
||||
@override
|
||||
bool get isServerStartup;
|
||||
@override
|
||||
String? get serverAddressText;
|
||||
|
||||
/// Create a copy of InputMethodServerState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$InputMethodServerStateImplCopyWith<_$InputMethodServerStateImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
26
lib/ui/home/input_method/server.g.dart
Normal file
26
lib/ui/home/input_method/server.g.dart
Normal file
@ -0,0 +1,26 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'server.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$inputMethodServerHash() => r'4ea07de4bca3268933b78335b670c09e6fac61bc';
|
||||
|
||||
/// See also [InputMethodServer].
|
||||
@ProviderFor(InputMethodServer)
|
||||
final inputMethodServerProvider = AutoDisposeNotifierProvider<InputMethodServer,
|
||||
InputMethodServerState>.internal(
|
||||
InputMethodServer.new,
|
||||
name: r'inputMethodServerProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$inputMethodServerHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$InputMethodServer = AutoDisposeNotifier<InputMethodServerState>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
95
lib/ui/home/input_method/server_qr_dialog_ui.dart
Normal file
95
lib/ui/home/input_method/server_qr_dialog_ui.dart
Normal file
@ -0,0 +1,95 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
|
||||
import 'server.dart';
|
||||
|
||||
class ServerQrDialogUI extends HookConsumerWidget {
|
||||
const ServerQrDialogUI({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final serverState = ref.watch(inputMethodServerProvider);
|
||||
|
||||
final urls = serverState.serverAddressText?.split(",") ?? [""];
|
||||
|
||||
final hasMultipleUrls = urls.length > 1;
|
||||
|
||||
final index = useState(0);
|
||||
|
||||
return ContentDialog(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * .4,
|
||||
),
|
||||
title: makeTitle(context),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(width: double.infinity, height: 12),
|
||||
if (hasMultipleUrls) ...[
|
||||
Text("我们没能找到合适的 ip 地址来访问服务,请您尝试以下地址(左右切换)"),
|
||||
] else
|
||||
Text(
|
||||
"请使用您的移动设备扫描以下二维码,或手动访问连接",
|
||||
style: TextStyle(color: Colors.white.withOpacity(.8)),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if (hasMultipleUrls)
|
||||
IconButton(
|
||||
icon: Icon(FluentIcons.chevron_left),
|
||||
onPressed: () {
|
||||
index.value = (index.value - 1) % urls.length;
|
||||
}),
|
||||
SizedBox(width: 24),
|
||||
Container(
|
||||
color: Colors.white,
|
||||
child: QrImageView(
|
||||
data: urls[index.value],
|
||||
size: 200,
|
||||
padding: EdgeInsets.all(12),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 24),
|
||||
if (hasMultipleUrls)
|
||||
IconButton(
|
||||
icon: Icon(FluentIcons.chevron_right),
|
||||
onPressed: () {
|
||||
index.value = (index.value + 1) % urls.length;
|
||||
}),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
Text(
|
||||
hasMultipleUrls
|
||||
? "(${index.value + 1} / ${urls.length})"
|
||||
: urls[index.value],
|
||||
style: TextStyle(fontSize: 13, color: Colors.white.withOpacity(.6)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeTitle(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
FluentIcons.back,
|
||||
size: 22,
|
||||
),
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
}),
|
||||
const SizedBox(width: 12),
|
||||
Text("服务二维码"),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -3,7 +3,6 @@ import 'dart:io';
|
||||
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/src/widgets/framework.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@ -12,7 +11,6 @@ import 'package:re_highlight/languages/ini.dart';
|
||||
import 'package:re_highlight/styles/vs2015.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:starcitizen_doctor/api/analytics.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/provider.dart';
|
||||
import 'package:starcitizen_doctor/data/app_advanced_localization_data.dart';
|
||||
|
@ -212,7 +212,6 @@ class __$$AdvancedLocalizationUIStateImplCopyWithImpl<$Res>
|
||||
/// @nodoc
|
||||
|
||||
class _$AdvancedLocalizationUIStateImpl
|
||||
with DiagnosticableTreeMixin
|
||||
implements _AdvancedLocalizationUIState {
|
||||
_$AdvancedLocalizationUIStateImpl(
|
||||
{this.workingText = "",
|
||||
@ -258,26 +257,10 @@ class _$AdvancedLocalizationUIStateImpl
|
||||
final String errorMessage;
|
||||
|
||||
@override
|
||||
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
|
||||
String toString() {
|
||||
return 'AdvancedLocalizationUIState(workingText: $workingText, classMap: $classMap, p4kGlobalIni: $p4kGlobalIni, serverGlobalIni: $serverGlobalIni, customizeGlobalIni: $customizeGlobalIni, apiLocalizationData: $apiLocalizationData, p4kGlobalIniLines: $p4kGlobalIniLines, serverGlobalIniLines: $serverGlobalIniLines, errorMessage: $errorMessage)';
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(DiagnosticsProperty('type', 'AdvancedLocalizationUIState'))
|
||||
..add(DiagnosticsProperty('workingText', workingText))
|
||||
..add(DiagnosticsProperty('classMap', classMap))
|
||||
..add(DiagnosticsProperty('p4kGlobalIni', p4kGlobalIni))
|
||||
..add(DiagnosticsProperty('serverGlobalIni', serverGlobalIni))
|
||||
..add(DiagnosticsProperty('customizeGlobalIni', customizeGlobalIni))
|
||||
..add(DiagnosticsProperty('apiLocalizationData', apiLocalizationData))
|
||||
..add(DiagnosticsProperty('p4kGlobalIniLines', p4kGlobalIniLines))
|
||||
..add(DiagnosticsProperty('serverGlobalIniLines', serverGlobalIniLines))
|
||||
..add(DiagnosticsProperty('errorMessage', errorMessage));
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
|
@ -7,7 +7,7 @@ part of 'advanced_localization_ui_model.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$advancedLocalizationUIModelHash() =>
|
||||
r'8241143c6dec93cd705e6b2e65cbca711cdfe2fb';
|
||||
r'6b65988e71733c01e9352765cf600c4781bdccb4';
|
||||
|
||||
/// See also [AdvancedLocalizationUIModel].
|
||||
@ProviderFor(AdvancedLocalizationUIModel)
|
||||
|
@ -4,7 +4,6 @@ import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:flutter_tilt/flutter_tilt.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||
import 'package:starcitizen_doctor/data/sc_localization_data.dart';
|
||||
import 'package:starcitizen_doctor/ui/tools/tools_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/widgets/widgets.dart';
|
||||
|
@ -7,7 +7,7 @@ part of 'localization_ui_model.dart';
|
||||
// **************************************************************************
|
||||
|
||||
String _$localizationUIModelHash() =>
|
||||
r'b8c893413fa8a314d0fa3b2cfffb63f723226bae';
|
||||
r'206512f457acdb0aaa2cd638fcdb31b6a88848a6';
|
||||
|
||||
/// See also [LocalizationUIModel].
|
||||
@ProviderFor(LocalizationUIModel)
|
||||
|
@ -63,6 +63,8 @@ dependencies:
|
||||
file: ^7.0.0
|
||||
re_editor: ^0.6.0
|
||||
re_highlight: ^0.0.3
|
||||
shelf: ^1.4.1
|
||||
qr_flutter: ^4.1.0
|
||||
dependency_overrides:
|
||||
http: ^1.1.2
|
||||
|
||||
@ -89,6 +91,9 @@ flutter:
|
||||
- assets/
|
||||
- assets/binary/
|
||||
- assets/countdown/
|
||||
- assets/web/input_method/
|
||||
- assets/web/input_method/js/
|
||||
- assets/web/input_method/style/
|
||||
|
||||
fonts:
|
||||
- family: SourceHanSansCN-Regular
|
||||
|
Loading…
Reference in New Issue
Block a user