Files
rog_app/lib/pages/index/index_controller.dart
2024-09-08 18:16:51 +09:00

1185 lines
39 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.

import 'dart:async';
import 'dart:io';
import 'package:get/get.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_polyline_points/flutter_polyline_points.dart';
import 'package:geojson_vi/geojson_vi.dart';
import 'package:geolocator/geolocator.dart';
import 'package:latlong2/latlong.dart';
import 'package:gifunavi/model/destination.dart';
import 'package:gifunavi/model/entry.dart';
import 'package:gifunavi/pages/destination/destination_controller.dart';
import 'package:gifunavi/pages/team/team_controller.dart';
import 'package:gifunavi/routes/app_pages.dart';
import 'package:gifunavi/services/auth_service.dart';
import 'package:gifunavi/services/location_service.dart';
import 'package:gifunavi/utils/database_helper.dart';
import 'package:gifunavi/widgets/debug_widget.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:gifunavi/services/api_service.dart';
import 'package:gifunavi/model/user.dart';
import 'package:gifunavi/main.dart';
import 'package:gifunavi/widgets/helper_dialog.dart';
import 'package:timezone/timezone.dart' as tz;
import 'package:timezone/data/latest.dart' as tz;
import '../permission/permission.dart';
//enum MapState { initializing, ready, error }
class IndexController extends GetxController with WidgetsBindingObserver {
List<GeoJSONFeatureCollection> locations = <GeoJSONFeatureCollection>[].obs;
List<GeoJSONFeature> currentFeature = <GeoJSONFeature>[].obs;
List<Destination> currentDestinationFeature = <Destination>[].obs;
List<dynamic> perfectures = <dynamic>[].obs;
List<LatLngBounds> currentBound = <LatLngBounds>[].obs;
List<dynamic> subPerfs = <dynamic>[].obs;
List<dynamic> areas = <dynamic>[].obs;
List<dynamic> customAreas = <dynamic>[].obs;
List<dynamic> cats = <dynamic>[].obs;
List<String> currentCat = <String>[].obs;
List<Map<String, dynamic>> currentUser = <Map<String, dynamic>>[].obs;
List<dynamic> currentAction = <dynamic>[].obs;
List<PointLatLng> routePoints = <PointLatLng>[].obs;
var routePointLenght = 0.obs;
double currentLat = 0.0, currentLon = 0.0;
var isLoading = false.obs;
var isRogMapcontrollerLoaded = false.obs;
var isCustomAreaSelected = false.obs;
//final mapControllerReadyStream = Stream<bool>.value(false); // MapControllerの初期化状態を通知するためのストリーム
// 複数の状態変数_isLoading, _isMapInitialized, indexController.isMapControllerReadyを一元管理します。
//final Rx<MapState> mapState = MapState.initializing.obs;
final Completer<void> mapControllerCompleter = Completer<void>();
late MapController mapController; // = Rx<MapController?>(null);
final RxBool isMapControllerReady = false.obs; // MapControllerの初期化状態を管理するフラグ
//MapController mapController = MapController();
MapController rogMapController = MapController();
LogManager logManager = LogManager();
String? userToken;
//late final ApiService _apiService;
final ApiService _apiService; // = Get.find<ApiService>();
final DatabaseHelper _dbHelper = DatabaseHelper.instance;
IndexController({
required ApiService apiService,
}) : _apiService = apiService;
// mode = 0 is map mode, mode = 1 list mode
var mode = 0.obs;
// master mode, rog or selection
var rogMode = 1.obs;
var desinationMode = 1.obs;
bool showPopup = true;
String dropdownValue = "9";
String subDropdownValue = "-1";
String areaDropdownValue = "-1";
String cateogory = "-all-";
final selectedEventName = 'add_location'.tr.obs;
RxBool isLoadingLocations = false.obs;
// ユーザーの参加状況を追跡する
final RxBool isUserParticipating = false.obs;
// ユーザーのゴール到達状況を追跡する
final RxBool hasUserReachedGoal = false.obs;
void initMapController() {
mapController = MapController();
// MapEventMoveEndイベントを使用して初期化完了を検出
mapController.mapEventStream.listen((event) {
if (event is MapEventMoveEnd && !isMapControllerReady.value) {
// MapEventMoveEndイベントが発生したら、MapControllerの準備完了とみなす
if (!isMapControllerReady.value) {
print('MapController is ready');
isMapControllerReady.value = true;
if (!mapControllerCompleter.isCompleted) {
mapControllerCompleter.complete();
}
}
}
});
}
// ユーザーの参加状況を更新するメソッド
void updateUserParticipationStatus(bool isParticipating) {
isUserParticipating.value = isParticipating;
update(); // GetX の update() メソッドを呼び出してUIを更新
}
// ユーザーのゴール到達状況を更新するメソッド
void updateUserGoalStatus(bool hasReachedGoal) {
hasUserReachedGoal.value = hasReachedGoal;
update(); // GetX の update() メソッドを呼び出してUIを更新
}
void setSelectedEventName(String eventName) {
selectedEventName.value = eventName;
}
final connectionStatus = Rx<ConnectivityResult>(ConnectivityResult.none);
var connectionStatusName = "".obs;
final Connectivity _connectivity = Connectivity();
late StreamSubscription<List<ConnectivityResult>> _connectivitySubscription;
final Rx<DateTime> lastUserUpdateTime = DateTime.now().obs;
RxInt teamId = RxInt(-1); // チームIDを保存するための変数
//late TeamController teamController = TeamController();
/*
void updateUserInfo(Map<String, dynamic> newUserInfo) {
currentUser.clear();
currentUser.add(newUserInfo);
lastUserUpdateTime.value = DateTime.now();
}
*/
final isReferenceMode = false.obs;
void setReferenceMode(bool value) {
isReferenceMode.value = value;
}
bool canStartRoge() {
return !isReferenceMode.value;
}
bool canCheckin() {
return !isReferenceMode.value;
}
void toggleMode() {
if (mode.value == 0) {
mode += 1;
} else {
mode -= 1;
}
}
void toggleDestinationMode() {
if (desinationMode.value == 0) {
desinationMode.value += 1;
} else {
desinationMode.value -= 1;
}
}
void switchPage(String page) {
////print("######## ${currentUser[0]["user"]["id"]}");
switch (page) {
case AppPages.INDEX:
{
rogMode.value = 0;
//print("-- rog mode is ctrl is ${rog_mode.value}");
Get.toNamed(page);
}
break;
case AppPages.TRAVEL:
{
rogMode.value = 1;
//Get.back();
//Get.off(DestnationPage(), binding: DestinationBinding());
}
break;
case AppPages.LOGIN:
{
rogMode.value = 2;
Get.toNamed(page);
}
break;
default:
{
rogMode.value = 1;
Get.toNamed(AppPages.INDEX);
}
}
}
Future<void> _checkLocationPermission() async {
if (Get.context == null) {
debugPrint('Get.context is null in _checkLocationPermission');
return;
}
LocationPermission permission = await Geolocator.checkPermission();
//permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
debugPrint('GPS : Denied');
await showLocationPermissionDeniedDialog();
} else if (permission == LocationPermission.deniedForever) {
debugPrint('GPS : Denied forever');
await showLocationPermissionDeniedDialog();
}else if (permission == LocationPermission.whileInUse){
debugPrint('GPS : While-In-Use');
await showLocationPermissionDeniedDialog();
}else{
debugPrint("Permission is no problem....");
}
}
// 追加
Future<void> showLocationPermissionDeniedDialog() async {
if (Get.context != null) {
print('Showing location permission denied dialog');
await showDialog(
context: Get.context!,
barrierDismissible: false,
builder: (BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: AlertDialog(
title: Text('location_permission_needed_title'.tr),
content: Text('location_permission_needed_main'.tr),
actions: [
TextButton(
onPressed: () {
logManager.addOperationLog("User tapped confirm button for location permission required.");
Navigator.of(context).pop();
},
child: Text('confirm'.tr),
),
],
),
);
},
);
} else {
print('Get.context is null in showLocationPermissionDeniedDialog');
// Get.contextがnullの場合の処理
print('Location permission denied, but context is null');
}
}
@override
void onInit() {
try {
super.onInit();
//mapController = MapController();
//ever(isMapControllerReady, (_) => _onMapControllerReady());
initMapController();
initConnectivity();
_connectivitySubscription = _connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
WidgetsBinding.instance.addObserver(this);
_startLocationService(); // アプリ起動時にLocationServiceを開始する
initializeApiService();
print('IndexController onInit called'); // デバッグ用の出力を追加
tz.initializeTimeZones();
//teamController = Get.find<TeamController>();
//loadLocations();
}catch(e,stacktrace){
print('Error in IndexController.onInit: $e');
print('Stack trace: $stacktrace');
// エラーレポートサービスにエラーを送信
//ErrorService.reportError(e, stackTrace, deviceInfo, LogManager().operationLogs);
}
}
void _onMapControllerReady() {
if (isMapControllerReady.value) {
loadLocations();
}
}
Future<void> loadLocations() async {
if (isLoadingLocations.value) return;
isLoadingLocations.value = true;
try {
debugPrint('IndexController: Starting to load locations');
await waitForMapControllerReady(); // loadLocations
debugPrint('IndexController: Permission granted, loading locations');
String eventCode = currentUser.isNotEmpty ? currentUser[0]["user"]["event_code"] ?? "" : "";
await loadLocationsBound(eventCode);
debugPrint('IndexController: Locations loaded successfully');
} catch (e) {
print('Error loading locations: $e');
// エラーハンドリングを追加(例:スナックバーでユーザーに通知)
Get.snackbar('エラー', '位置情報の取得に失敗しました: ${e.toString()}');
} finally {
isLoadingLocations.value = false;
}
}
void _updateConnectionStatus(List<ConnectivityResult> results) {
final result = results.isNotEmpty ? results.first : ConnectivityResult.none;
connectionStatus.value = result;
switch (result) {
case ConnectivityResult.wifi:
connectionStatusName.value = "WiFi";
break;
case ConnectivityResult.mobile:
connectionStatusName.value = "mobile";
break;
case ConnectivityResult.none:
connectionStatusName.value = "offline";
break;
default:
connectionStatusName.value = "不明";
break;
}
logManager.addOperationLog("Connection status changed to: ${connectionStatusName.value}");
if (result != ConnectivityResult.none) {
_handleConnectionRestored();
}
}
Future<void> _handleConnectionRestored() async {
await _syncPendingData();
}
Future<void> initConnectivity() async {
try {
final result = await _connectivity.checkConnectivity();
_updateConnectionStatus(result); // リストとして渡す
} on PlatformException catch (e) {
print('Couldn\'t check connectivity status: ${e.message}');
}
}
// 保留中のデータを同期する例のメソッド
Future<void> _syncPendingData() async {
// ここに保留中のデータを同期するロジックを実装
// 例: ローカルDBに保存された未送信のデータをサーバーに送信する
try {
// 同期処理
logManager.addOperationLog("Syncing pending data");
// 同期ロジックをここに実装
} catch (e) {
logManager.addOperationLog("Error syncing pending data: $e");
// エラーハンドリング
}
}
Future<void> initializeApiService() async {
if (currentUser.isNotEmpty) {
// 既にログインしている場合
await Get.putAsync(() => ApiService().init());
//await Get.putAsync(() => ApiService().init());
// 必要に応じて追加の初期化処理
}
}
/*
void checkPermission()
{
debugPrint("MapControllerの初期化が完了したら、位置情報の許可をチェックする");
_checkLocationPermission();
}
*/
@override
void onClose() {
_connectivitySubscription.cancel();
WidgetsBinding.instance.removeObserver(this);
_stopLocationService(); // アプリ終了時にLocationServiceを停止する
super.onClose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
if (!_isLocationServiceRunning()) {
_startLocationService();
}
} else if (state == AppLifecycleState.paused) {
_stopLocationService();
}
}
bool _isLocationServiceRunning() {
// LocationServiceが実行中かどうかを確認する処理を実装する
// 例えば、SharedPreferencesにサービスの状態を保存するなど
// ここでは簡単のために、常にfalseを返すようにしています
return false;
}
void _startLocationService() async {
if (Platform.isAndroid) {
const platform = MethodChannel('location');
try {
logManager.addOperationLog("Called start location service.");
await platform.invokeMethod('startLocationService');
} on PlatformException catch (e) {
print("Failed to start location service: '${e.message}'.");
}
}else if (Platform.isIOS) {
// iOSの位置情報サービス開始ロジック
// 例: geolocatorプラグインを使用する場合
try {
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
// 位置情報サービスが無効の場合の処理
return;
}
// 位置情報の権限確認と取得開始
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
// 権限が拒否された場合の処理
return;
}
}
// 位置情報の取得開始
Geolocator.getPositionStream().listen((Position position) {
// 位置情報を使用した処理
});
} catch (e) {
print('Error starting iOS location service: $e');
}
}
}
void _stopLocationService() async {
if (Platform.isAndroid) {
const platform = MethodChannel('location');
try {
logManager.addOperationLog("Called stop location service.");
await platform.invokeMethod('stopLocationService');
} on PlatformException catch (e) {
print("Failed to stop location service: '${e.message}'.");
}
}else{
debugPrint("stopLocation for iOS");
}
}
/*
@override
void onReady() async {
await readUserToken();
final token = userToken;
if (token != null && token.isNotEmpty) {
await loadUserDetailsForToken(token);
fixMapBound(token);
} else {
// ユーザートークンが存在しない場合はログイン画面にリダイレクト
Get.offAllNamed(AppPages.LOGIN);
}
// 地図のイベントリスナーを設定
indexController.mapController.mapEventStream.listen((MapEvent mapEvent) {
if (mapEvent is MapEventMoveEnd) {
indexController.loadLocationsBound();
}
});
super.onReady();
}
*/
/*
Future<void> initConnectivity() async {
late ConnectivityResult result;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
connectionStatus = (await _connectivity.checkConnectivity()) as ConnectivityResult;
await _updateConnectionStatus(connectionStatus);
} on PlatformException catch (_) {
//print('Couldn\'t check connectivity status --- $e');
return;
}
//return _updateConnectionStatus(result);
}
*/
LatLngBounds boundsFromLatLngList(List<LatLng> list) {
double? x0, x1, y0, y1;
for (LatLng latLng in list) {
if (x0 == null || x1 == null || y0 == null || y1 == null) {
x0 = x1 = latLng.latitude;
y0 = y1 = latLng.longitude;
} else {
if (latLng.latitude > x1) x1 = latLng.latitude;
if (latLng.latitude < x0) x0 = latLng.latitude;
if (latLng.longitude > y1) y1 = latLng.longitude;
if (latLng.longitude < y0) y0 = latLng.longitude;
}
}
logManager.addOperationLog("Called boundsFromLatLngList (${x1!},${y1!})-(${x0!},${y0!}).");
return LatLngBounds(LatLng(x1, y1), LatLng(x0, y0));
}
// 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。
//
Future<void> login(String email, String password) async {
try {
isLoading.value = true;
final value = await AuthService.login(email, password);
if (value.isNotEmpty && value['token'] != null) {
await changeUser(value);
await _initializeUserData();
Get.offAllNamed(AppPages.INDEX);
} else {
Get.snackbar('ログイン失敗', 'メールアドレスまたはパスワードが間違っています');
}
} catch (e) {
print('Login error: $e');
Get.snackbar('ログイン失敗', 'エラーが発生しました。もう一度お試しください。');
} finally {
isLoading.value = false;
}
}
Future<void> _initializeUserData() async {
try {
await fetchUserEventInfo();
await fetchTeamData();
// 他の必要なデータ取得処理
} catch (e) {
print('Error initializing user data: $e');
Get.snackbar('Error', 'Failed to load user data. Please try again.');
}
}
Future<void> login_old(String email, String password, BuildContext context) async{
try {
AuthService.login(email, password).then((value) async {
print("------- logged in user details ######## $value ###### --------");
if (value.isNotEmpty && value['token']!=null) {
logManager.addOperationLog("User logged in : $value.");
// Navigator.pop(context);
print("--------- user details login ----- $value");
// ログイン成功後、api_serviceを初期化
await Get.putAsync(() => ApiService().init());
// ユーザー情報の完全性をチェック
if (await checkUserInfoComplete()) {
Get.offAllNamed(AppPages.INDEX);
} else {
Get.offAllNamed(AppPages.USER_DETAILS_EDIT);
}
} else {
logManager.addOperationLog("User failed login : $email , $password.");
isLoading.value = false;
Get.snackbar(
"login_failed".tr,
"check_login_id_or_password".tr,
backgroundColor: Colors.red,
colorText: Colors.white,
icon: const Icon(Icons.error, size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
//backgroundColor: Colors.yellow,
//icon:Image(image:AssetImage("assets/images/dora.png"))
);
}
});
} catch(e ){
print('Login error: $e');
Get.snackbar('Login Failed', 'An error occurred. Please try again.');
}
}
// 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。
//
void changePassword(
String oldpassword, String newpassword, BuildContext context) {
String token = currentUser[0]['token'];
////print("------- change password ######## ${currentUser[0]['token']} ###### --------");
AuthService.changePassword(oldpassword, newpassword, token).then((value) {
////print("------- change password ######## $value ###### --------");
if (value.isNotEmpty) {
logManager.addOperationLog("User successed to change password : $oldpassword , $newpassword.");
isLoading.value = false;
Navigator.pop(context);
if (rogMode.value == 1) {
switchPage(AppPages.TRAVEL);
} else {
switchPage(AppPages.INDEX);
}
} else {
logManager.addOperationLog("User failed to change password : $oldpassword , $newpassword.");
Get.snackbar(
'failed'.tr,
'password_change_failed_please_try_again'.tr,
backgroundColor: Colors.red,
colorText: Colors.white,
icon: const Icon(Icons.error, size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: const Duration(milliseconds: 800),
//backgroundColor: Colors.yellow,
//icon:Image(image:AssetImage("assets/images/dora.png"))
);
}
});
isLoading.value = false;
}
/*
void logout() async {
locations.clear();
DatabaseHelper db = DatabaseHelper.instance;
db.deleteAllDestinations().then((value) {
DestinationController destinationController =
Get.find<DestinationController>();
destinationController.populateDestinations();
});
currentUser.clear();
cats.clear();
// ユーザートークンをデバイスから削除
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.remove("user_token");
}
*/
Future<void> logout() async {
logManager.addOperationLog("User logout : $currentUser .");
saveGameState();
locations.clear();
DatabaseHelper db = DatabaseHelper.instance;
db.deleteAllDestinationsExceptTodayCheckins().then((value) {
DestinationController destinationController =
Get.find<DestinationController>();
destinationController.populateDestinations();
});
currentUser.clear();
cats.clear();
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.remove("user_token");
}
// 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。
//
void register(String email, String password, String password2, BuildContext context) {
AuthService.register(email, password,password2).then((value) {
if (value.containsKey("error")) {
String errMessage = value["error"];
debugPrint("ユーザー登録失敗:$email, $password,$errMessage");
logManager.addOperationLog("User failed to register new account : $email , $password ,$errMessage.");
isLoading.value = false;
Get.snackbar(
'user_registration_failed_please_try_again'.tr, // ユーザー登録に失敗しました。
errMessage,
backgroundColor: Colors.red,
colorText: Colors.white,
icon: const Icon(Icons.error, size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
//backgroundColor: Colors.yellow,
//icon:Image(image:AssetImage("assets/images/dora.png"))
);
}else{
debugPrint("ユーザー登録成功:$email, $password");
logManager.addOperationLog("User tried to register new account : $email , $password .");
currentUser.clear();
//currentUser.add(value);
isLoading.value = false;
// ユーザー登録成功メッセージを表示
Get.snackbar(
'success'.tr,
'user_registration_successful'.tr,
backgroundColor: Colors.green,
colorText: Colors.white,
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
);
//Navigator.pop(context);
Get.toNamed(AppPages.LOGIN);
}
});
}
void saveToDevice(String val) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString("user_token", val);
print("saveToDevice: $val");
}
/*
void changeUser(Map<String, dynamic> value, {bool replace = true}) {
print("---- change user to $value -----");
currentUser.clear();
currentUser.add(value);
if (replace) {
saveToDevice(currentUser[0]["token"]);
}
isLoading.value = false;
loadLocationsBound();
if (currentUser.isNotEmpty) {
rogMode.value = 0;
} else {
rogMode.value = 1;
}
print('--- c rog mode --- ${rogMode.value}');
Get.toNamed(AppPages.INDEX);
}
*/
Future<void> changeUser(Map<String, dynamic> value, {bool replace = true}) async{
try {
if (value['user'] == null || value['token'] == null) {
throw Exception('Invalid user data');
}
currentUser.clear();
currentUser.add(value);
if (replace) {
saveToDevice(currentUser[0]["token"]);
}
isLoading.value = false;
// ユーザーのイベント情報を取得
await fetchUserEventInfo();
loadLocationsBound(currentUser[0]["user"]["event_code"]);
if (currentUser.isNotEmpty) {
rogMode.value = 0;
restoreGame();
// チームデータを取得
await fetchTeamData();
} else {
rogMode.value = 1;
}
Get.toNamed(AppPages.INDEX);
} catch( e ){
print('Error in changeUser: $e');
Get.snackbar('Error', 'Failed to update user information');
}
}
Future<bool> checkUserInfoComplete() async {
try {
final user = await ApiService.to.getCurrentUser();
return user.firstname.isNotEmpty &&
user.lastname.isNotEmpty &&
user.dateOfBirth != null;
} catch (e) {
print('Error checking user info: $e');
return false;
}
}
Future<void> fetchUserEventInfo() async {
try {
final List<Entry> entries = await _apiService.getEntries();
if (entries.isNotEmpty) {
final Entry latestEntry = entries.last;
final tokyo = tz.getLocation('Asia/Tokyo');
final eventDate = latestEntry.date!.toUtc();
//final eventDate = tz.TZDateTime.from(utcDate, tokyo);
final eventDateOnly = tz.TZDateTime(tokyo, eventDate.year, eventDate.month, eventDate.day);
currentUser[0]['user']['event_date'] = eventDateOnly.toIso8601String().split('T')[0];
currentUser[0]['user']['event_code'] = latestEntry.event.eventName;
currentUser[0]['user']['team_name'] = latestEntry.team.teamName;
currentUser[0]['user']['group'] = latestEntry.team.category.categoryName;
currentUser[0]['user']['zekken_number'] = latestEntry.zekkenNumber;
// 最後のゴール日時を取得
final lastGoalTime = await getLastGoalTime();
currentUser[0]['user']['last_goal_time'] = lastGoalTime?.toIso8601String();
print('Updated user event info: ${currentUser[0]['user']}');
} else {
print('No entries found for the user');
_clearUserEventInfo();
}
} catch (e) {
print('Error fetching user event info: $e');
_clearUserEventInfo();
}
}
Future<DateTime?> getLastGoalTime() async {
try {
final userId = currentUser[0]['user']['id'];
return await _apiService.getLastGoalTime(userId);
} catch (e) {
print('Error getting last goal time: $e');
}
return null;
}
void _clearUserEventInfo() {
currentUser[0]['user']['event_date'] = null;
currentUser[0]['user']['event_code'] = null;
currentUser[0]['user']['team_name'] = null;
currentUser[0]['user']['group'] = null;
currentUser[0]['user']['zekken_number'] = null;
}
Future<void> fetchTeamData() async {
try {
Get.put(TeamController(apiService:Get.find<ApiService>()));
// \"TeamController\" not found. You need to call \"Get.put(TeamController())\" or \"Get.lazyPut(()=>TeamController())\"
final teamController = Get.find<TeamController>();
await teamController.fetchTeams();
if (teamController.teams.isNotEmpty) {
teamId.value = teamController.teams.first.id;
}
} catch (e) {
print("Error fetching team data: $e");
}
}
loadUserDetailsForToken(String token) async {
AuthService.userForToken(token).then((value) {
print("----token val-- $value ------");
if (value![0]["user"].isEmpty) {
Get.toNamed(AppPages.LOGIN);
return;
}
changeUser(value[0], replace: false);
});
}
/* Old code
void loadLocationsBound() {
if (isCustomAreaSelected.value == true) {
return;
}
locations.clear();
String cat = currentCat.isNotEmpty ? currentCat[0] : "";
if (currentCat.isNotEmpty && currentCat[0] == "-all-") {
cat = "";
}
LatLngBounds bounds = mapController.bounds!;
currentBound.clear();
currentBound.add(bounds);
////print(currentCat);
LocationService.loadLocationsBound(
bounds.southWest.latitude,
bounds.southWest.longitude,
bounds.northWest.latitude,
bounds.northWest.longitude,
bounds.northEast.latitude,
bounds.northEast.longitude,
bounds.southEast.latitude,
bounds.southEast.longitude,
cat)
.then((value) {
////print("---value length ------ ${value!.collection.length}");
if (value == null) {
return;
}
if (value.features.isEmpty) {
if (showPopup == false) {
return;
}
Get.snackbar(
"Too many Points",
"please zoom in",
icon: const Icon(Icons.assistant_photo_outlined,
size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 2),
backgroundColor: Colors.yellow,
//icon:Image(image:AssetImage("assets/images/dora.png"))
);
showPopup = false;
//Get.showSnackbar(GetSnackBar(message: "Too many points, please zoom in",));
}
if (value.features.isNotEmpty) {
////print("---- added---");
locations.add(value);
}
});
}
*/
// 2024-04-03 Akira .. Update the code . See ticket 2800.
//
// 2024-4-8 Akira : See 2809
// IndexControllerクラスでは、Future.delayedの呼び出しをunawaitedで囲んで、
// 非同期処理の結果を待たずに先に進むようにしました。これにより、メモリリークを防ぐことができます
//
// 要検討Future.delayedを使用して非同期処理を待たずに先に進むようにしていますが、
// これによってメモリリークが発生する可能性があります。非同期処理の結果を適切に処理することを検討してください。
//
Future<void> loadLocationsBound(String eventCode) async {
if (isCustomAreaSelected.value == true) {
return;
}
try {
// MapControllerの初期化が完了するまで待機
await waitForMapControllerReady();
// null チェックを追加
if (mapController.bounds == null) {
print("MapController bounds are null");
return;
}
// バウンドが有効かどうかを確認する
LatLngBounds bounds = mapController.bounds!;
if (!_isValidBounds(bounds)) {
print("MapController bounds are not valid");
return;
}
locations.clear();
String cat = currentCat.isNotEmpty ? currentCat[0] : "";
if (currentCat.isNotEmpty && currentCat[0] == "-all-") {
cat = "";
}
/*
// Akira Add 2024-4-6
if( mapController.controller == null ) {
print("操作が完了する前にMapControllerまたはウィジェットが破棄されました。");
isLoading.value = true; // ローディング状態をtrueに設定
return;
}
//
*/
//LatLngBounds bounds = mapController.bounds!;
currentBound.clear();
currentBound.add(bounds);
//isLoading.value = true; // ローディング状態をtrueに設定
//print("bounds --- (${bounds.southWest.latitude},${bounds.southWest.longitude}),(${bounds.northWest.latitude},${bounds.northWest.longitude}),(${bounds.northEast.latitude},${bounds.northEast.longitude}),(${bounds.southEast.latitude},${bounds.southEast.longitude})");
// 要検討APIからのレスポンスがnullの場合のエラーハンドリングが不十分です。適切なエラーメッセージを表示するなどの処理を追加してください。
try {
final eventCode = currentUser[0]["user"]["event_code"];
final value = await LocationService.loadLocationsBound(
bounds.southWest.latitude,
bounds.southWest.longitude,
bounds.northWest.latitude,
bounds.northWest.longitude,
bounds.northEast.latitude,
bounds.northEast.longitude,
bounds.southEast.latitude,
bounds.southEast.longitude,
cat,
eventCode
);
/*
if (value == null) {
// APIからのレスポンスがnullの場合
print("LocationService.loadLocationsBound からの回答がnullのため、マップをリロード");
DestinationController destinationController = Get.find<DestinationController>(); // 追加
final tk = currentUser[0]["token"]; // 追加
if (tk != null) { // 追加
destinationController.fixMapBound(tk); // 追加
} // 追加
return;
}
*/
isLoading.value = false; // ローディング状態をfalseに設定
if (value == null) {
// APIからのレスポンスがnullの場合
print("LocationService.loadLocationsBound からの回答がnullです");
} else {
if (value.features.isEmpty) {
if (showPopup == false) {
return;
}
Get.snackbar(
"Too many Points",
"please zoom in",
backgroundColor: Colors.yellow,
colorText: Colors.white,
icon: const Icon(Icons.assistant_photo_outlined, size: 40.0,
color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
);
showPopup = false;
}
if (value.features.isNotEmpty) {
locations.add(value);
}
}
/*
if (value != null && value.features.isEmpty) {
if (showPopup == false) {
return;
}
Get.snackbar(
"Too many Points",
"please zoom in",
backgroundColor: Colors.yellow,
colorText: Colors.white,
icon: const Icon(
Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
//backgroundColor: Colors.yellow,
);
showPopup = false;
}
if (value != null && value.features.isNotEmpty) {
locations.add(value);
}
*/
} catch (e) {
print("Error in loadLocationsBound: $e");
// エラーが発生した場合のリトライ処理や適切なエラーメッセージの表示を行う
// 例えば、一定時間後に再度loadLocationsBound()を呼び出すなど
}
} catch (e) {
print('Error in loadLocationsBound: $e');
//Get.snackbar('エラー', '位置情報の読み込みに失敗しました。もう一度お試しください。');
}
}
// バウンドが有効かどうかを確認するヘルパーメソッド
bool _isValidBounds(LatLngBounds bounds) {
// 緯度と経度が有効な範囲内にあるかチェック
return bounds.southWest.latitude.abs() <= 90 &&
bounds.southWest.longitude.abs() <= 180 &&
bounds.northEast.latitude.abs() <= 90 &&
bounds.northEast.longitude.abs() <= 180 &&
bounds.southWest.latitude < bounds.northEast.latitude;
}
static const int maxRetries = 5;
int retryCount = 0;
//===Akira 追加:2024-4-6 #2800
// 要検討MapControllerの初期化が完了するまで待機していますが、タイムアウトを設定することを検討してください。
// 初期化に時間がかかりすぎる場合、ユーザーにわかりやすいメッセージを表示するようにしてください。
//
Future<void> waitForMapControllerReady() async {
if (isMapControllerReady.value) return;
//await isMapControllerReady.firstWhere((value) => value == true);
const timeout = Duration(seconds: 30);
try {
await Future.any([
mapControllerCompleter.future,
Future.delayed(timeout).then((_) => throw TimeoutException('MapController initialization timed out')),
]);
isMapControllerReady.value = true;
} catch (e) {
print('Warning waiting for MapController: $e');
// タイムアウト後もマップが機能している場合は、強制的に準備完了とマーク
if (!isMapControllerReady.value) {
isMapControllerReady.value = true;
if (!mapControllerCompleter.isCompleted) {
mapControllerCompleter.complete();
}
}
}
}
//===Akira 追加:2024-4-6 #2800
void retryMapInitialization() {
// マップの再初期化ロジック
initMapController();
// 他の必要な再初期化処理
}
void setBound(LatLngBounds bounds) {
currentBound.clear();
currentBound.add(bounds);
}
GeoJSONFeature? getFeatureForLatLong(double lat, double long) {
if (locations.isNotEmpty) {
GeoJSONFeature? foundFeature;
for (var i in locations[0].features) {
GeoJSONMultiPoint p = i!.geometry as GeoJSONMultiPoint;
if (p.coordinates[0][1] == lat && p.coordinates[0][0] == long) {
foundFeature = i;
}
}
return foundFeature;
}
return null;
}
void reloadMap( String eventCode ) {
// マップをリロードするロジックを実装
// 例: 現在の位置情報を再取得し、マップを更新する
loadLocationsBound( eventCode );
}
Future<void> checkEntryData() async {
// エントリーデータの有無をチェックするロジック
final teamController = TeamController(apiService:Get.find<ApiService>());
bool hasEntryData = teamController.checkIfUserHasEntryData();
if (!hasEntryData) {
await showHelperDialog(
'イベントに参加するには、チーム登録・メンバー登録及びエントリーが必要になります。',
'entry_check'
);
// ドロワーを表示するロジック
Get.toNamed('/drawer');
}
}
void updateCurrentUser(User updatedUser) {
currentUser[0]['user'] = updatedUser.toJson();
update();
}
}