Files
rog_app/lib/pages/index/index_controller.dart

694 lines
23 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/pages/destination/destination_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:shared_preferences/shared_preferences.dart';
import '../../main.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();
String? userToken;
// 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-";
ConnectivityResult connectionStatus = ConnectivityResult.none;
var connectionStatusName = "".obs;
final Connectivity _connectivity = Connectivity();
late StreamSubscription<ConnectivityResult> _connectivitySubscription;
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('位置情報の許可が必要です'),
content: Text('設定>プライバシーとセキュリティ>位置情報サービス を開いて、岐阜ナビを探し、「位置情報の許可」を「常に」にして下さい。'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('OK'),
),
],
),
);
},
);
} 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を開始する
print('IndexController onInit called'); // デバッグ用の出力を追加
}
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 {
await platform.invokeMethod('startLocationService');
} on PlatformException catch (e) {
print("Failed to start location service: '${e.message}'.");
}
}
void _stopLocationService() async {
const platform = MethodChannel('location');
try {
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;
}
}
return LatLngBounds(LatLng(x1!, y1!), LatLng(x0!, y0!));
}
// 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。
//
void login(String email, String password, BuildContext context) {
AuthService.login(email, password).then((value) {
print("------- logged in user details ######## $value ###### --------");
if (value.isNotEmpty) {
// Navigator.pop(context);
print("--------- user details login ----- $value");
//await Future.delayed(const Duration(milliseconds: 500)); // Added Akira:2024-4-6, #2800
changeUser(value);
} else {
isLoading.value = false;
Get.snackbar(
"ログイン失敗",
"ログインIDかパスワードを確認して下さい。",
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 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) {
isLoading.value = false;
Navigator.pop(context);
if (rogMode.value == 1) {
switchPage(AppPages.TRAVEL);
} else {
switchPage(AppPages.INDEX);
}
} else {
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 {
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, BuildContext context) {
AuthService.register(email, password).then((value) {
if (value.isNotEmpty) {
currentUser.clear();
currentUser.add(value);
isLoading.value = false;
Navigator.pop(context);
Get.toNamed(AppPages.INDEX);
} else {
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(milliseconds: 800),
//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}) {
currentUser.clear();
currentUser.add(value);
if (replace) {
saveToDevice(currentUser[0]["token"]);
}
isLoading.value = false;
loadLocationsBound();
if (currentUser.isNotEmpty) {
rogMode.value = 0;
restoreGame();
} else {
rogMode.value = 1;
}
Get.toNamed(AppPages.INDEX);
}
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() 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 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
);
/*
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;
}
}