claude nmsl
This commit is contained in:
1 parent
cd6491cdc5
commit
2ad6781049
10 files changed
+111
-27
No files matched your search
@@ -0,0 +1,66 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Breakpoints: phone < 600, tablet 600–900, desktop > 900
|
||||||
|
const double _breakpointTablet = 600;
|
||||||
|
const double _breakpointDesktop = 900;
|
||||||
|
|
||||||
|
/// Returns a responsive max width for content containers.
|
||||||
|
/// - Phone: 500
|
||||||
|
/// - Tablet: 700
|
||||||
|
/// - Desktop: 900
|
||||||
|
double responsiveMaxWidth(BuildContext context) {
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
if (width >= _breakpointDesktop) return 900;
|
||||||
|
if (width >= _breakpointTablet) return 700;
|
||||||
|
return 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the screen is tablet-sized or wider.
|
||||||
|
bool isTablet(BuildContext context) =>
|
||||||
|
MediaQuery.of(context).size.width >= _breakpointTablet;
|
||||||
|
|
||||||
|
/// Whether the screen is desktop-sized or wider.
|
||||||
|
bool isDesktop(BuildContext context) =>
|
||||||
|
MediaQuery.of(context).size.width >= _breakpointDesktop;
|
||||||
|
|
||||||
|
/// Build columns for tablet/desktop — on phone, each item is full-width;
|
||||||
|
/// on tablet+ they are laid out in [columns] columns.
|
||||||
|
List<Widget> responsiveGrid({
|
||||||
|
required BuildContext context,
|
||||||
|
required List<Widget> children,
|
||||||
|
int columns = 2,
|
||||||
|
double spacing = 12,
|
||||||
|
}) {
|
||||||
|
final tablet = isTablet(context);
|
||||||
|
if (!tablet) return children;
|
||||||
|
|
||||||
|
final rows = <Widget>[];
|
||||||
|
final padded = <Widget>[];
|
||||||
|
for (int i = 0; i < children.length; i++) {
|
||||||
|
padded.add(children[i]);
|
||||||
|
if ((i + 1) % columns == 0 || i == children.length - 1) {
|
||||||
|
final cols = <Widget>[];
|
||||||
|
for (int j = 0; j < padded.length; j++) {
|
||||||
|
cols.add(Expanded(child: padded[j]));
|
||||||
|
}
|
||||||
|
// pad incomplete last row with empty expanded widgets
|
||||||
|
while (cols.length < columns) {
|
||||||
|
cols.add(const Expanded(child: SizedBox.shrink()));
|
||||||
|
}
|
||||||
|
rows.add(Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: rows.isEmpty ? 0 : spacing),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
for (int j = 0; j < cols.length; j++) ...[
|
||||||
|
if (j > 0) SizedBox(width: spacing),
|
||||||
|
cols[j],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
padded.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ class AppStrings {
|
|||||||
static const appTitle = 'Project Ichino';
|
static const appTitle = 'Project Ichino';
|
||||||
static const loginSubtitle = '扫描或输入二维码进行登录';
|
static const loginSubtitle = '扫描或输入二维码进行登录';
|
||||||
static const qrCodeToken = 'QR Code 令牌';
|
static const qrCodeToken = 'QR Code 令牌';
|
||||||
|
static const qrHint = '粘贴二维码解析内容...';
|
||||||
static const qrParsedResult = '解析结果(截取后 64 位)';
|
static const qrParsedResult = '解析结果(截取后 64 位)';
|
||||||
static const uploadQR = '上传二维码';
|
static const uploadQR = '上传二维码';
|
||||||
static const login = '登录';
|
static const login = '登录';
|
||||||
@@ -123,18 +124,31 @@ class AppStrings {
|
|||||||
static const machineSettings = '机器信息';
|
static const machineSettings = '机器信息';
|
||||||
static const save = '保存';
|
static const save = '保存';
|
||||||
static const labelTitleServerUrl = 'Title Server URL';
|
static const labelTitleServerUrl = 'Title Server URL';
|
||||||
|
static const hintTitleServerUrl = 'https://maimai-gm.wahlap.com:42081/Maimai2Servlet/';
|
||||||
static const labelAesKey = 'AES Key';
|
static const labelAesKey = 'AES Key';
|
||||||
|
static const hintAesKey = 'your aes key string';
|
||||||
static const labelAesIv = 'AES IV';
|
static const labelAesIv = 'AES IV';
|
||||||
|
static const hintAesIv = 'your aes iv string';
|
||||||
static const labelClientId = 'Client ID';
|
static const labelClientId = 'Client ID';
|
||||||
|
static const hintClientId = 'A63E01C2805';
|
||||||
static const labelRegionId = 'Region ID';
|
static const labelRegionId = 'Region ID';
|
||||||
|
static const hintRegionId = '1';
|
||||||
static const labelPlaceId = 'Place ID';
|
static const labelPlaceId = 'Place ID';
|
||||||
|
static const hintPlaceId = '1403';
|
||||||
static const labelObfuscateParam = 'Obfuscate Param';
|
static const labelObfuscateParam = 'Obfuscate Param';
|
||||||
|
static const hintObfuscateParam = 'LatuAa81';
|
||||||
static const labelApiVersion = 'API Version (Mai-Encoding)';
|
static const labelApiVersion = 'API Version (Mai-Encoding)';
|
||||||
|
static const hintApiVersion = '1.53';
|
||||||
static const labelRegionName = 'Region 名称';
|
static const labelRegionName = 'Region 名称';
|
||||||
|
static const hintRegionName = '北京';
|
||||||
static const labelPlaceName = 'Place 名称';
|
static const labelPlaceName = 'Place 名称';
|
||||||
|
static const hintPlaceName = 'Place name';
|
||||||
static const labelKeychipId = 'Keychip ID';
|
static const labelKeychipId = 'Keychip ID';
|
||||||
|
static const hintKeychipId = 'ID';
|
||||||
static const labelAimeUrl = 'Aime URL';
|
static const labelAimeUrl = 'Aime URL';
|
||||||
|
static const hintAimeUrl = 'http://ai.sys-allnet.cn/wc_aime/api/get_data';
|
||||||
static const labelAimeSalt = 'Aime Salt';
|
static const labelAimeSalt = 'Aime Salt';
|
||||||
|
static const hintAimeSalt = 'API Key 派生用 Salt';
|
||||||
static String fieldRequired(String label) => '$label 不能为空';
|
static String fieldRequired(String label) => '$label 不能为空';
|
||||||
|
|
||||||
// Config Export/Import
|
// Config Export/Import
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class TitleServerConfig {
|
|||||||
this.aimeUrl = '',
|
this.aimeUrl = '',
|
||||||
this.aimeSalt = '',
|
this.aimeSalt = '',
|
||||||
String? openGameID,
|
String? openGameID,
|
||||||
}) : openGameID = openGameID ?? '';
|
}) : openGameID = openGameID ?? 'MAID';
|
||||||
|
|
||||||
List<int> get aesKeyBytes => utf8.encode(aesKey);
|
List<int> get aesKeyBytes => utf8.encode(aesKey);
|
||||||
List<int> get aesIvBytes => utf8.encode(aesIv);
|
List<int> get aesIvBytes => utf8.encode(aesIv);
|
||||||
@@ -70,7 +70,7 @@ class TitleServerConfig {
|
|||||||
keychipId: json['keychipId'] as String? ?? '',
|
keychipId: json['keychipId'] as String? ?? '',
|
||||||
aimeUrl: json['aimeUrl'] as String? ?? '',
|
aimeUrl: json['aimeUrl'] as String? ?? '',
|
||||||
aimeSalt: json['aimeSalt'] as String? ?? '',
|
aimeSalt: json['aimeSalt'] as String? ?? '',
|
||||||
openGameID: json['openGameID'] as String? ?? '',
|
openGameID: json['openGameID'] as String? ?? 'MAID',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'config/responsive.dart';
|
||||||
import 'config/strings.dart';
|
import 'config/strings.dart';
|
||||||
import 'config/title_server_config.dart';
|
import 'config/title_server_config.dart';
|
||||||
import 'services/api_service.dart';
|
import 'services/api_service.dart';
|
||||||
@@ -161,7 +162,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 48),
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 48),
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 440),
|
constraints: BoxConstraints(maxWidth: responsiveMaxWidth(context)),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../config/responsive.dart';
|
||||||
import '../config/strings.dart';
|
import '../config/strings.dart';
|
||||||
|
|
||||||
class AboutPage extends StatelessWidget {
|
class AboutPage extends StatelessWidget {
|
||||||
@@ -12,7 +13,7 @@ class AboutPage extends StatelessWidget {
|
|||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(24),
|
padding: const EdgeInsets.all(24),
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 500),
|
constraints: BoxConstraints(maxWidth: responsiveMaxWidth(context)),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'dart:convert';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
import '../config/responsive.dart';
|
||||||
import '../config/strings.dart';
|
import '../config/strings.dart';
|
||||||
import '../config/title_server_config.dart';
|
import '../config/title_server_config.dart';
|
||||||
|
|
||||||
@@ -73,7 +74,7 @@ class ConfigExportImportPage extends StatelessWidget {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(24),
|
padding: const EdgeInsets.all(24),
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 400),
|
constraints: BoxConstraints(maxWidth: responsiveMaxWidth(context)),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../config/responsive.dart';
|
||||||
import '../config/strings.dart';
|
import '../config/strings.dart';
|
||||||
import '../config/title_server_config.dart';
|
import '../config/title_server_config.dart';
|
||||||
import '../models/user_data.dart';
|
import '../models/user_data.dart';
|
||||||
@@ -343,7 +344,7 @@ class _HomePageState extends State<HomePage> {
|
|||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 500),
|
constraints: BoxConstraints(maxWidth: responsiveMaxWidth(context)),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
if (preview.errorId != 0) _ErrorIdBanner(theme: theme, errorId: preview.errorId),
|
if (preview.errorId != 0) _ErrorIdBanner(theme: theme, errorId: preview.errorId),
|
||||||
@@ -387,9 +388,13 @@ class _HomePageState extends State<HomePage> {
|
|||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_ProfileFromPreviewCard(theme: theme, preview: preview),
|
_ProfileFromPreviewCard(theme: theme, preview: preview),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
...responsiveGrid(
|
||||||
|
context: context,
|
||||||
|
children: [
|
||||||
_PreviewGameInfoCard(theme: theme, preview: preview),
|
_PreviewGameInfoCard(theme: theme, preview: preview),
|
||||||
const SizedBox(height: 12),
|
|
||||||
_PreviewStatusCard(theme: theme, preview: preview),
|
_PreviewStatusCard(theme: theme, preview: preview),
|
||||||
|
],
|
||||||
|
),
|
||||||
if (TitleApiService.lastRawResponse != null) ...[
|
if (TitleApiService.lastRawResponse != null) ...[
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_DebugRawJsonCard(
|
_DebugRawJsonCard(
|
||||||
@@ -411,22 +416,22 @@ class _HomePageState extends State<HomePage> {
|
|||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 500),
|
constraints: BoxConstraints(maxWidth: responsiveMaxWidth(context)),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_ProfileFromUserDataCard(theme: theme, data: data),
|
_ProfileFromUserDataCard(theme: theme, data: data),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
...responsiveGrid(
|
||||||
|
context: context,
|
||||||
|
children: [
|
||||||
_RatingBreakdownCard(theme: theme, data: data),
|
_RatingBreakdownCard(theme: theme, data: data),
|
||||||
const SizedBox(height: 12),
|
|
||||||
_FirstPlayCard(theme: theme, data: data),
|
_FirstPlayCard(theme: theme, data: data),
|
||||||
const SizedBox(height: 12),
|
|
||||||
_GameInfoFromUserDataCard(theme: theme, data: data),
|
_GameInfoFromUserDataCard(theme: theme, data: data),
|
||||||
const SizedBox(height: 12),
|
|
||||||
_PlayStatsCard(theme: theme, data: data),
|
_PlayStatsCard(theme: theme, data: data),
|
||||||
const SizedBox(height: 12),
|
|
||||||
_StatusFromUserDataCard(theme: theme, preview: preview, data: data),
|
_StatusFromUserDataCard(theme: theme, preview: preview, data: data),
|
||||||
const SizedBox(height: 12),
|
|
||||||
_UserDataDetailCard(theme: theme, data: data),
|
_UserDataDetailCard(theme: theme, data: data),
|
||||||
|
],
|
||||||
|
),
|
||||||
if (TitleApiService.lastRawResponse != null) ...[
|
if (TitleApiService.lastRawResponse != null) ...[
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_DebugRawJsonCard(
|
_DebugRawJsonCard(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../config/responsive.dart';
|
||||||
import '../config/strings.dart';
|
import '../config/strings.dart';
|
||||||
import '../config/title_server_config.dart';
|
import '../config/title_server_config.dart';
|
||||||
import 'config_export_import_page.dart';
|
import 'config_export_import_page.dart';
|
||||||
@@ -103,7 +104,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 500),
|
constraints: BoxConstraints(maxWidth: responsiveMaxWidth(context)),
|
||||||
child: Form(
|
child: Form(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../config/responsive.dart';
|
||||||
import '../config/strings.dart';
|
import '../config/strings.dart';
|
||||||
import '../config/title_server_config.dart';
|
import '../config/title_server_config.dart';
|
||||||
import '../services/title_api_service.dart';
|
import '../services/title_api_service.dart';
|
||||||
@@ -251,7 +252,7 @@ class _TicketPageState extends State<TicketPage> {
|
|||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 500),
|
constraints: BoxConstraints(maxWidth: responsiveMaxWidth(context)),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
if (widget.loginDateTime == null) _buildNotLoggedInBanner(theme),
|
if (widget.loginDateTime == null) _buildNotLoggedInBanner(theme),
|
||||||
|
|||||||
@@ -27,19 +27,13 @@ class ApiService {
|
|||||||
ApiService([this._config]);
|
ApiService([this._config]);
|
||||||
|
|
||||||
TitleServerConfig? get _cfg => _config ?? TitleServerConfigHolder().config;
|
TitleServerConfig? get _cfg => _config ?? TitleServerConfigHolder().config;
|
||||||
String get _chipId {
|
String get _chipId => _cfg?.keychipId ?? '';
|
||||||
final v = _cfg?.keychipId;
|
String get _aimeSalt => _cfg?.aimeSalt ?? '';
|
||||||
return (v != null && v.isNotEmpty) ? v : 'A63E-01C28055905';
|
String get _aimeUrl => _cfg?.aimeUrl ?? '';
|
||||||
|
String get _openGameID {
|
||||||
|
final v = _cfg?.openGameID;
|
||||||
|
return (v != null && v.isNotEmpty) ? v : 'MAID';
|
||||||
}
|
}
|
||||||
String get _aimeSalt {
|
|
||||||
final v = _cfg?.aimeSalt;
|
|
||||||
return (v != null && v.isNotEmpty) ? v : 'XcW5FW4cPArBXEk4vzKz3CIrMuA5EVVW';
|
|
||||||
}
|
|
||||||
String get _aimeUrl {
|
|
||||||
final v = _cfg?.aimeUrl;
|
|
||||||
return (v != null && v.isNotEmpty) ? v : 'http://ai.sys-allnet.cn/wc_aime/api/get_data';
|
|
||||||
}
|
|
||||||
String get _openGameID => _cfg?.openGameID ?? 'MAID';
|
|
||||||
|
|
||||||
String _formatTimestamp() {
|
String _formatTimestamp() {
|
||||||
final tokyo = DateTime.now().toUtc().add(const Duration(hours: 9));
|
final tokyo = DateTime.now().toUtc().add(const Duration(hours: 9));
|
||||||
|
|||||||
Reference in new issue
Block a user