refactor: code quality improvements based on critical review
Made-with: Cursor
This commit is contained in:
parent
2074f85da8
commit
94f7ee20ea
|
|
@ -78,6 +78,7 @@ Desktop.ini
|
|||
*.tmp
|
||||
*.bak
|
||||
analyze_output*.txt
|
||||
build_log_*.txt
|
||||
|
||||
# Deprecated build scripts (API keys hardcoded - use build_consumer_apks.ps1 instead)
|
||||
build_all_apks.ps1
|
||||
|
|
|
|||
|
|
@ -10,10 +10,6 @@ import 'screens/main_screen.dart';
|
|||
import 'screens/license_screen.dart';
|
||||
import 'services/migration_service.dart';
|
||||
|
||||
/// ビルド時Pro解放フラグ(現在未使用 — 実行時ライセンスはisProProviderで管理)
|
||||
/// 将来的に削除予定。isProProvider (license_provider.dart) を使うこと。
|
||||
const bool isProVersion = bool.fromEnvironment('IS_PRO_VERSION', defaultValue: false);
|
||||
|
||||
/// 店舗向けビルドかどうかを判定するビルド時フラグ
|
||||
///
|
||||
/// ビルドコマンド:
|
||||
|
|
@ -57,12 +53,9 @@ void main() async {
|
|||
: box.get('migration_version', defaultValue: 0) as int; // 新規ユーザー: 未実行=v0
|
||||
|
||||
if (storedVersion < currentMigrationVersion) {
|
||||
debugPrint('🚀 Running MigrationService (v$storedVersion → v$currentMigrationVersion)...');
|
||||
await MigrationService.runMigration();
|
||||
await box.put('migration_version', currentMigrationVersion);
|
||||
await box.put('migration_completed', true); // 旧フラグも維持(後方互換)
|
||||
} else {
|
||||
debugPrint('✅ Migration up to date (v$storedVersion). Skipping.');
|
||||
}
|
||||
|
||||
// ✅ AI解析キャッシュは使うときに初期化する(Lazy initialization)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../services/gemini_service.dart';
|
||||
|
||||
/// GeminiService のシングルトンプロバイダー
|
||||
///
|
||||
/// アプリ全体で同一インスタンスを共有する。
|
||||
/// レート制限の状態(_lastApiCallTime)がインスタンス間で共有されるため、
|
||||
/// 複数画面から同時に呼び出しても連打防止が正しく機能する。
|
||||
///
|
||||
/// 使用例:
|
||||
/// ```dart
|
||||
/// final geminiService = ref.read(geminiServiceProvider);
|
||||
/// final result = await geminiService.analyzeSakeLabel(paths);
|
||||
/// ```
|
||||
final geminiServiceProvider = Provider<GeminiService>((ref) {
|
||||
return GeminiService();
|
||||
});
|
||||
|
|
@ -10,6 +10,7 @@ import 'package:uuid/uuid.dart';
|
|||
import 'package:gal/gal.dart';
|
||||
|
||||
import '../services/gemini_service.dart';
|
||||
import '../providers/gemini_provider.dart';
|
||||
import '../services/gemini_exceptions.dart';
|
||||
import '../services/image_compression_service.dart';
|
||||
import '../services/gamification_service.dart'; // Badge check
|
||||
|
|
@ -372,19 +373,21 @@ class _CameraScreenState extends ConsumerState<CameraScreen> with SingleTickerPr
|
|||
Future<void> _analyzeImages() async {
|
||||
if (_capturedImages.isEmpty) return;
|
||||
|
||||
// async gap 前に context 依存オブジェクトをキャプチャ
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
final navigator = Navigator.of(context);
|
||||
|
||||
final isOnline = await NetworkService.isOnline();
|
||||
if (!isOnline) {
|
||||
// オフライン時: Draft として保存
|
||||
debugPrint('📴 Offline detected: Saving as draft...');
|
||||
debugPrint('Offline detected: Saving as draft...');
|
||||
|
||||
try {
|
||||
// 🔧 FIX: 複数画像をすべて保存
|
||||
await DraftService.saveDraft(_capturedImages);
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
// ユーザーに通知
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
messenger.showSnackBar(
|
||||
const SnackBar(
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
|
@ -407,13 +410,12 @@ class _CameraScreenState extends ConsumerState<CameraScreen> with SingleTickerPr
|
|||
),
|
||||
);
|
||||
|
||||
// カメラ画面を閉じる
|
||||
Navigator.of(context).pop();
|
||||
navigator.pop();
|
||||
return;
|
||||
} catch (e) {
|
||||
debugPrint('⚠️ Draft save error: $e');
|
||||
debugPrint('Draft save error: $e');
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
messenger.showSnackBar(
|
||||
SnackBar(content: Text('Draft保存エラー: $e')),
|
||||
);
|
||||
return;
|
||||
|
|
@ -421,7 +423,6 @@ class _CameraScreenState extends ConsumerState<CameraScreen> with SingleTickerPr
|
|||
}
|
||||
|
||||
// オンライン時: 通常の解析フロー
|
||||
// Show AnalyzingDialog
|
||||
if (!mounted) return;
|
||||
showDialog(
|
||||
context: context,
|
||||
|
|
@ -432,7 +433,7 @@ class _CameraScreenState extends ConsumerState<CameraScreen> with SingleTickerPr
|
|||
try {
|
||||
// Direct Gemini Vision Analysis (OCR removed for app size reduction)
|
||||
debugPrint('📸 Starting Gemini Vision Direct Analysis for ${_capturedImages.length} images');
|
||||
final geminiService = GeminiService();
|
||||
final geminiService = ref.read(geminiServiceProvider);
|
||||
final result = await geminiService.analyzeSakeLabel(_capturedImages);
|
||||
|
||||
// Create SakeItem (Schema v2.0)
|
||||
|
|
@ -504,11 +505,8 @@ class _CameraScreenState extends ConsumerState<CameraScreen> with SingleTickerPr
|
|||
|
||||
if (!mounted) return;
|
||||
|
||||
// Close Dialog
|
||||
Navigator.of(context).pop();
|
||||
|
||||
// Close Camera Screen (Return to Home)
|
||||
Navigator.of(context).pop();
|
||||
navigator.pop(); // Close AnalyzingDialog
|
||||
navigator.pop(); // Close Camera Screen (Return to Home)
|
||||
|
||||
// Success Message (with EXP/Level Up/Badge info)
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
|
|
@ -554,28 +552,28 @@ class _CameraScreenState extends ConsumerState<CameraScreen> with SingleTickerPr
|
|||
}
|
||||
}
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
messenger.showSnackBar(
|
||||
SnackBar(
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: messageWidgets,
|
||||
),
|
||||
duration: Duration(seconds: newBadges.isNotEmpty ? 6 : 4), // Longer for badges
|
||||
duration: Duration(seconds: newBadges.isNotEmpty ? 6 : 4),
|
||||
),
|
||||
);
|
||||
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop(); // Close AnalyzingDialog
|
||||
navigator.pop(); // Close AnalyzingDialog
|
||||
|
||||
// AIサーバー混雑(503)→ ドラフト保存してオフライン時と同じ扱いに
|
||||
if (e is GeminiCongestionException) {
|
||||
try {
|
||||
await DraftService.saveDraft(_capturedImages);
|
||||
if (!mounted) return;
|
||||
Navigator.of(context).pop(); // Close camera screen
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
navigator.pop(); // Close camera screen
|
||||
messenger.showSnackBar(
|
||||
const SnackBar(
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
|
@ -598,9 +596,8 @@ class _CameraScreenState extends ConsumerState<CameraScreen> with SingleTickerPr
|
|||
),
|
||||
);
|
||||
} catch (_) {
|
||||
// ドラフト保存も失敗した場合のみエラー表示
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
messenger.showSnackBar(
|
||||
const SnackBar(content: Text('解析もドラフト保存も失敗しました。再試行してください。')),
|
||||
);
|
||||
}
|
||||
|
|
@ -616,7 +613,7 @@ class _CameraScreenState extends ConsumerState<CameraScreen> with SingleTickerPr
|
|||
}
|
||||
|
||||
final appColors = Theme.of(context).extension<AppColors>()!;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
messenger.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('解析エラー: $e'),
|
||||
duration: const Duration(seconds: 5),
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import 'package:hive_flutter/hive_flutter.dart';
|
|||
import '../models/sake_item.dart';
|
||||
import '../providers/sake_list_provider.dart';
|
||||
import '../services/gemini_service.dart';
|
||||
import '../providers/gemini_provider.dart';
|
||||
|
||||
class DevMenuScreen extends ConsumerWidget {
|
||||
const DevMenuScreen({super.key});
|
||||
|
|
@ -198,7 +199,7 @@ class DevMenuScreen extends ConsumerWidget {
|
|||
final box = Hive.box<SakeItem>('sake_items');
|
||||
|
||||
try {
|
||||
final gemini = GeminiService();
|
||||
final gemini = ref.read(geminiServiceProvider);
|
||||
|
||||
for (final item in targets) {
|
||||
if (item.displayData.imagePaths.isEmpty) continue;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import 'package:lucide_icons/lucide_icons.dart';
|
||||
import '../models/sake_item.dart';
|
||||
import '../services/gemini_service.dart';
|
||||
import '../providers/gemini_provider.dart';
|
||||
import '../services/sake_recommendation_service.dart';
|
||||
import '../widgets/analyzing_dialog.dart';
|
||||
import '../widgets/sake_3d_carousel_with_reason.dart';
|
||||
|
|
@ -331,11 +332,8 @@ class _SakeDetailScreenState extends ConsumerState<SakeDetailScreen> {
|
|||
existingPaths.add(path);
|
||||
}
|
||||
}
|
||||
// mounted チェック後に context 依存オブジェクトをキャプチャ(async gap 対策)
|
||||
if (!mounted) return;
|
||||
// ignore: use_build_context_synchronously
|
||||
final nav = Navigator.of(context);
|
||||
// ignore: use_build_context_synchronously
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
|
||||
if (existingPaths.isEmpty) {
|
||||
|
|
@ -348,15 +346,13 @@ class _SakeDetailScreenState extends ConsumerState<SakeDetailScreen> {
|
|||
setState(() => _isAnalyzing = true);
|
||||
|
||||
try {
|
||||
// ignore: use_build_context_synchronously
|
||||
showDialog(
|
||||
context: context, // ignore: use_build_context_synchronously
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => const AnalyzingDialog(),
|
||||
);
|
||||
|
||||
final geminiService = GeminiService();
|
||||
// forceRefresh: true でキャッシュを無視して再解析
|
||||
final geminiService = ref.read(geminiServiceProvider);
|
||||
final result = await geminiService.analyzeSakeLabel(existingPaths, forceRefresh: true);
|
||||
|
||||
final newItem = _sake.copyWith(
|
||||
|
|
|
|||
|
|
@ -1,121 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../models/sake_item.dart';
|
||||
import '../models/user_profile.dart';
|
||||
import '../models/menu_settings.dart';
|
||||
import '../services/migration_service.dart';
|
||||
import 'main_screen.dart';
|
||||
|
||||
class SplashScreen extends ConsumerStatefulWidget {
|
||||
const SplashScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<SplashScreen> createState() => _SplashScreenState();
|
||||
}
|
||||
|
||||
class _SplashScreenState extends ConsumerState<SplashScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initializeData();
|
||||
}
|
||||
|
||||
Future<void> _initializeData() async {
|
||||
// Artificial delay for branding (optional, keep it minimal)
|
||||
// await Future.delayed(const Duration(milliseconds: 500));
|
||||
|
||||
try {
|
||||
// Initialize Hive
|
||||
await Hive.initFlutter();
|
||||
|
||||
// Register Adapters
|
||||
Hive.registerAdapter(SakeItemAdapter());
|
||||
Hive.registerAdapter(UserProfileAdapter());
|
||||
Hive.registerAdapter(MenuSettingsAdapter());
|
||||
// Phase 0 New Adapters
|
||||
Hive.registerAdapter(DisplayDataAdapter());
|
||||
Hive.registerAdapter(HiddenSpecsAdapter());
|
||||
Hive.registerAdapter(UserDataAdapter());
|
||||
Hive.registerAdapter(GamificationAdapter());
|
||||
Hive.registerAdapter(MetadataAdapter());
|
||||
Hive.registerAdapter(ItemTypeAdapter());
|
||||
|
||||
// Open all boxes (Parallel Execution)
|
||||
final results = await Future.wait([
|
||||
Hive.openBox('settings'),
|
||||
Hive.openBox<UserProfile>('user_profile'),
|
||||
Hive.openBox<SakeItem>('sake_items'),
|
||||
Hive.openBox<MenuSettings>('menu_settings'),
|
||||
]);
|
||||
|
||||
final settingsBox = results[0];
|
||||
|
||||
// Run Phase 0 Migration (Only once)
|
||||
final migrationCompleted = settingsBox.get('migration_completed', defaultValue: false);
|
||||
if (!migrationCompleted) {
|
||||
debugPrint('🚀 Running MigrationService...');
|
||||
await MigrationService.runMigration();
|
||||
await settingsBox.put('migration_completed', true);
|
||||
} else {
|
||||
debugPrint('✅ Migration already completed. Skipping.');
|
||||
}
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
// Navigate to MainScreen
|
||||
Navigator.of(context).pushReplacement(
|
||||
PageRouteBuilder(
|
||||
pageBuilder: (context, animation, secondaryAnimation) => const MainScreen(),
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
return FadeTransition(opacity: animation, child: child);
|
||||
},
|
||||
transitionDuration: const Duration(milliseconds: 500),
|
||||
),
|
||||
);
|
||||
|
||||
} catch (e) {
|
||||
debugPrint('❌ Initialization Error: $e');
|
||||
// Show error UI or retry logic
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white, // Match brand color
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// App Logo
|
||||
const Text(
|
||||
'🍶',
|
||||
style: TextStyle(fontSize: 80),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
'Ponshu Room',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.grey[800],
|
||||
letterSpacing: 1.2,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 48),
|
||||
// Subtle loading indicator
|
||||
SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Colors.grey[400],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -69,6 +69,7 @@ class AnalysisCacheService {
|
|||
/// キャッシュから取得
|
||||
///
|
||||
/// 戻り値: キャッシュがあれば解析結果、なければ null
|
||||
/// タイムスタンプ付き新形式と旧形式(直接 JSON)の両方に対応
|
||||
static Future<SakeAnalysisResult?> getCached(String imageHash) async {
|
||||
await init();
|
||||
if (_box == null) return null;
|
||||
|
|
@ -76,33 +77,43 @@ class AnalysisCacheService {
|
|||
try {
|
||||
final jsonString = _box!.get(imageHash);
|
||||
if (jsonString == null) {
|
||||
debugPrint('🔍 Cache MISS: $imageHash');
|
||||
debugPrint('Cache MISS: $imageHash');
|
||||
return null;
|
||||
}
|
||||
|
||||
final jsonMap = jsonDecode(jsonString) as Map<String, dynamic>;
|
||||
debugPrint('✅ Cache HIT: ${jsonMap['name'] ?? 'Unknown'} ($imageHash)');
|
||||
return SakeAnalysisResult.fromJson(jsonMap);
|
||||
final decoded = jsonDecode(jsonString) as Map<String, dynamic>;
|
||||
|
||||
// 新形式: { data: {...}, savedAt: "..." }
|
||||
if (decoded.containsKey('data') && decoded.containsKey('savedAt')) {
|
||||
debugPrint('Cache HIT: ${decoded['data']['name'] ?? 'Unknown'}');
|
||||
return SakeAnalysisResult.fromJson(decoded['data'] as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
// 旧形式(後方互換): 直接 SakeAnalysisResult の JSON
|
||||
debugPrint('Cache HIT (legacy): ${decoded['name'] ?? 'Unknown'}');
|
||||
return SakeAnalysisResult.fromJson(decoded);
|
||||
} catch (e) {
|
||||
debugPrint('⚠️ Cache read error: $e');
|
||||
debugPrint('Cache read error: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// キャッシュに保存
|
||||
///
|
||||
/// 解析結果をJSON化してHiveに永続化
|
||||
/// 解析結果を JSON 化してタイムスタンプ付きで Hive に永続化
|
||||
static Future<void> saveCache(String imageHash, SakeAnalysisResult result) async {
|
||||
await init();
|
||||
if (_box == null) return;
|
||||
|
||||
try {
|
||||
final jsonMap = result.toJson();
|
||||
final jsonString = jsonEncode(jsonMap);
|
||||
await _box!.put(imageHash, jsonString);
|
||||
debugPrint('💾 Cache SAVED: ${result.name ?? 'Unknown'} ($imageHash)');
|
||||
final entry = {
|
||||
'data': result.toJson(),
|
||||
'savedAt': DateTime.now().toIso8601String(),
|
||||
};
|
||||
await _box!.put(imageHash, jsonEncode(entry));
|
||||
debugPrint('Cache saved: ${result.name ?? 'Unknown'}');
|
||||
} catch (e) {
|
||||
debugPrint('⚠️ Cache save error: $e');
|
||||
debugPrint('Cache save error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -122,13 +133,37 @@ class AnalysisCacheService {
|
|||
return _box?.length ?? 0;
|
||||
}
|
||||
|
||||
/// キャッシュの有効期限チェック(将来実装)
|
||||
/// 30日以上経過したキャッシュエントリを削除
|
||||
///
|
||||
/// 現在は永続キャッシュだが、将来的に有効期限を設定する場合:
|
||||
/// - 30日経過したキャッシュは削除
|
||||
/// - 日本酒の仕様変更(リニューアル)に対応
|
||||
static Future<void> cleanupExpired() async {
|
||||
// TODO: キャッシュにタイムスタンプを追加し、30日以上古いエントリを削除する
|
||||
/// アプリ起動時またはバックグラウンドで呼び出す。
|
||||
/// 旧形式エントリ(タイムスタンプなし)は対象外として保持する。
|
||||
static Future<void> cleanupExpired({int ttlDays = 30}) async {
|
||||
await init();
|
||||
if (_box == null) return;
|
||||
|
||||
final cutoff = DateTime.now().subtract(Duration(days: ttlDays));
|
||||
final keysToDelete = <dynamic>[];
|
||||
|
||||
for (final key in _box!.keys) {
|
||||
try {
|
||||
final jsonString = _box!.get(key as String);
|
||||
if (jsonString == null) continue;
|
||||
final decoded = jsonDecode(jsonString) as Map<String, dynamic>;
|
||||
if (!decoded.containsKey('savedAt')) continue; // 旧形式は対象外
|
||||
|
||||
final savedAt = DateTime.tryParse(decoded['savedAt'] as String? ?? '');
|
||||
if (savedAt != null && savedAt.isBefore(cutoff)) {
|
||||
keysToDelete.add(key);
|
||||
}
|
||||
} catch (_) {
|
||||
// 読み込みエラーのエントリは無視
|
||||
}
|
||||
}
|
||||
|
||||
if (keysToDelete.isNotEmpty) {
|
||||
await _box!.deleteAll(keysToDelete);
|
||||
debugPrint('Cache cleanup: ${keysToDelete.length} expired entries removed');
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
|
|
@ -162,25 +197,29 @@ class AnalysisCacheService {
|
|||
|
||||
/// 銘柄名をインデックスに登録
|
||||
///
|
||||
/// 解析結果のキャッシュ保存後に呼び出す
|
||||
/// 既にインデックスがある場合は上書きしない(最初の結果を優先)
|
||||
static Future<void> registerBrandIndex(String? brandName, String imageHash) async {
|
||||
/// [forceUpdate] = true のとき(再解析など)は既存エントリを上書きする。
|
||||
/// false のときは最初の結果を優先し、上書きしない。
|
||||
static Future<void> registerBrandIndex(
|
||||
String? brandName,
|
||||
String imageHash, {
|
||||
bool forceUpdate = false,
|
||||
}) async {
|
||||
if (brandName == null || brandName.isEmpty) return;
|
||||
await init();
|
||||
if (_brandIndexBox == null) return;
|
||||
|
||||
try {
|
||||
final normalized = _normalizeBrandName(brandName);
|
||||
final exists = _brandIndexBox!.containsKey(normalized);
|
||||
|
||||
// 既にインデックスがある場合は上書きしない(最初の結果を優先)
|
||||
if (!_brandIndexBox!.containsKey(normalized)) {
|
||||
if (!exists || forceUpdate) {
|
||||
await _brandIndexBox!.put(normalized, imageHash);
|
||||
debugPrint('📝 Brand index registered: $brandName → $imageHash');
|
||||
debugPrint('Brand index ${exists ? "updated" : "registered"}: $brandName');
|
||||
} else {
|
||||
debugPrint('ℹ️ Brand index already exists: $brandName (skipped)');
|
||||
debugPrint('Brand index already exists: $brandName (skipped)');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('⚠️ Brand index registration error: $e');
|
||||
debugPrint('Brand index registration error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -284,8 +284,12 @@ class GeminiService {
|
|||
if (imagePaths.isNotEmpty) {
|
||||
final imageHash = await AnalysisCacheService.computeCombinedHash(imagePaths);
|
||||
await AnalysisCacheService.saveCache(imageHash, result);
|
||||
// 4. 銘柄名インデックスに登録(v1.0.15: チャート一貫性向上)
|
||||
await AnalysisCacheService.registerBrandIndex(result.name, imageHash);
|
||||
// 4. 銘柄名インデックスに登録(forceRefresh 時は誤認識結果を上書き)
|
||||
await AnalysisCacheService.registerBrandIndex(
|
||||
result.name,
|
||||
imageHash,
|
||||
forceUpdate: forceRefresh,
|
||||
);
|
||||
}
|
||||
|
||||
if (attempt > 0) debugPrint('Succeeded on attempt $attempt (model: $modelName)');
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.0.30+37
|
||||
version: 1.0.31+38
|
||||
|
||||
environment:
|
||||
sdk: ^3.10.1
|
||||
|
|
@ -97,8 +97,8 @@ flutter_launcher_icons:
|
|||
web:
|
||||
generate: true
|
||||
image_path: "assets/images/app_icon.png"
|
||||
background_color: "#hex_code"
|
||||
theme_color: "#hex_code"
|
||||
background_color: "#FDFAF5"
|
||||
theme_color: "#4A3B32"
|
||||
windows:
|
||||
generate: true
|
||||
image_path: "assets/images/app_icon.png"
|
||||
|
|
|
|||
|
|
@ -6,15 +6,24 @@
|
|||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
|
||||
#include <file_selector_windows/file_selector_windows.h>
|
||||
#include <gal/gal_plugin_c_api.h>
|
||||
#include <printing/printing_plugin.h>
|
||||
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
||||
FileSelectorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||
GalPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("GalPluginCApi"));
|
||||
PrintingPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("PrintingPlugin"));
|
||||
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,12 @@
|
|||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
connectivity_plus
|
||||
file_selector_windows
|
||||
gal
|
||||
printing
|
||||
share_plus
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
|
|
|||
Loading…
Reference in New Issue