Files
rog_app/lib/pages/index/index_controller.dart
2024-08-05 03:08:12 +09:00

846 lines
28 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 '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:get/get.dart';
import 'package:latlong2/latlong.dart';
import 'package:rogapp/model/destination.dart';
import 'package:rogapp/model/entry.dart';
import 'package:rogapp/pages/destination/destination_controller.dart';
import 'package:rogapp/pages/team/team_controller.dart';
import 'package:rogapp/routes/app_pages.dart';
import 'package:rogapp/services/auth_service.dart';
import 'package:rogapp/services/location_service.dart';
import 'package:rogapp/utils/database_helper.dart';
import 'package:rogapp/widgets/debug_widget.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:rogapp/services/api_service.dart';
import 'package:rogapp/model/user.dart';
import 'package:rogapp/main.dart';
import 'package:rogapp/widgets/helper_dialog.dart';
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;
RxBool isMapControllerReady = RxBool(false); // MapControllerの初期化状態を管理するフラグ
//final mapControllerReadyStream = Stream<bool>.value(false); // MapControllerの初期化状態を通知するためのストリーム
MapController mapController = MapController();
MapController rogMapController = MapController();
LogManager logManager = LogManager();
String? userToken;
//late final ApiService _apiService;
final ApiService _apiService = Get.find<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;
void setSelectedEventName(String eventName) {
selectedEventName.value = eventName;
}
ConnectivityResult connectionStatus = ConnectivityResult.none;
var connectionStatusName = "".obs;
final Connectivity _connectivity = Connectivity();
late StreamSubscription<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() {
_connectivitySubscription =
_connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
super.onInit();
WidgetsBinding.instance?.addObserver(this);
_startLocationService(); // アプリ起動時にLocationServiceを開始する
initializeApiService();
print('IndexController onInit called'); // デバッグ用の出力を追加
//teamController = Get.find<TeamController>();
}
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 {
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}'.");
}
}
void _stopLocationService() async {
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}'.");
}
}
/*
@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> _updateConnectionStatus(ConnectivityResult result) async {
connectionStatus = result;
connectionStatusName.value = result.name;
}
Future<void> initConnectivity() async {
late ConnectivityResult result;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
result = await _connectivity.checkConnectivity();
} 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!));
}
// 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。
//
void login(String email, String password, BuildContext context) async{
AuthService.login(email, password).then((value) async {
print("------- logged in user details ######## $value ###### --------");
if (value.isNotEmpty) {
logManager.addOperationLog("User logged in : ${value}.");
// Navigator.pop(context);
print("--------- user details login ----- $value");
changeUser(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"))
);
}
});
}
Future<bool> checkUserInfoComplete() async {
final user = await ApiService.to.getCurrentUser();
return user.firstname.isNotEmpty &&
user.lastname.isNotEmpty &&
user.dateOfBirth != null &&
user.female != null;
}
// 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。
//
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");
}
*/
void logout() async {
logManager.addOperationLog("User logout : ${currentUser} .");
saveGameState();
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");
}
// 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。
//
void register(String email, String password, String password2, BuildContext context) {
AuthService.register(email, password,password2).then((value) {
if (value.isNotEmpty) {
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.INDEX);
} else {
debugPrint("ユーザー登録失敗:${email}, ${password}");
logManager.addOperationLog("User failed to register new account : ${email} , ${password} .");
isLoading.value = false;
Get.snackbar(
'failed'.tr, // 失敗
'user_registration_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(seconds: 3),
//backgroundColor: Colors.yellow,
//icon:Image(image:AssetImage("assets/images/dora.png"))
);
}
});
}
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);
}
*/
void changeUser(Map<String, dynamic> value, {bool replace = true}) async{
currentUser.clear();
currentUser.add(value);
if (replace) {
saveToDevice(currentUser[0]["token"]);
}
isLoading.value = false;
loadLocationsBound( currentUser[0]["user"]["event_code"]);
if (currentUser.isNotEmpty) {
rogMode.value = 0;
restoreGame();
// チームデータを取得
await fetchTeamData();
} else {
rogMode.value = 1;
}
Get.toNamed(AppPages.INDEX);
}
Future<void> fetchTeamData() async {
try {
Get.put(TeamController());
// \"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を使用して非同期処理を待たずに先に進むようにしていますが、
// これによってメモリリークが発生する可能性があります。非同期処理の結果を適切に処理することを検討してください。
//
void loadLocationsBound(String eventCode) async {
if (isCustomAreaSelected.value == true) {
return;
}
// MapControllerの初期化が完了するまで待機
await waitForMapControllerReady();
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!;
if (bounds == null) {
// MapControllerの初期化が完了していない場合は処理を行わない
return;
}
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()を呼び出すなど
}
}
//===Akira 追加:2024-4-6 #2800
// 要検討MapControllerの初期化が完了するまで待機していますが、タイムアウトを設定することを検討してください。
// 初期化に時間がかかりすぎる場合、ユーザーにわかりやすいメッセージを表示するようにしてください。
//
Future<void> waitForMapControllerReady() async {
if (!isMapControllerReady.value) {
await Future.doWhile(() async {
await Future.delayed(const Duration(milliseconds: 100));
return !isMapControllerReady.value;
});
}
}
//===Akira 追加:2024-4-6 #2800
void setBound(LatLngBounds bounds) {
currentBound.clear();
currentBound.add(bounds);
}
GeoJSONFeature? getFeatureForLatLong(double lat, double long) {
if (locations.isNotEmpty) {
GeoJSONFeature? foundFeature;
locations[0].features.forEach((i) {
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();
bool hasEntryData = teamController.checkIfUserHasEntryData();
if (!hasEntryData) {
await showHelperDialog(
'イベントに参加するには、チーム登録・メンバー登録及びエントリーが必要になります。',
'entry_check'
);
// ドロワーを表示するロジック
Get.toNamed('/drawer');
}
}
void updateCurrentUser(User updatedUser) {
currentUser[0]['user'] = updatedUser.toJson();
update();
}
}