Files
rog_app/lib/pages/destination/destination_controller.dart

1459 lines
53 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:io';
import 'dart:typed_data';
import 'package:camera_camera/camera_camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_location_marker/flutter_map_location_marker.dart';
import 'package:geojson_vi/geojson_vi.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:latlong2/latlong.dart';
import 'package:rogapp/main.dart';
import 'package:rogapp/model/destination.dart';
import 'package:rogapp/model/gps_data.dart';
import 'package:rogapp/pages/camera/camera_page.dart';
import 'package:rogapp/pages/index/index_controller.dart';
import 'package:rogapp/routes/app_pages.dart';
import 'package:rogapp/services/DatabaseService.dart';
import 'package:rogapp/services/destination_service.dart';
import 'package:rogapp/services/external_service.dart';
import 'package:rogapp/services/location_service.dart';
import 'package:rogapp/services/maxtrix_service.dart';
import 'package:rogapp/services/perfecture_service.dart';
import 'package:rogapp/utils/database_gps.dart';
import 'package:rogapp/utils/database_helper.dart';
import 'package:rogapp/utils/location_controller.dart';
import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_normal_point.dart';
import 'dart:async';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'package:rogapp/widgets/debug_widget.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart';
// 目的地に関連する状態管理とロジックを担当するクラスです。
//
class DestinationController extends GetxController {
late LocationSettings locationSettings; // 位置情報の設定を保持する変数です。
//Timer? _GPStimer; // GPSタイマーを保持する変数です。
var destinationCount = 0.obs; // 目的地の数を保持するReactive変数です。
List<Destination> destinations = <Destination>[].obs; // 目的地のリストを保持するObservable変数です。
double currentLat = 0.0; // 現在の緯度と経度を保持する変数です。
double currentLon = 0.0;
double lastValidLat = 0.0; // 最後に中・強信号で拾ったGPS位置。
// ロゲ開始を屋内でやったら 0 のままなので、屋外で行うこと。
double lastValidLon = 0.0;
DateTime lastGPSCollectedTime = DateTime.now(); // 最後にGPSデータが収集された時刻を保持する変数です。
bool shouldShowBottomSheet = true; // ボトムシートを表示すべきかどうかを示すフラグです。
static bool gps_push_started = false; // ゲームの状態を示す静的変数です。
static bool game_started = false;
static bool ready_for_goal = false;
bool skip_10s = false; // 10秒間のスキップフラグを示す変数です。
List<Destination> currentSelectedDestinations = <Destination>[].obs; // 現在選択されている目的地のリストを保持するObservable変数です。
var isInCheckin = false.obs; // ゲームの状態を示すReactive変数です。
var isInRog = false.obs;
var isAtStart = false.obs;
var isAtGoal = false.obs;
var isPhotoShoot = false.obs;
DateTime lastGoalAt = DateTime.now().subtract(const Duration(days: 1)); // 最後にゴールした時刻を保持する変数です。
//List<Rogaining> rogainings = <Rogaining>[].obs;
bool checkingIn = false; // チェックイン中かどうかを示すフラグです。
var isGpsSelected = true.obs; // GPSが選択されているかどうかを示すReactive変数です。
BuildContext? context; // ビルドコンテキストを保持する変数です。
List<String> gps = <String>["-- stating --"].obs; // GPSと位置情報の許可に関する情報を保持するObservable変数です。
List<String> locationPermission = <String>[" -- starting -- "].obs;
var travelMode = 0.obs; // 移動モードを保持するReactive変数です。
bool skipGps = false; // GPSをスキップするかどうかを示すフラグです。
bool okToUseGPS = false; // 最新のGPS情報を使用して良いかを示すフラグ。
Map<String, dynamic> matrix = {}; // 行列データを保持する変数です。
final photos = <File>[].obs; // 写真のリストを保持するReactive変数です。
final IndexController indexController = Get.find<IndexController>(); // IndexControllerのインスタンスを保持する変数です。
final LocationController locationController = Get.put(LocationController()); // LocationControllerのインスタンスを保持する変数です。
final DatabaseService dbService = DatabaseService(); // DatabaseServiceのインスタンスを保持する変数です。
int _start = 0; // 開始時刻を保持する変数です。
int chekcs = 0; // チェックポイントの数を保持する変数です。
var rogainingCounted = false.obs; // ロゲイニングがカウントされたかどうかを示すReactive変数です。
// destinationController.rogainingCountedは、現在のロゲイニングセッションでポイントがカウントされたかどうかを管理するフラグです。
//
// このフラグは以下のような状況で使用されます:
//
// ロゲイニングを開始したとき、rogainingCountedはfalseに初期化されます。これは、まだポイントがカウントされていないことを示します。
// チェックポイントに到着し、チェックインが成功したとき、rogainingCountedはtrueに設定されます。これは、そのセッションでポイントがカウントされたことを示します。
// ロゲイニングを終了したとき、rogainingCountedは再びfalseに設定されます。これは、次のセッションに備えてフラグをリセットするためです。
// このフラグは、主に以下の目的で使用されます:
//
// ゴール地点でのロジックの制御rogainingCountedがtrueの場合、つまりポイントがカウントされている場合にのみ、ゴール処理を実行できます。
// UI の更新rogainingCountedの状態に基づいて、適切なメッセージやボタンを表示することができます。
/*
//==== Akira .. GPS信号シミュレーション用 ===== ここから、2024-4-5
//
bool kDebugMode = true;
// シミュレーションモードのフラグ
RxBool isSimulationMode = RxBool(true);
// シミュレーションモードを切り替えるための関数
void toggleSimulationMode(bool value) {
isSimulationMode.value = value;
}
// 現在位置の取得メソッドを追加
LatLng getCurrentLocation() {
return LatLng(lastValidLat, lastValidLon);
}
//
// GPS信号の強弱を判断するメソッドを追加します。
//
String getGpsSignalStrength() {
// デバッグモードかつシミュレーションモードの場合は、シミュレートされた信号強度を返す
print("kDebugMode : ${kDebugMode}, isSimulationMode : ${isSimulationMode.value}");
if (kDebugMode && isSimulationMode.value) {
return locationController.getSimulatedSignalStrength();
}
// 通常モードの場合は、実際の信号強度を返す
final accuracy = locationController.currentPosition.value?.accuracy ?? double.infinity;
if (accuracy <= 10) {
return 'high';
} else if (accuracy <= 30) {
return 'medium';
} else {
return 'low';
}
}
//
//==== Akira .. GPS信号シミュレーション用 ======= ここまで
*/
// 日時をフォーマットされた文字列に変換する関数です。
//
String getFormatedTime(DateTime datetime) {
return DateFormat('yyyy-MM-dd HH:mm:ss').format(datetime);
}
// 追加Akira 2024-4-5
// GPS信号の精度が一定値以上の場合、GPS信号が弱いと判断する
//
bool isGpsSignalWeak() {
final accuracy = locationController.currentPosition.value?.accuracy;
if (accuracy == null) {
return true; // 位置情報が取得できていない場合、GPS信号が弱いと見なす
}
return accuracy > 50;
//return locationController.currentPosition.value?.accuracy ?? double.infinity > 50;
}
//
Destination festuretoDestination(GeoJSONFeature fs) {
GeoJSONMultiPoint mp = fs.geometry as GeoJSONMultiPoint;
LatLng pt = LatLng(mp.coordinates[0][1], mp.coordinates[0][0]);
//print("----- ${indexController.currentFeature[0].properties} -----");
return Destination(
name: fs.properties!["location_name"],
sub_loc_id: fs.properties!["sub_loc_id"],
address: fs.properties!["address"],
phone: fs.properties!["phone"],
email: fs.properties!["email"],
webcontents: fs.properties!["webcontents"],
videos: fs.properties!["videos"],
category: fs.properties!["category"],
series: 1,
lat: pt.latitude,
lon: pt.longitude,
location_id: fs.properties!["location_id"],
list_order: 1,
photos: fs.properties!["photos"],
checkin_radious: fs.properties!["checkin_radius"],
auto_checkin: fs.properties!["auto_checkin"] == true ? 1 : 0,
cp: fs.properties!["cp"],
checkin_point: fs.properties!["checkin_point"],
buy_point: fs.properties!["buy_point"],
selected: false,
checkedin: false,
hidden_location: fs.properties!["hidden_location"] == true ? 1 : 0,
tags: fs.properties!["tags"]);
}
// 指定された目的地の位置情報に基づいてタイマーを開始する関数です。
//
Future<void> startTimerLocation(GeoJSONFeature fs, double distance) async {
//print("---- in startTimer ----");
// print("---- is in rog is $is_in_rog ----");
double checkinRadious = fs.properties!['checkin_radius'] ?? double.infinity;
if (checkinRadious >= distance) {
indexController.currentFeature.clear();
Destination d = festuretoDestination(fs);
// print("----- destination lenght is ${destinations.length} -----");
indexController.currentFeature.add(fs);
//print("---- before calling startTimer ----");
await startTimer(d, distance);
return;
}
}
// 指定された目的地に対してタイマーを開始する関数です。
// 目的地の位置情報を取得し、チェックイン半径内にいるかどうかを確認します。
// 写真撮影モードの場合は、ボトムシートを表示して写真撮影を行います。
// 目的地がデータベースに存在しない場合は、新しい目的地としてデータベースに挿入します。
// 目的地に応じて、チェックイン、ゴール、買い物ポイントの処理を行います。
//
// 2024-4-8 akira: GPS信号が弱い場合でも、最後に取得した位置情報を使用してチェックインやゴールの処理を続行できるようになります。また、チェックインやゴールの処理では、GPS信号の精度チェックを緩和することで、GPS信号が弱い場合でもボタンを押せるようになります。
//
// 要検討:エラーが発生した場合のエラーハンドリングを追加し、適切なメッセージを表示することを検討してください。
//
Future<void> startTimer(Destination d, double distance) async {
//print("=== passed dest is ${d.location_id} ${d.checkedin} ====");
skipGps = true;
//print("---- in startTimer ----");
DatabaseHelper db = DatabaseHelper.instance;
List<Destination> ds = await db.getDestinationByLatLon(d.lat!, d.lon!);
Destination? dss;
if (ds.isNotEmpty) {
dss = ds.first;
}
double checkinRadious = d.checkin_radious ?? double.infinity;
bool autoCheckin = d.auto_checkin == 0 ? false : true;
bool buyPoint = dss != null && dss.buy_point != null && dss.buy_point! > 0
? true
: false;
bool buyPointImageAdded =
dss != null && dss.buypoint_image != null ? true : false;
bool buyPointCanceled =
dss != null && dss.buy_point != null && dss.buy_point == 0
? true
: false;
bool locationAlreadyCheckedIn =
ds.isNotEmpty && ds[0].checkedin == true ? true : false;
bool isuserLoggedIn = indexController.currentUser.isNotEmpty ? true : false;
//make current destination
// print("---- checkin_radious $checkinRadious ----");
// print("---- distance $distance ----");
if (checkinRadious >= distance || checkinRadious == -1) {
//currentSelectedDestinations.add(d);
indexController.currentDestinationFeature.clear();
indexController.currentDestinationFeature.add(d);
// print(
// "---- checked in as ${indexController.currentDestinationFeature[0].checkedin.toString()} ----");
} else {
// GPS信号が弱い場合でも、チェックインやゴールの処理を続行する
// comment out by Akira, 2024-4-5
// skipGps = false;
// return;
// GPS信号が弱い場合、最後に取得した高いまたは中程度の位置情報を使用
if (okToUseGPS) {
double lastValidDistance = Geolocator.distanceBetween(
lastValidLat, lastValidLon,
d.lat!, d.lon!
);
/*
double lastValidDistance = distance.as(
LengthUnit.Meter,
LatLng(lastValidLat, lastValidLon),
LatLng(d.lat!, d.lon!),
);
*/
if (checkinRadious >= lastValidDistance || checkinRadious == -1) {
indexController.currentDestinationFeature.clear();
indexController.currentDestinationFeature.add(d);
} else {
skipGps = false;
return;
}
} else {
skipGps = false;
return;
}
}
if (isPhotoShoot.value == true) {
photos.clear();
if (shouldShowBottomSheet) {
shouldShowBottomSheet = false;
if (d.cp == -1) return;
await showModalBottomSheet(
constraints:
BoxConstraints.loose(Size(Get.width, Get.height * 0.75)),
context: Get.context!,
isScrollControlled: true,
builder: ((context) => CameraPage(destination: d)))
.whenComplete(() {
shouldShowBottomSheet = true;
skipGps = false;
chekcs = 0;
isInCheckin.value = false;
});
}
return;
}
if (ds.isEmpty) {
// print("----- in location popup cp - ${d.cp}----");
if (d.cp == -1 && DateTime.now().difference(lastGoalAt).inHours >= 24) {
chekcs = 1;
//start
// print("~~~~ calling start ~~~~");
// print("---- in start -----");
chekcs = 1;
isInCheckin.value = true;
isAtStart.value = true;
if (shouldShowBottomSheet) {
shouldShowBottomSheet = false;
if (d.cp == -1||d.cp==-2||d.cp==0) return;
Widget bottomSheet = BottomSheetNormalPoint(destination: d);
await showModalBottomSheet(
constraints:
BoxConstraints.loose(Size(Get.width, Get.height * 0.85)),
context: Get.context!,
isScrollControlled: true,
builder: ((context) => bottomSheet)
).whenComplete(() {
shouldShowBottomSheet = true;
skipGps = false;
chekcs = 0;
isAtStart.value = false;
isInCheckin.value = false;
});
}
return;
} else if (isInRog.value == true &&
indexController.rogMode.value == 1 &&
d.cp != -1) {
// print("----- in location popup checkin cp - ${d.cp}----");
chekcs = 2;
isInCheckin.value = true;
if (shouldShowBottomSheet) {
shouldShowBottomSheet = false;
if (d.cp == -1||d.cp==-2||d.cp==0) return;
Widget bottomSheet = BottomSheetNormalPoint(destination: d);
await showModalBottomSheet(
constraints:
BoxConstraints.loose(Size(Get.width, Get.height * 0.75)),
context: Get.context!,
isScrollControlled: true,
builder: ((context) => bottomSheet)
).whenComplete(() {
shouldShowBottomSheet = true;
skipGps = false;
chekcs = 0;
isInCheckin.value = false;
});
}
return;
}
}
// print("---- location checkin radious ${d.checkin_radious} ----");
// print("---- already checked in $locationAlreadyCheckedIn ----");
if ((checkinRadious >= distance || checkinRadious == -1) &&
locationAlreadyCheckedIn == false &&
isInRog.value == true) {
if (autoCheckin) {
if (!checkingIn) {
//print(
// "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ make checkin ${d.sub_loc_id}@@@@@@@@@@@");
makeCheckin(d, true, "");
if (d.cp != -1) {
rogainingCounted.value = true;
}
skipGps = false;
}
return;
} else {
// print("--- hidden loc ${d.hidden_location} ----");
// ask for checkin
if (d.hidden_location != null &&
d.hidden_location == 0 &&
isInRog.value == true &&
d.cp != -1) {
chekcs = 3;
isInCheckin.value = true;
photos.clear();
// print("--- calling checkin ---");
if (shouldShowBottomSheet) {
shouldShowBottomSheet = false;
if (d.cp == -1) return;
await showModalBottomSheet(
constraints:
BoxConstraints.loose(Size(Get.width, Get.height * 0.75)),
context: Get.context!,
isScrollControlled: true,
builder: ((context) => CameraPage(
destination: d,
))).whenComplete(() {
shouldShowBottomSheet = true;
skipGps = false;
rogainingCounted.value = true;
chekcs = 0;
isInCheckin.value = false;
});
}
return;
} else if (isInRog.value == true && d.cp != -1) {
chekcs = 4;
isInCheckin.value = true;
if (shouldShowBottomSheet) {
shouldShowBottomSheet = false;
if (d.cp == -1||d.cp==-2||d.cp==0) return;
Widget bottomSheet = BottomSheetNormalPoint(destination: d);
await showMaterialModalBottomSheet(
expand: true,
context: Get.context!,
backgroundColor: Colors.transparent,
builder: (context) => bottomSheet
).whenComplete(() {
shouldShowBottomSheet = true;
skipGps = false;
chekcs = 0;
isInCheckin.value = false;
});
}
return;
}
}
} else if ((checkinRadious >= distance || checkinRadious == -1) &&
locationAlreadyCheckedIn == true &&
buyPointImageAdded == false &&
ds.isNotEmpty &&
buyPoint == true &&
buyPointCanceled == false &&
isInRog.value == true) {
chekcs = 5;
isInCheckin.value = true;
photos.clear();
//print("--- open buy point $buyPointImageAdded ${d.buypoint_image} ----");
if (shouldShowBottomSheet) {
shouldShowBottomSheet = false;
if (d.cp == -1) return;
await showModalBottomSheet(
constraints:
BoxConstraints.loose(Size(Get.width, Get.height * 0.75)),
context: Get.context!,
isScrollControlled: true,
builder: ((context) => CameraPage(
destination: d,
buyPointPhoto: true,
dbDest: ds.first,
))).whenComplete(() {
shouldShowBottomSheet = true;
skipGps = false;
rogainingCounted.value = true;
chekcs = 0;
isInCheckin.value = false;
});
}
return;
}
// print("---- cp --- ${d.cp} -----");
// print("--- at goal $is_at_goal ---");
// print("--- rog counted $rogaining_counted ---");
// print("--- loc already checked in $locationAlreadyCheckedIn ---");
// print(
// "==== date diff is ${DateTime.now().difference(last_goal_at).inHours} ====");
if (isuserLoggedIn &&
d.cp == -1 &&
locationAlreadyCheckedIn &&
skip_10s == false) {
//check for rogaining
if (isAtGoal.value == false && rogainingCounted.value) {
//goal
//print("---- in goal -----");
chekcs = 5;
isAtGoal.value = true;
photos.clear();
if (shouldShowBottomSheet) {
shouldShowBottomSheet = false;
if (d.cp == -1) return;
await showModalBottomSheet(
constraints:
BoxConstraints.loose(Size(Get.width, Get.height * 0.75)),
context: Get.context!,
isScrollControlled: true,
builder: ((context) => CameraPage(
destination: d,
))).whenComplete(() {
shouldShowBottomSheet = true;
skipGps = false;
chekcs = 0;
isAtGoal.value = false;
});
}
return;
} else if (isInRog.value == false &&
indexController.rogMode.value == 1 &&
DateTime.now().difference(lastGoalAt).inHours >= 24) {
//start
//print("---- in start -----");
chekcs = 6;
isAtStart.value = true;
if (shouldShowBottomSheet) {
shouldShowBottomSheet = false;
if (d.cp == -1||d.cp==-2||d.cp==0) return;
Widget bottomSheet = BottomSheetNormalPoint(destination: d);
await showModalBottomSheet(
constraints:
BoxConstraints.loose(Size(Get.width, Get.height * 0.75)),
context: Get.context!,
isScrollControlled: true,
builder: ((context) => bottomSheet)
).whenComplete(() {
shouldShowBottomSheet = true;
//print("----- finished start -------");
skipGps = false;
chekcs = 0;
isAtStart.value = false;
});
}
return;
}
}
//print("==== _chekcs $chekcs ====");
if (chekcs == 0) {
skipGps = false;
}
return;
}
// ロゲイニングをリセットする関数です。
// ゲームの状態をリセットし、データベースからデータを削除します。
//
Future<void> resetRogaining({bool isgoal = false}) async {
//print("----- resetting --------");
isInCheckin.value = false;
isInRog.value = false;
isAtStart.value = false;
isAtGoal.value = false;
isGpsSelected.value = true;
skipGps = false;
ready_for_goal = false;
_start = 0;
chekcs = 0;
rogainingCounted.value = false;
DatabaseHelper db = DatabaseHelper.instance;
if (isgoal == false) {
await db.deleteAllDestinations();
await db.deleteAllRogaining();
}
int? latgoal = await db.latestGoal();
if (latgoal != null) {
lastGoalAt = DateTime.fromMicrosecondsSinceEpoch(latgoal);
//print("===== last goal : $last_goal_at =====");
}
dbService.updateDatabase();
}
// すべての目的地を削除する関数です。
//
void deleteAllDestinations() {
DatabaseHelper db = DatabaseHelper.instance;
db.deleteAllDestinations().then((value) {
populateDestinations();
});
}
// カメラを開いて写真を撮影する関数です。
//
void openCamera(BuildContext context, Destination? destination) {
photos.clear();
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => CameraCamera(
resolutionPreset: ResolutionPreset.medium,
onFile: (file) {
photos.add(file);
Navigator.pop(context);
//print("----image file is : $file----");
//setState(() {});
},
)));
}
// ルートポイントを取得する関数です。
//
void getRoutePoints() {
indexController.routePoints = [];
indexController.routePointLenght.value = 0;
DestinationService.getDestinationLine(destinations, matrix)?.then((value) {
indexController.routePoints = value;
indexController.routePointLenght.value =
indexController.routePoints.length;
});
}
// 指定された緯度と経度に対応する目的地を取得する関数です。
//
Future<Destination?> getDestinationForLatLong(double lat, double long) async {
for (final d in destinations) {
if (lat == d.lat && long == d.lon) {
return d;
}
}
return null;
}
// チェックインの呼び出しを行う関数です。
// 指定された目的地に対してチェックインの処理を行います。
//
Future<void> callforCheckin(Destination d) async {
bool autoCheckin = d.auto_checkin == 0 ? false : true;
//print("---- f- checkin ${d.sub_loc_id} ----");
if (autoCheckin) {
if (!checkingIn) {
makeCheckin(d, true, "");
if (d.cp != -1) {
rogainingCounted.value = true;
}
}
} else {
//print("--- hidden loc ${d.hidden_location} ----");
// ask for checkin
//print("is rog ---- ${is_in_rog.value} ----");
if (d.hidden_location != null &&
d.hidden_location == 0 &&
isInRog.value == true &&
d.cp != -1) {
chekcs = 3;
isInCheckin.value = true;
photos.clear();
await showModalBottomSheet(
constraints:
BoxConstraints.loose(Size(Get.width, Get.height * 0.75)),
context: Get.context!,
isScrollControlled: true,
builder: ((context) => CameraPage(
manulaCheckin: true,
destination: d,
))).whenComplete(() async {
if (d.buy_point != null && d.buy_point! > 0) {
skipGps = true;
photos.clear();
DatabaseHelper db = DatabaseHelper.instance;
List<Destination> ds =
await db.getDestinationByLatLon(d.lat!, d.lon!);
Destination? dss;
if (ds.isNotEmpty) {
dss = ds.first;
}
await showModalBottomSheet(
constraints:
BoxConstraints.loose(Size(Get.width, Get.height * 0.75)),
context: Get.context!,
isScrollControlled: true,
builder: ((context) => CameraPage(
buyPointPhoto: true,
destination: d,
dbDest: dss,
))).whenComplete(() {
skipGps = false;
rogainingCounted.value = true;
chekcs = 0;
isInCheckin.value = false;
Get.back();
});
} else {
skipGps = false;
chekcs = 0;
isInCheckin.value = false;
}
});
} else {
Get.snackbar(
"ロゲが始まっていません",
"ロゲ開始ボタンをタップして、ロゲイニングを始める必要があります",
icon: const Icon(
Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
backgroundColor: Colors.yellow,
);
}
}
}
// GPSデータをデータベースに追加する関数です。
//
Future<void> addGPStoDB(double la, double ln, {isCheckin = 0}) async {
//print("in addGPStoDB ${indexController.currentUser}");
try {
GpsDatabaseHelper db = GpsDatabaseHelper.instance;
final team_name = indexController.currentUser[0]["user"]['team_name'];
final event_code = indexController.currentUser[0]["user"]["event_code"];
GpsData gps_data = GpsData(
id: 0,
team_name: team_name,
event_code: event_code,
lat: la,
lon: ln,
is_checkin: isCheckin,
created_at: DateTime.now().millisecondsSinceEpoch);
var res = await db.insertGps(gps_data);
} catch (err) {
print("errr ready gps ${err}");
return;
}
}
// チェックインを確認する関数です。
// ゲームが開始されていない場合は、ゲームを開始します。
// 目的地のリストを走査し、現在位置がチェックイン半径内にある場合は、チェックインの処理を行います。
// GPSデータの送信を開始します。
//
// 2024-4-8 Akira : See 2809
// checkForCheckinメソッドの再帰呼び出しをunawaitedで囲んで、非同期処理の結果を待たずに先に進むようにしました。また、再帰呼び出しの前に一定時間待機するようにしました。
//
Future<void> checkForCheckin() async {
//print("--- Start of checkForCheckin function ---");
dbService.updateDatabase();
await Future.delayed(const Duration(milliseconds: 3000));
game_started = true;
try {
indexController.locations[0].features.forEach((fs) async {
GeoJSONMultiPoint mp = fs!.geometry as GeoJSONMultiPoint;
LatLng pt = LatLng(mp.coordinates[0][1], mp.coordinates[0][0]);
double latFs = pt.latitude;
double lonFs = pt.longitude;
var distanceFs = const Distance();
double distFs = distanceFs.as(LengthUnit.Meter, LatLng(latFs, lonFs),
LatLng(currentLat, currentLon));
Destination des = festuretoDestination(fs);
if (distFs <= des.checkin_radious! && skipGps == false) {
await startTimerLocation(fs, distFs);
// Note: You cannot break out of forEach. If you need to stop processing, you might have to reconsider using forEach.
}
});
if (gps_push_started == false) {
unawaited( pushGPStoServer() );
}
//print("--- 123 ---- $skip_gps----");
} catch (e) {
print("An error occurred: $e");
// await checkForCheckin();
} finally {
await Future.delayed(const Duration(seconds: 5)); // 一定時間待機してから再帰呼び出し
//print("--- End of checkForCheckin function, calling recursively ---");
unawaited( checkForCheckin() );
}
}
// GPSデータをサーバーにプッシュする関数です。
//
Future<void> pushGPStoServer() async {
// print(
// "^^^^^^^^ ${DateFormat('kk:mm:ss \n EEE d MMM').format(DateTime.now())}");
try {
gps_push_started = true;
ExternalService().pushGPS();
} catch (e) {
//print("An error occurred: $e");
//await pushGPStoServer();
} finally {
//print("--- End of pushGPStoServer function, calling recursively ---");
await Future.delayed(const Duration(seconds: 5 * 60));
await pushGPStoServer();
}
}
// ロゲイニングにデータを追加する関数です。
//
void addToRogaining(double lat, double lon, int destinationId) async {
DatabaseHelper db = DatabaseHelper.instance;
List<Destination> d = await db.getDestinationById(destinationId);
if (d.isEmpty) {
Destination df = festuretoDestination(indexController.currentFeature[0]);
//print("--- made checkin ${df.location_id} ----");
makeCheckin(df, true, "");
}
isInRog.value = true;
saveGameState();
}
// 買い物ポイントをキャンセルする関数です。
//
Future<void> cancelBuyPoint(Destination destination) async {
DatabaseHelper db = DatabaseHelper.instance;
await db.updateCancelBuyPoint(destination);
populateDestinations();
}
// 指定されたパスの画像をギャラリーに保存する関数です。
//
_saveImageFromPath(String imagePath) async {
// Read the image file from the given path
File imageFile = File(imagePath);
Uint8List imageBytes = await imageFile.readAsBytes();
// Save the image to the gallery
final result = await ImageGallerySaver.saveImage(imageBytes);
//print("--- save result --- ${result}");
}
// 買い物ポイントを作成する関数です。 指定された目的地に対して買い物ポイントの処理を行います。
//
// 買い物ポイントの作成に失敗した場合のエラーハンドリングを追加することを検討してください。
//
Future<void> makeBuyPoint(Destination destination, String imageurl) async {
DatabaseHelper db = DatabaseHelper.instance;
await db.updateBuyPoint(destination, imageurl);
populateDestinations();
await _saveImageFromPath(imageurl);
if (indexController.currentUser.isNotEmpty) {
double cpNum = destination.cp!;
int userId = indexController.currentUser[0]["user"]["id"];
//print("--- Pressed -----");
String team = indexController.currentUser[0]["user"]['team_name'];
//print("--- _team : ${_team}-----");
String eventCode = indexController.currentUser[0]["user"]["event_code"];
//print("--- _event_code : ${_event_code}-----");
String token = indexController.currentUser[0]["token"];
//print("--- _token : ${_token}-----");
DateTime now = DateTime.now();
String formattedDate = DateFormat('yyyy-MM-dd HH:mm:ss').format(now);
//print("------ checkin event $eventCode ------");
ExternalService()
.makeCheckpoint(userId, token, formattedDate, team, cpNum.round(),
eventCode, imageurl)
.then((value) {
//print("------Ext service check point $value ------");
});
}
}
// チェックインを行う関数です。 指定された目的地に対してチェックインの処理を行います。
//
// 要検討:チェックインのリクエストが失敗した場合のエラーハンドリングを追加することをお勧めします。
//
Future<void> makeCheckin(
Destination destination, bool action, String imageurl) async {
// print("~~~~ calling checkin function ~~~~");
// print(
// "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ressssss ${destination.sub_loc_id}@@@@@@@@@@@");
DatabaseHelper db = DatabaseHelper.instance;
List<Destination> ddd =
await db.getDestinationByLatLon(destination.lat!, destination.lon!);
if (ddd.isEmpty) {
destination.checkedin = true;
destination.checkin_image = imageurl;
await db.insertDestination(destination);
// print("~~~~ inserted into db ~~~~");
}
await _saveImageFromPath(imageurl);
populateDestinations();
/// post to NATNAT
if (indexController.currentUser.isNotEmpty) {
double cpNum = destination.cp!;
int userId = indexController.currentUser[0]["user"]["id"];
//print("--- Pressed -----");
String team = indexController.currentUser[0]["user"]['team_name'];
//print("--- _team : ${_team}-----");
String eventCode = indexController.currentUser[0]["user"]["event_code"];
//print("--- _event_code : ${_event_code}-----");
String token = indexController.currentUser[0]["token"];
//print("--- _token : ${_token}-----");
DateTime now = DateTime.now();
String formattedDate = DateFormat('yyyy-MM-dd HH:mm:ss').format(now);
await addGPStoDB(currentLat, currentLon, isCheckin: 1);
// print("------ checkin event $eventCode ------");
ExternalService()
.makeCheckpoint(userId, token, formattedDate, team, cpNum.round(),
eventCode, imageurl)
.then((value) {
// print("------Ext service check point $value ------");
});
}
dbService.updateDatabase();
}
// チェックインを削除する関数です。
//
Future<void> removeCheckin(int cp) {
dbService.updateDatabase();
return ExternalService().removeCheckin(cp);
}
// ゲームを開始する関数です。
//
Future<void> startGame() async {
//print("------ starting game ------");
if (game_started == false) {
await checkForCheckin();
}
}
// コントローラーの初期化時に呼び出されるライフサイクルメソッドです。
//
@override
void onInit() async {
super.onInit();
// 要検討:エラーメッセージを表示するなどの適切な処理を追加することを検討してください。
//
// locationController からデバイスの受け取るGPS情報を取得し、
// handleLocationUpdate を呼び出している。
//
locationController.locationMarkerPositionStream.listen(
(locationMarkerPosition) {
//if (locationMarkerPosition != null) {
handleLocationUpdate(locationMarkerPosition);
//}
}, onError: (err) {
print("Location Error: $err");
});
startGame();
}
// コントローラーのクローズ時に呼び出されるライフサイクルメソッドです。
//
@override
void onClose() {
locationController.stopPositionStream();
}
// 位置情報の更新を処理する関数です。
// 現在位置とスタート地点との距離を計算します。
// 現在位置と前回の位置情報との距離と時間差を確認し、一定の条件を満たす場合はGPSデータをデータベースに追加します。
//
// 要検討GPSデータの追加に失敗した場合のエラーハンドリングを追加することをお勧めします。
//
void handleLocationUpdate(LocationMarkerPosition? position) async {
try {
//final DestinationController destinationController = Get.find<DestinationController>();
//final signalStrength = locationController.getGpsSignalStrength();
okToUseGPS = false;
double prevLat = currentLat; // 直前の位置
double prevLon = currentLon;
if (position!=null){
currentLat = position.latitude;
currentLon = position.longitude;
okToUseGPS = true;
} else {
// 信号強度が低い場合、最後に取得した高いまたは中程度の位置情報を使用
// 但し、最初から高精度のものがない場合、どうするか?
//
if (lastValidLat != 0.0 && lastValidLon != 0.0) {
currentLat = lastValidLat;
currentLon = lastValidLon;
okToUseGPS = true;
} else {
// GPSの届く場所に行って、信号を拾ってください。とメッセージを出す。
position = null;
print("GPSの届く場所に行って、信号を拾ってください。");
Get.snackbar(
"GPS信号を正確に拾えていません",
"空が大きく見えるところへ行ってGPS信号を拾ってください。",
icon: const Icon(
Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
backgroundColor: Colors.yellow,
);
}
}
if (okToUseGPS && position!=null) {
// スタート位置から150m離れたら、ready_for_goal
if (distanceToStart() >= 150) {
ready_for_goal = true;
}
var distance = const Distance();
double distanceToDest = distance.as(
LengthUnit.Meter,
LatLng(position.latitude, position.longitude),
LatLng(prevLat, prevLon)
);
Duration difference = lastGPSCollectedTime.difference(DateTime.now()).abs();
// 最後にGPS信号を取得した時刻から10秒以上経過、かつ10m以上経過普通に歩くスピード
if (difference.inSeconds >= 10 || distanceToDest >= 10) {
// print(
// "^^^^^^^^ GPS data collected ${DateFormat('kk:mm:ss \n EEE d MMM').format(DateTime.now())}, ^^^ ${position.latitude}, ${position.longitude}");
LogManager().addLog(
"GPS : $currentLat, $currentLon - ${DateTime.now().hour}:${DateTime.now().minute}:${DateTime.now().second}:${DateTime.now().microsecond}");
if (isInRog.value) {
await addGPStoDB(position.latitude, position.longitude);
lastGPSCollectedTime = DateTime.now();
}
}
}
} finally {
/* Akira , 2024-4-5
if (position != null &&
(position.latitude != 0 && position.longitude != 0)) {
currentLat = position.latitude;
currentLon = position.longitude;
}
*/
if (okToUseGPS) {
// 位置情報が取得できた場合、精度に関わらず最後の位置情報を更新
//currentLat = position.latitude;
//currentLon = position.longitude;
}
}
}
// スタート地点までの距離を計算する関数です。
//
double distanceToStart() {
if (indexController.locations.isEmpty) {
return 1000000000;
}
//print("=== gfs len == ${indexController.locations[0].collection.length}");
double distanceToDest = double.infinity;
if (indexController.locations[0].features.isEmpty) {
return distanceToDest;
}
GeoJSONFeature? gfs = indexController.locations[0].features.firstWhere(
(element) => festuretoDestination(element!).cp == -1,
orElse: () => null, // Provide a null value if no element is found
);
//print("gfs : ${gfs}");
if (gfs == null) {
return distanceToDest;
}
//final currentLocation = getCurrentLocation(); // GPS信号中以上での現在位置
Destination des = festuretoDestination(gfs);
//print("=== gfs == ${des.toMap()}");
var distance = const Distance();
distanceToDest = distance.as(LengthUnit.Meter,
LatLng(currentLat,currentLon), LatLng(des.lat!, des.lon!));
// LatLng(currentLat, currentLon), LatLng(des.lat!, des.lon!));
//print("==== dist==${distanceToDest}");
return distanceToDest;
}
// 強制チェックイン距離を取得する関数です。
//
int getForcedChckinDistance(Destination dest) {
if (dest.checkin_radious == -1) {
return 10000000000000000;
}
int _retValue = 100;
if (dest.cp == -1) {
return 500;
}
Destination? ds;
GeoJSONFeature? gfs = indexController.locations[0].features.firstWhere(
(element) => festuretoDestination(element!).cp == -1,
orElse: () => null, // Provide a null value if no element is found
);
if (gfs == null) {
return _retValue;
}
ds = festuretoDestination(gfs);
var distance = const Distance();
double distanceToDest = distance.as(LengthUnit.Meter,
LatLng(dest.lat!, dest.lon!), LatLng(ds.lat!, ds.lon!));
if (distanceToDest <= 500) {
return 500;
}
//print("==== forced dist ==${distanceToDest}");
return _retValue;
}
// ユーザートークンを読み取る関数です。
//
readUserToken() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
indexController.userToken = prefs.getString("user_token");
}
// コントローラーの準備完了時に呼び出されるライフサイクルメソッドです。
//
@override
void onReady() async {
await readUserToken();
final token = indexController.userToken;
if (token != null && token.isNotEmpty) {
await indexController.loadUserDetailsForToken(token);
fixMapBound(token);
}else {
Get.toNamed(AppPages.LOGIN)!.then((value) {
if (indexController.currentUser.isNotEmpty) {
final tk = indexController.currentUser[0]["token"];
fixMapBound(tk);
} else {
Get.toNamed(AppPages.TRAVEL);
PerfectureService.getSubExt("9").then((value) {
if (value != null) {
LatLngBounds bnds = LatLngBounds(
LatLng(value[1], value[0]), LatLng(value[3], value[2]));
indexController.mapController
.fitBounds(bnds); //.centerZoomFitBounds(bnds);
}
});
}
});
}
// 地図のイベントリスナーを設定
indexController.mapController.mapEventStream.listen((MapEvent mapEvent) {
if (mapEvent is MapEventMoveEnd) {
indexController.loadLocationsBound();
}
});
super.onReady();
}
// 地図の境界を修正する関数です。
//
void fixMapBound(String token) {
//String _token = indexController.currentUser[0]["token"];
indexController.switchPage(AppPages.INDEX);
LocationService.getLocationsExt(token).then((value) {
if (value != null) {
//print("--- loc ext is - $value ----");
LatLngBounds bnds = LatLngBounds(
LatLng(value[1], value[0]), LatLng(value[3], value[2]));
//print("--- bnds is - $bnds ----");
indexController.mapController.fitBounds(
bnds,
);
indexController.currentBound.clear();
indexController.currentBound.add(bnds);
indexController.loadLocationsBound();
centerMapToCurrentLocation();
}
});
}
/*
void fixMapBound(String token) {
indexController.switchPage(AppPages.INDEX);
LocationService.getLocationsExt(token).then((value) {
if (value != null) {
LatLngBounds bnds = LatLngBounds(
LatLng(value[1], value[0]),
LatLng(value[3], value[2]),
);
if (indexController.isMapControllerReady.value) {
indexController.mapController.fitBounds(
bnds,
);
indexController.currentBound.clear();
indexController.currentBound.add(bnds);
indexController.loadLocationsBound();
centerMapToCurrentLocation();
} else {
// MapControllerが初期化されるまで待機し、その後fitBoundsを実行
WidgetsBinding.instance.addPostFrameCallback((_) {
indexController.mapController.fitBounds(
bnds,
);
indexController.currentBound.clear();
indexController.currentBound.add(bnds);
indexController.loadLocationsBound();
centerMapToCurrentLocation();
});
}
}
});
}
*/
// 地図を現在位置に中央揃えする関数です。
//
void centerMapToCurrentLocation() {
//print("center is ${currentLat}, ${currentLon}");
// Akira ... 状況によって呼ぶか呼ばないか
if (currentLat != 0 || currentLon != 0) {
indexController.mapController.move(LatLng(currentLat, currentLon), 17.0);
}
}
// 接続状態が変更されたときに呼び出される関数です。
//
void connectionChanged(String val) {
//print('----- %%%%%%%%%%%%%%%%%%%%% ----- $val');
Map<String, dynamic> res = {};
if (val == "wifi" || val == "mobile") {
String token = indexController.currentUser[0]["token"];
DatabaseHelper db = DatabaseHelper.instance;
db.allRogianing().then((value) {
value.forEach((e) async {
if (e.rog_action_type == 0) {
res = await ExternalService().startRogaining();
} else if (e.rog_action_type == 1) {
var datetime = DateTime.fromMicrosecondsSinceEpoch(e.checkintime!);
res = await ExternalService().makeCheckpoint(
e.user_id!,
token,
getFormatedTime(datetime),
e.team_name!,
e.cp_number!,
e.event_code!,
e.image!);
} else if (e.rog_action_type == 2) {
var datetime = DateTime.fromMicrosecondsSinceEpoch(e.checkintime!);
res = await ExternalService().makeGoal(
e.user_id!,
token,
e.team_name!,
e.image!,
getFormatedTime(datetime),
e.event_code!);
}
if (res.isNotEmpty) {
db.deleteRogaining(e.id!);
}
});
});
}
}
// 位置情報の許可を確認する関数です。
//
void checkPermission() async {
LocationPermission permission = await Geolocator.checkPermission();
if (permission != LocationPermission.whileInUse ||
permission != LocationPermission.always) {
locationPermission.clear();
locationPermission.add(permission.name);
permission = await Geolocator.requestPermission();
}
}
// IDに基づいて目的地を取得する関数です。
//
Destination? destinationById(int id) {
Destination? d;
//print("--- target des - $id ----");
for (Destination ss in destinations) {
//print("--- des - ${ss.location_id} ----");
if (ss.location_id == id) {
d = ss;
break;
}
}
return d;
}
// 目的地を削除する関数です。
//
void deleteDestination(Destination d) {
//int id = destinations[index].location_id!;
//print("---- index ${destinations[index].location_id!}-----");
for (Destination ss in destinations) {
if (ss.location_id == d.location_id) {
destinations.remove(ss);
break;
}
}
DatabaseHelper db = DatabaseHelper.instance;
db.deleteDestination(d.location_id!).then((value) {
populateDestinations();
});
dbService.updateDatabase();
}
// データベースからすべての目的地を削除する関数です。
//
void deleteDBDestinations() {
DatabaseHelper db = DatabaseHelper.instance;
db.deleteAllDestinations().then((value) {
populateDestinations();
});
dbService.updateDatabase();
}
// ---------- database ------------------///
// 目的地を追加する関数です。
//
void addDestinations(Destination dest) {
DatabaseHelper db = DatabaseHelper.instance;
db.getDestinationByLatLon(dest.lat!, dest.lon!).then((value) {
if (value.isNotEmpty) {
db.deleteDestination(value[0].location_id!).then((value) {
db.insertDestination(dest).then((value) {
//print(
// "----- destination controller deleted and inserted destination id $value ---- :::::");
populateDestinations();
});
});
} else {
db.insertDestination(dest).then((value) {
//print("----- destination controller added as new $value--- :::::");
populateDestinations();
});
}
});
dbService.updateDatabase();
}
// 目的地の選択状態を切り替える関数です。
//
void toggleSelection(Destination dest) async {
try {
DatabaseHelper db = DatabaseHelper.instance;
await db.toggleSelecttion(dest);
destinations.clear();
db.getDestinations().then((value) {
destinationCount.value = 0;
currentSelectedDestinations.clear();
for (Destination d in value) {
//print("------ destination controller populating destination-------- ${d.checkedin}-------- :::::");
//print("-----populated----- ${d.toMap()}");
if (d.selected!) {
currentSelectedDestinations.add(d);
}
destinations.add(d);
}
destinationCount.value = destinations.length;
});
} catch( e ){
print('Error in toggleSelection: $e');
Get.snackbar(
"画面切り替えでエラー",
"画面の切り替えができませんでした",
icon: const Icon(
Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
backgroundColor: Colors.yellow,
);
}
}
// ダイアログを表示する関数です。
//
buildShowDialog(BuildContext context) {
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return const Center(
child: CircularProgressIndicator(),
);
});
}
// 現在地点からの目的地の行列を計算する関数です。
//
void destinationMatrixFromCurrentPoint(List<Destination> points) {
//buildShowDialog(Get.context!);
MatrixService.getDestinations(points).then((mat) {
//print(" matrix is ------- $mat");
matrix = mat;
try {
indexController.routePoints = [];
indexController.routePointLenght.value = 0;
DestinationService.getDestinationLine(points, matrix)?.then((value) {
indexController.routePoints = value;
indexController.routePointLenght.value =
indexController.routePoints.length;
//Get.toNamed(AppPages.TRAVEL);
});
destinationCount.value = destinations.length;
} catch (_) {
skipGps = false;
return;
} finally {
//Get.back();
}
});
}
// 目的地のリストを取得してObservable変数を更新する関数です。
//
void populateDestinations() {
DatabaseHelper db = DatabaseHelper.instance;
destinations.clear();
db.getDestinations().then((value) {
destinationCount.value = 0;
for (Destination d in value) {
destinations.add(d);
}
if (destinations.isEmpty) {
rogainingCounted.value = false;
}
});
}
// 目的地の順序を変更する関数です。
//
void makeOrder(Destination d, int dir) {
DatabaseHelper db = DatabaseHelper.instance;
db.updateOrder(d, dir).then((value) {
populateDestinations();
});
}
}