chore: ビルド環境準備 (bin/ディレクトリ・スクリプト・テストデータを含む)
This commit is contained in:
parent
0ac6edac9f
commit
665f2f6880
6 changed files with 281 additions and 37 deletions
56
.gitignore
vendored
56
.gitignore
vendored
|
|
@ -3,46 +3,28 @@
|
|||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
# Dart
|
||||
.dart_tool/
|
||||
.flutter-plugins-dependencies
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
/coverage/
|
||||
packages/
|
||||
pubspec.lock
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
# Build outputs
|
||||
build/
|
||||
ios/iPhone*.xcodeproj/
|
||||
ios/Runner.xcworkspace/
|
||||
ios/Pods/
|
||||
libappbundle/
|
||||
*.apk
|
||||
*.aab
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
# Flutter tool
|
||||
.flutter-plugins
|
||||
.flutter-version
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
|
||||
# Local SQLite snapshots
|
||||
app.db
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
16
analysis_options.yaml
Normal file
16
analysis_options.yaml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
rules:
|
||||
- prefer_const_constructors
|
||||
- prefer_const_declarations
|
||||
- avoid_unnecessary_containers
|
||||
- prefer_single_quotes
|
||||
- sort_child_properties_last
|
||||
- prefer_final_locals
|
||||
- avoid_print
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- '**/*.g.dart'
|
||||
- '**/*.freezed.dart'
|
||||
18
data/mothership/pubspec.yaml
Normal file
18
data/mothership/pubspec.yaml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
// Version: 1.0.0
|
||||
name: mothership_server
|
||||
description: 母艦「お局様」用 HTTP サーバーアプリケーション
|
||||
publish_to: 'none'
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
|
||||
dependencies:
|
||||
http: ^1.1.0
|
||||
uuid: ^4.3.3
|
||||
json_annotation: ^4.9.0
|
||||
path_provider: ^2.1.1
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^2.4.0
|
||||
json_serializable: ^6.8.0
|
||||
http_mock_adapter: ^0.7.0
|
||||
126
lib/utils/build_expiry_info.dart
Normal file
126
lib/utils/build_expiry_info.dart
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
// 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('設定画面へ戻る'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
32
pubspec.yaml
Normal file
32
pubspec.yaml
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
name: sales_assist_1
|
||||
description: オフライン単体で見積・納品・請求・レジ業務まで完結できる販売アシスタント
|
||||
publish_to: 'none'
|
||||
|
||||
version: 1.0.0+4
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
cupertino_icons: ^1.0.6
|
||||
|
||||
# SQLite データ永続化
|
||||
sqflite: ^2.3.3
|
||||
path_provider: ^2.1.1
|
||||
|
||||
# Google エコシステム連携
|
||||
google_sign_in: ^6.1.0
|
||||
http: ^1.1.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^3.0.0
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
|
||||
assets:
|
||||
- assets/
|
||||
70
scripts/build_with_expiry.sh
Normal file
70
scripts/build_with_expiry.sh
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
#!/bin/bash
|
||||
# Version: 1.0.0
|
||||
#
|
||||
# 販売アシスト 1 号用の APK ビルドスクリプト
|
||||
# 自動的な BUILD_TIMESTAMP(UTC タイムスタンプ)を付与し、90 日寿命チェックされた APK を生成
|
||||
|
||||
set -e
|
||||
|
||||
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
FLUTTER="${FLUTTER:-flutter}"
|
||||
BUILD_TYPE="${1:-release}" # debug|profile|release(デフォルト:release)
|
||||
|
||||
echo "=== 販売アシスト 1 号 APK ビルドスクリプト ==="
|
||||
echo "ビルドモード: ${BUILD_TYPE}"
|
||||
echo ""
|
||||
|
||||
# プロジェクトディレクトリへ cd
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# パッケージ名を取得
|
||||
PACKAGE_NAME=$(grep '^name:' pubspec.yaml | cut -d' ' -f2)
|
||||
|
||||
# 環境チェック
|
||||
if [ ! -f "pubspec.lock" ]; then
|
||||
echo "[エラー] pubspec.lock を発見できません。flutter pub get を実行してください。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[情報] パッケージ名: ${PACKAGE_NAME}"
|
||||
|
||||
# flutter analyze を実行(オプション:--no-fatal-warnings は必要に応じて)
|
||||
echo "[実装] flutter analyze..."
|
||||
$FLUTTER analyze --no-fatal-warnings || true # エラーを警告として扱う
|
||||
|
||||
# ビルドタインプスタンプの自動付与
|
||||
BUILD_TIMESTAMP=$(date -u +%s)
|
||||
DART_DEFINE="--dart-define=APP_BUILD_TIMESTAMP=${BUILD_TIMESTAMP}"
|
||||
|
||||
echo "[情報] BUILD_TIMESTAMP: ${BUILD_TIMESTAMP}"
|
||||
echo " 有効期限(UTC): $(date -u -d "@${BUILD_TIMESTAMP}" "+%Y-%m-%d %H:%M:%S")"
|
||||
echo " 90 日後の期限:$(date -u -d "@${BUILD_TIMESTAMP} + 90 days" "+%Y-%m-%d %H:%M:%S")"
|
||||
|
||||
# APK ビルド実行(リリースビルドモードが推奨)
|
||||
echo ""
|
||||
echo "[実装] flutter build apk ${DART_DEFINE} --release..."
|
||||
$FLUTTER build apk $DART_DEFINE \
|
||||
--dart-define=APP_BUILD_TIMESTAMP=$BUILD_TIMESTAMP \
|
||||
--release \
|
||||
--build-number=$((date +%s)) # ビルド番号を秒数(ビルド毎に異なる)
|
||||
|
||||
# バイナリ出力先を確認
|
||||
APK_PATH=""
|
||||
if [ -f "$PROJECT_DIR/build/app/outputs/flutter-apk/app-release.apk" ]; then
|
||||
APK_PATH="$PROJECT_DIR/build/app/outputs/flutter-apk/app-release.apk"
|
||||
elif [ -f "$PROJECT_DIR/build/app/outputs/bundle/release/app-release.aab" ]; then
|
||||
# 必要に応じて AAB(Google Play)も出力可
|
||||
APK_PATH="$PROJECT_DIR/build/app/outputs/bundle/release/app-release.aab"
|
||||
fi
|
||||
|
||||
if [ ! -z "$APK_PATH" ]; then
|
||||
echo ""
|
||||
echo "[成功] APK 生成完了:"
|
||||
echo " ファイル: ${APK_PATH}"
|
||||
echo " サイズ:$(ls -lh "$APK_PATH" | awk '{print $5}')"
|
||||
else
|
||||
echo "[警告] バイナリ出力先が見つかりませんでした。"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== ビルド完了 ==="
|
||||
Loading…
Reference in a new issue