h-1.flutter.4/lib/utils/build_expiry_info.dart

126 lines
No EOL
4.1 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Version: 1.0.0
import 'package:flutter/foundation.dart';
/// アプリのビルド lifetime を管理するクラス
class BuildExpiryInfo {
/// バリッドな状態90 日以内)
static const String statusValid = 'valid';
/// 期限切れ状態
static const String statusExpired = 'expired';
/// ビルド時間を UTC 秒数として受け取り、残存寿命を判定する
/// [timestamp] は UTC タイムスタンプ(秒)
String getStatus(int timestamp) {
final now = DateTime.now().toUtc();
final buildTime = DateTime.fromMillisecondsSinceEpoch(
timestamp * 1000,
isUtc: true,
);
final expiryTime = _expiryTimestamp.buildDateTime;
if (now.isAfter(expiryTime)) {
return statusExpired;
}
return statusValid;
}
/// 残り寿命を DateTime オブジェクトとして返す(期限切れの場合 null
DateTime? getRemainingExpiry(int timestamp) {
final now = DateTime.now().toUtc();
final expiryTime = _expiryTimestamp.buildDateTime;
if (now.isAfter(expiryTime)) {
return null;
}
// 残り寿命を計算90 日 + 15 日間延命用 buffer
final daysRemaining = ((expiryTime.millisecondsSinceEpoch - now.millisecondsSinceEpoch) ~/ Duration.millisecondsPerDay);
return now.add(Duration(days: daysRemaining));
}
static BuildExpiryInfo get instance => _instance;
factory BuildExpiryInfo() => _instance;
late final DateTime _expiryTimestamp;
String _appBuildTimestamp = '';
/// アプリのパッケージ情報からビルド時間を取得する
void initializeFromPackageInfo(PackageInfo packageInfo) async {
_appBuildTimestamp = (await packageInfo).version;
}
/// 寿命切れ画面を表示すべきかどうかを判定
bool get isExpired => getStatus(_parseTimestamp(_appBuildTimestamp));
/// 寿命切れメッセージの取得
String get expiredMessage {
return '本アプリの有効期限が切れています。\n母艦(お局様)からの同期が必要となります。';
}
}
/// アプリ初期化時に呼ぶヘルパー
class ExpiryInitHelper {
static Future<void> initialize(BuildContext context, PackageInfo packageInfo) async {
final expiry = BuildExpiryInfo();
// パッケージ情報からビルド時間を含むバージョンをパース(例: "1.0.0+1234567890"
final timestamp = int.tryParse(_extractTimestamp(packageInfo.version));
if (timestamp != null) {
expiry._expiryTimestamp = DateTime.fromMillisecondsSinceEpoch(
timestamp * 1000,
isUtc: true,
).add(const Duration(days: 90));
// トランザクションで寿命チェックを実行
await context.mount<ExpiredApp>(
key: ExpiryInitHelper._routeName,
condition: () => expiry.isExpired,
builder: (context) => ExpiredApp(expiry: expiry),
);
}
}
static String _extractTimestamp(String version) {
// "1.0.0+1234567890" の形式の場合、+以降をパース
final parts = version.split('+');
if (parts.length > 1) {
return parts[1];
}
return '0';
}
static const String _routeName = '/expired_app_route';
}
class ExpiredApp extends StatelessWidget {
final BuildExpiryInfo expiry;
const ExpiredApp({super.key, required this.expiry});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('有効期限切れ')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error_outline, size: 80, color: Colors.red),
const SizedBox(height: 24),
Text(
expiry.expiredMessage,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 16),
),
const SizedBox(height: 32),
ElevatedButton(
onPressed: () => Navigator.of(context).pushNamedAndRemoveUntil('/', (route) => false),
child: const Text('設定画面へ戻る'),
),
],
),
),
);
}
}