未確認だが問題回避のためプッシュ

This commit is contained in:
2024-04-09 01:58:25 +09:00
parent dd9dd0d087
commit 1b4073f690
59 changed files with 2384 additions and 410 deletions

View File

@ -1,4 +1,5 @@
// ignore: non_constant_identifier_names
// 不要
String location_line_date = """
{
"type": "FeatureCollection",

View File

@ -1,6 +1,12 @@
import 'dart:async';
//import 'dart:convert';
//import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
import 'package:get/get.dart';
//import 'package:vm_service/vm_service.dart';
//import 'package:dart_vm_info/dart_vm_info.dart';
import 'package:rogapp/pages/destination/destination_controller.dart';
import 'package:rogapp/pages/index/index_binding.dart';
import 'package:rogapp/routes/app_pages.dart';
@ -66,6 +72,8 @@ void main() async {
ErrorService.reportError(details.exception, details.stack ?? StackTrace.current, deviceInfo);
};
// startMemoryMonitoring(); // 2024-4-8 Akira: メモリ使用量のチェックを開始 See #2810
runZonedGuarded(() {
runApp(const ProviderScope(child: MyApp()));
}, (error, stackTrace) {
@ -75,6 +83,72 @@ void main() async {
//runApp(const MyApp());
}
// メモリ使用量の解説https://qiita.com/hukusuke1007/items/e4e987836412e9bc73b9
/*
// 2024-4-8 Akira: メモリ使用量のチェックのため追加 See #2810
// startMemoryMonitoring関数が5分ごとに呼び出され、メモリ使用量をチェックします。
// メモリ使用量が閾値ここでは500MBを超えた場合、エラーメッセージとスタックトレースをレポートします。
//
void startMemoryMonitoring() {
const threshold = 500 * 1024 * 1024; // 500MB
// メモリ使用量情報を取得
final memoryUsage = MemoryUsage.fromJson(DartVMInfo.getAllocationProfile());
if (memoryUsage.heapUsage > threshold) {
final now = DateTime.now().toIso8601String();
final message = 'High memory usage detected at $now: ${memoryUsage.heapUsage} bytes';
print(message);
reportError(message, StackTrace.current);
showMemoryWarningDialog();
}
Timer(const Duration(minutes: 5), startMemoryMonitoring);
}
class MemoryUsage {
final int heapUsage;
MemoryUsage({required this.heapUsage});
factory MemoryUsage.fromJson(Map<String, dynamic> json) {
return MemoryUsage(
heapUsage: json['heapUsage'] as int,
);
}
}
*/
// 2024-4-8 Akira: メモリ使用量のチェックのため追加 See #2810
// reportError関数でエラーレポートを送信します。具体的な実装は、利用するエラー報告サービスによって異なります。
void reportError(String message, StackTrace stackTrace) async {
// エラーレポートの送信処理を実装
// 例: SentryやFirebase Crashlyticsなどのエラー報告サービスを利用
print("ReportError : ${message} . stacktrace : ${stackTrace}");
}
// 2024-4-8 Akira: メモリ使用量のチェックのため追加 See #2810
// showMemoryWarningDialog関数で、メモリ使用量が高い場合にユーザーに警告ダイアログを表示します。
//
void showMemoryWarningDialog() {
if (Get.context != null) {
showDialog(
context: Get.context!,
builder: (context) => AlertDialog(
title: const Text('メモリ使用量の警告'),
content: const Text('アプリのメモリ使用量が高くなっています。アプリを再起動することをお勧めします。'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('OK'),
),
],
),
);
}
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);

View File

@ -1,3 +1,6 @@
// プロパティの型がString?やint?などのオプショナル型になっています。
// これらのプロパティが常に値を持つことが保証されている場合は、非オプショナル型を使用することで、不要なnullチェックを回避できます。
//
class AuthUser {
AuthUser();

View File

@ -1,3 +1,6 @@
// プロパティの型がString?やint?などのオプショナル型になっています。
// これらのプロパティが常に値を持つことが保証されている場合は、非オプショナル型を使用することで、不要なnullチェックを回避できます。
//
class Destination {
String? name;
String? address;

View File

@ -31,6 +31,8 @@ String getTagText(bool isRecept, String? tags) {
return "";
}
// 要修正:画像の読み込みエラーが発生した場合のエラーハンドリングが不十分です。エラーメッセージを表示するなどの処理を追加してください。
//
Image getDisplayImage(Destination destination) {
String serverUrl = ConstValues.currentServer();
@ -104,6 +106,8 @@ class CameraPage extends StatelessWidget {
Timer? timer;
// 要修正:エラーハンドリングが不十分です。例外が発生した場合の処理を追加することをお勧めします。
//
Widget getAction(BuildContext context) {
//print("----cccheckin is --- ${dbDest?.checkedin} ----");
if (manulaCheckin == true) {
@ -431,6 +435,8 @@ class BuyPointCamera extends StatelessWidget {
height: 370,
decoration: BoxDecoration(
image: DecorationImage(
// 要修正getReceiptImage関数の戻り値がnullの場合のエラーハンドリングが不十分です。適切なデフォルト画像を表示するなどの処理を追加してください。
//
image: getReceiptImage(), fit: BoxFit.cover)),
),
),

View File

@ -33,6 +33,8 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:geolocator/geolocator.dart';
// 目的地に関連する状態管理とロジックを担当するクラスです。
//
class DestinationController extends GetxController {
@ -44,6 +46,8 @@ class DestinationController extends GetxController {
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; // ボトムシートを表示すべきかどうかを示すフラグです。
@ -75,6 +79,7 @@ class DestinationController extends GetxController {
var travelMode = 0.obs; // 移動モードを保持するReactive変数です。
bool skipGps = false; // GPSをスキップするかどうかを示すフラグです。
bool okToUseGPS = false; // 最新のGPS情報を使用して良いかを示すフラグ。
Map<String, dynamic> matrix = {}; // 行列データを保持する変数です。
@ -199,6 +204,10 @@ class DestinationController extends GetxController {
// 目的地がデータベースに存在しない場合は、新しい目的地としてデータベースに挿入します。
// 目的地に応じて、チェックイン、ゴール、買い物ポイントの処理を行います。
//
// 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;
@ -240,6 +249,31 @@ class DestinationController extends GetxController {
// 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) {
@ -407,10 +441,10 @@ class DestinationController extends GetxController {
context: Get.context!,
isScrollControlled: true,
builder: ((context) => CameraPage(
buyPointPhoto: true,
destination: d,
dbDest: ds.first,
))).whenComplete(() {
destination: d,
buyPointPhoto: true,
dbDest: ds.first,
))).whenComplete(() {
shouldShowBottomSheet = true;
skipGps = false;
rogainingCounted.value = true;
@ -641,13 +675,14 @@ class DestinationController extends GetxController {
});
} 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,
);
"ロゲが始まっていません",
"ロゲ開始ボタンをタップして、ロゲイニングを始める必要があります",
icon: const Icon(
Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
backgroundColor: Colors.yellow,
);
}
}
}
@ -680,6 +715,9 @@ class DestinationController extends GetxController {
// 目的地のリストを走査し、現在位置がチェックイン半径内にある場合は、チェックインの処理を行います。
// GPSデータの送信を開始します。
//
// 2024-4-8 Akira : See 2809
// checkForCheckinメソッドの再帰呼び出しをunawaitedで囲んで、非同期処理の結果を待たずに先に進むようにしました。また、再帰呼び出しの前に一定時間待機するようにしました。
//
Future<void> checkForCheckin() async {
//print("--- Start of checkForCheckin function ---");
dbService.updateDatabase();
@ -705,15 +743,16 @@ class DestinationController extends GetxController {
});
if (gps_push_started == false) {
pushGPStoServer();
unawaited( pushGPStoServer() );
}
//print("--- 123 ---- $skip_gps----");
} catch (e) {
//print("An error occurred: $e");
await checkForCheckin();
print("An error occurred: $e");
// await checkForCheckin();
} finally {
await Future.delayed(const Duration(seconds: 5)); // 一定時間待機してから再帰呼び出し
//print("--- End of checkForCheckin function, calling recursively ---");
await checkForCheckin();
unawaited( checkForCheckin() );
}
}
@ -772,6 +811,8 @@ class DestinationController extends GetxController {
// 買い物ポイントを作成する関数です。 指定された目的地に対して買い物ポイントの処理を行います。
//
// 買い物ポイントの作成に失敗した場合のエラーハンドリングを追加することを検討してください。
//
Future<void> makeBuyPoint(Destination destination, String imageurl) async {
DatabaseHelper db = DatabaseHelper.instance;
await db.updateBuyPoint(destination, imageurl);
@ -804,6 +845,8 @@ class DestinationController extends GetxController {
// チェックインを行う関数です。 指定された目的地に対してチェックインの処理を行います。
//
// 要検討:チェックインのリクエストが失敗した場合のエラーハンドリングを追加することをお勧めします。
//
Future<void> makeCheckin(
Destination destination, bool action, String imageurl) async {
// print("~~~~ calling checkin function ~~~~");
@ -873,6 +916,9 @@ class DestinationController extends GetxController {
@override
void onInit() async {
super.onInit();
// 要検討:エラーメッセージを表示するなどの適切な処理を追加することを検討してください。
//
locationController.locationMarkerPositionStream.listen(
(locationMarkerPosition) {
if (locationMarkerPosition != null) {
@ -896,9 +942,49 @@ class DestinationController extends GetxController {
// 現在位置とスタート地点との距離を計算します。
// 現在位置と前回の位置情報との距離と時間差を確認し、一定の条件を満たす場合はGPSデータをデータベースに追加します。
//
// 要検討GPSデータの追加に失敗した場合のエラーハンドリングを追加することをお勧めします。
//
void handleLocationUpdate(LocationMarkerPosition? position) async {
try {
if (position != null) {
final DestinationController destinationController = Get.find<DestinationController>();
final signalStrength = destinationController.getGpsSignalStrength();
okToUseGPS = false;
double prevLat = lastValidLat; // 一つ前の位置情報を記録
double prevLon = lastValidLon;
if (position!=null && (signalStrength == 'high' || signalStrength == 'medium')) {
// 信号強度が高いまたは中程度の場合、現在の位置情報を更新
currentLat = position.latitude;
currentLon = position.longitude;
lastValidLat = position.latitude;
lastValidLon = 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;
}
@ -907,10 +993,11 @@ class DestinationController extends GetxController {
double distanceToDest = distance.as(
LengthUnit.Meter,
LatLng(position.latitude, position.longitude),
LatLng(currentLat, currentLon));
LatLng(prevLat, prevLon)
);
Duration difference =
lastGPSCollectedTime.difference(DateTime.now()).abs();
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}");
@ -931,10 +1018,10 @@ class DestinationController extends GetxController {
currentLon = position.longitude;
}
*/
if (position != null) {
if (okToUseGPS) {
// 位置情報が取得できた場合、精度に関わらず最後の位置情報を更新
currentLat = position.latitude;
currentLon = position.longitude;
//currentLat = position.latitude;
//currentLon = position.longitude;
}
}
}
@ -1020,25 +1107,32 @@ class DestinationController extends GetxController {
if (token != null && token.isNotEmpty) {
await indexController.loadUserDetailsForToken(token);
fixMapBound(token);
return;
}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);
}
});
}
});
}
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();
}
@ -1106,7 +1200,7 @@ class DestinationController extends GetxController {
print("center is ${currentLon}, ${currentLon}");
return true;
}());
// Akira
// Akira ... 状況によって呼ぶか呼ばないか
if (currentLat != 0 || currentLon != 0) {
indexController.mapController.move(LatLng(currentLat, currentLon), 17.0);
}
@ -1236,22 +1330,36 @@ class DestinationController extends GetxController {
// 目的地の選択状態を切り替える関数です。
//
void toggleSelection(Destination dest) async {
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);
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);
}
destinations.add(d);
}
destinationCount.value = destinations.length;
});
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,
);
}
}
// ダイアログを表示する関数です。

View File

@ -1,252 +0,0 @@
// import 'package:flutter/material.dart';
// import 'package:geolocator/geolocator.dart';
// import 'package:get/get.dart';
// import 'package:latlong2/latlong.dart';
// import 'package:rogapp/pages/destination/destination_controller.dart';
// import 'package:rogapp/pages/destination_map/destination_map_page.dart';
// import 'package:rogapp/pages/drawer/drawer_page.dart';
// import 'package:rogapp/pages/index/index_controller.dart';
// import 'package:rogapp/routes/app_pages.dart';
// import 'package:rogapp/widgets/destination_widget.dart';
// class XDestnationPage extends StatelessWidget {
// XDestnationPage({Key? key}) : super(key: key);
// final DestinationController destinationController =
// Get.find<DestinationController>();
// final IndexController indexController = Get.find<IndexController>();
// final List<int> _items = List<int>.generate(50, (int index) => index);
// Future<void> showCurrentPosition() async {
// LocationPermission permission = await Geolocator.checkPermission();
// if (permission != LocationPermission.whileInUse ||
// permission != LocationPermission.always) {
// permission = await Geolocator.requestPermission();
// }
// Position position = await Geolocator.getCurrentPosition(
// desiredAccuracy: LocationAccuracy.high,
// forceAndroidLocationManager: true);
// indexController.rogMapController
// .move(LatLng(position.latitude, position.longitude), 14);
// }
// Image getImage(int index) {
// if (destinationController.destinations[index].photos == null ||
// destinationController.destinations[index].photos == "") {
// return const Image(image: AssetImage('assets/images/empty_image.png'));
// } else {
// return Image(
// image:
// NetworkImage(destinationController.destinations[index].photos!));
// }
// }
// Widget getRoutingImage(int route) {
// switch (route) {
// case 0:
// return const Image(
// image: AssetImage('assets/images/p4_9_man.png'),
// width: 35.0,
// );
// case 1:
// return const Image(
// image: AssetImage('assets/images/p4_8_car.png'),
// width: 35.0,
// );
// case 2:
// return const Image(
// image: AssetImage('assets/images/p4_10_train.png'),
// width: 35.0,
// );
// default:
// return const Image(
// image: AssetImage('assets/images/p4_9_man.png'),
// width: 35.0,
// );
// }
// }
// @override
// Widget build(BuildContext context) {
// return WillPopScope(
// onWillPop: () async {
// indexController.switchPage(AppPages.INITIAL);
// return false;
// },
// child: Scaffold(
// drawer: DrawerPage(),
// bottomNavigationBar: BottomAppBar(
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: <Widget>[
// Padding(
// padding: const EdgeInsets.only(left: 13.0),
// child: InkWell(
// child: Obx((() => getRoutingImage(
// destinationController.travelMode.value))),
// onTap: () {
// Get.bottomSheet(
// Obx(
// () => ListView(
// children: [
// Padding(
// padding: const EdgeInsets.only(
// top: 30.0, bottom: 30),
// child: Center(
// child: Text(
// "select_travel_mode".tr,
// style: const TextStyle(
// fontSize: 22.0,
// color: Colors.red,
// fontWeight: FontWeight.bold),
// ),
// ),
// ),
// ListTile(
// selected:
// destinationController.travelMode.value ==
// 0
// ? true
// : false,
// selectedTileColor: Colors.amber.shade200,
// leading: const Image(
// image: AssetImage(
// 'assets/images/p4_9_man.png'),
// ),
// title: Text("walking".tr),
// onTap: () {
// destinationController.travelMode.value = 0;
// destinationController
// .populateDestinations();
// Get.back();
// },
// ),
// ListTile(
// selected:
// destinationController.travelMode.value ==
// 1
// ? true
// : false,
// selectedTileColor: Colors.amber.shade200,
// leading: const Image(
// image: AssetImage(
// 'assets/images/p4_8_car.png'),
// ),
// title: Text("driving".tr),
// onTap: () {
// destinationController.travelMode.value = 1;
// destinationController
// .populateDestinations();
// Get.back();
// },
// ),
// // ListTile(
// // selected: destinationController.travelMode == 2 ? true : false,
// // selectedTileColor: Colors.amber.shade200,
// // leading: Image(image: AssetImage('assets/images/p4_10_train.png'),),
// // title: Text("transit".tr),
// // onTap:(){
// // destinationController.travelMode.value = 2;
// // destinationController.PopulateDestinations();
// // Get.back();
// // },
// // ),
// ],
// ),
// ),
// isScrollControlled: false,
// backgroundColor: Colors.white,
// );
// //destinationController.PopulateDestinations();
// }),
// ),
// IconButton(
// icon: const Icon(
// Icons.travel_explore,
// size: 35,
// ),
// onPressed: () {
// indexController.switchPage(AppPages.INITIAL);
// }),
// ],
// ),
// ),
// floatingActionButton: FloatingActionButton(
// onPressed: () {
// //print("######");
// indexController.toggleDestinationMode();
// },
// tooltip: 'Increment',
// elevation: 4.0,
// child: Obx(() => indexController.desinationMode.value == 1
// ? const Image(image: AssetImage('assets/images/list2.png'))
// : const Image(image: AssetImage('assets/images/map.png'))),
// ),
// floatingActionButtonLocation:
// FloatingActionButtonLocation.centerDocked,
// appBar: AppBar(
// automaticallyImplyLeading: true,
// title: Text("app_title".tr),
// actions: [
// InkWell(
// onTap: () {
// Get.toNamed(AppPages.CAMERA_PAGE);
// },
// child: destinationController.isInRog.value == true
// ? Image.asset(
// "assets/images/basic-walking.gif",
// height: 10.0,
// )
// : destinationController.isAtGoal.value == true
// ? IconButton(
// onPressed: () {
// Get.toNamed(AppPages.CAMERA_PAGE);
// },
// icon: const Icon(Icons.assistant_photo),
// )
// : IconButton(
// onPressed: () {
// Get.toNamed(AppPages.CAMERA_PAGE);
// },
// icon: const Icon(Icons.accessibility),
// ),
// ),
// // Obx(() =>
// // Text(indexController.connectionStatusName.value)
// // ),
// Obx(
// () => ToggleButtons(
// disabledColor: Colors.grey.shade200,
// selectedColor: Colors.red,
// onPressed: (int index) {
// destinationController.isGpsSelected.value =
// !destinationController.isGpsSelected.value;
// if (destinationController.isGpsSelected.value) {
// destinationController.chekcs = 0;
// destinationController.skipGps = false;
// //destinationController.resetRogaining();
// }
// },
// isSelected: [destinationController.isGpsSelected.value],
// children: const <Widget>[
// Icon(
// Icons.explore,
// size: 35.0,
// )
// ],
// ),
// ),
// // IconButton(onPressed: (){
// // showCurrentPosition();
// // },
// // icon: Icon(Icons.location_on_outlined))
// ],
// ),
// body: Obx(() => indexController.desinationMode.value == 0
// ? DestinationWidget()
// : DestinationMapPage())),
// );
// }
// }

View File

@ -34,6 +34,8 @@ class DestinationMapPage extends StatelessWidget {
return pts;
}
// 要検討:マーカーのタップイベントを処理する際に、エラーハンドリングが不十分です。例外が発生した場合の処理を追加することをお勧めします。
//
List<Marker>? getMarkers() {
List<Marker> pts = [];
int index = -1;
@ -123,6 +125,8 @@ class DestinationMapPage extends StatelessWidget {
)));
}
// 要検討MapOptionsのboundsプロパティにハードコードされた座標が使用されています。これを動的に設定できるようにすることを検討してください。
//
FlutterMap travelMap() {
return FlutterMap(
mapController: indexController.rogMapController,

View File

@ -11,6 +11,8 @@ class DrawerPage extends StatelessWidget {
final IndexController indexController = Get.find<IndexController>();
// 要検討URLの起動に失敗した場合のエラーハンドリングが不十分です。適切なエラーメッセージを表示するなどの処理を追加してください。
//
void _launchURL(url) async {
if (!await launchUrl(url)) throw 'Could not launch $url';
}
@ -90,6 +92,8 @@ class DrawerPage extends StatelessWidget {
leading: const Icon(Icons.password),
title: const Text("リセット"),
onTap: () {
// 要検討:リセット操作の確認メッセージをローカライズすることを検討してください。
//
Get.defaultDialog(
title: "よろしいですか、リセットしますか?",
middleText: "これにより、すべてのゲーム データが削除され、すべての状態が削除されます",
@ -132,6 +136,8 @@ class DrawerPage extends StatelessWidget {
),
indexController.currentUser.isNotEmpty
? ListTile(
// 要検討:アカウント削除のリクエストが失敗した場合のエラーハンドリングを追加することをお勧めします。
//
leading: const Icon(Icons.delete_forever),
title: Text("ユーザーデータを削除する".tr),
onTap: () {

View File

@ -31,6 +31,8 @@ class _GpsPageState extends State<GpsPage> {
loadGpsData();
}
// 要検討GPSデータの読み込みに失敗した場合のエラーハンドリングが不十分です。適切なエラーメッセージを表示するなどの処理を追加してください。
//
void loadGpsData() async {
final team_name = indexController.currentUser[0]["user"]['team_name'];
final event_code = indexController.currentUser[0]["user"]["event_code"];
@ -41,6 +43,8 @@ class _GpsPageState extends State<GpsPage> {
//print("--- gps data ${data} ----");
}
// 要検討:マーカーの形状を決定する際に、マジックナンバーが使用されています。定数を使用するなどして、コードの可読性を向上させることを検討してください。
//
Widget getMarkerShape(GpsData i) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,

View File

@ -24,6 +24,8 @@ class _HistoryPageState extends State<HistoryPage> {
child: Column(
children: [
FutureBuilder(
// 要検討:スナップショットのエラーハンドリングが行われていますが、具体的なエラーメッセージを表示するようにすることをお勧めします。
//
future: db.getDestinations(),
builder: (BuildContext context,
AsyncSnapshot<List<Destination>> snapshot) {
@ -49,6 +51,7 @@ class _HistoryPageState extends State<HistoryPage> {
return Padding(
padding: const EdgeInsets.all(8.0),
child: CustomWidget(
// 要検討:画像のサイズがハードコードされています。画像のサイズを動的に設定できるようにすることを検討してください。
title: dests[index].name!,
subtitle:
"${dests[index].sub_loc_id} : ${dests[index].name}",

View File

@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rogapp/pages/search/search_page.dart';
// 要検討SearchPageへのナビゲーションにNavigator.pushを使用していますが、一貫性のためにGet.toやGet.toNamedを使用することを検討してください。
//
class HomePage extends GetView{
const HomePage({Key? key}) : super(key: key);

View File

@ -166,6 +166,8 @@ class IndexController extends GetxController {
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 ###### --------");
@ -189,6 +191,8 @@ class IndexController extends GetxController {
});
}
// 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。
//
void changePassword(
String oldpassword, String newpassword, BuildContext context) {
String token = currentUser[0]['token'];
@ -230,6 +234,8 @@ class IndexController extends GetxController {
cats.clear();
}
// 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。
//
void register(String email, String password, BuildContext context) {
AuthService.register(email, password).then((value) {
if (value.isNotEmpty) {
@ -345,14 +351,20 @@ class IndexController extends GetxController {
// 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;
}
// Akira 追加:2024-4-6 #2800
await waitForMapControllerReady(); // MapControllerの初期化が完了するまで待機
//await waitForMapControllerReady(); // MapControllerの初期化が完了するまで待機
// Akira 追加:2024-4-6 #2800
// ==> remove 2024-4-8
locations.clear();
String cat = currentCat.isNotEmpty ? currentCat[0] : "";
@ -375,22 +387,25 @@ class IndexController extends GetxController {
isLoading.value = true; // ローディング状態をtrueに設定
Future.delayed(const Duration(seconds: 1), () async {
// unawaited( Future.delayed(const Duration(seconds: 1), () async {
// remove
//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})");
var 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の場合のエラーハンドリングが不十分です。適切なエラーメッセージを表示するなどの処理を追加してください。
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>(); // 追加
@ -399,32 +414,41 @@ class IndexController extends GetxController {
// destinationController.fixMapBound(tk); // 追加
//} // 追加
return;
}
isLoading.value = false; // ローディング状態をfalseに設定
if (value!=null && value.features.isEmpty) {
}
isLoading.value = false; // ローディング状態をfalseに設定
if (value != null && 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),
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) {
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 {

View File

@ -22,6 +22,9 @@ import 'package:rogapp/utils/location_controller.dart';
// 全体的に、index_page.dartはアプリのメインページの構造を定義し、他のコンポーネントやページへの橋渡しを行っているファイルです。
//
// 要検討GPSデータの表示アイコンをタップした際のエラーハンドリングを追加することをお勧めします。
// MapWidgetとListWidgetの切り替えにObxを使用していますが、パフォーマンスを考慮して、必要な場合にのみウィジェットを再構築するようにしてください。
// DestinationControllerのisSimulationModeを使用してGPS信号の強弱をシミュレーションしていますが、本番環境では適切に実際のGPS信号を使用するようにしてください。
// IndexPageクラスは、GetView<IndexController>を継承したStatelessWidgetです。このクラスは、アプリのメインページを表すウィジェットです。
//

View File

@ -2,6 +2,9 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rogapp/routes/app_pages.dart';
// 要検討:ログインボタンとサインアップボタンの配色を見直すことを検討してください。現在の配色では、ボタンの役割がわかりにくい可能性があります。
// ボタンのテキストをローカライズすることを検討してください。
//
class LandingPage extends StatefulWidget {
const LandingPage({ Key? key }) : super(key: key);

View File

@ -3,6 +3,8 @@ import 'package:flutter/material.dart';
class LoadingPage extends StatelessWidget {
const LoadingPage({Key? key}) : super(key: key);
// 要検討ローディングインジケーターの値を固定値0.8)にしていますが、実際のローディング進捗に合わせて動的に変更することを検討してください。
//
@override
Widget build(BuildContext context) {
return Container(

View File

@ -3,6 +3,10 @@ import 'package:get/get.dart';
import 'package:rogapp/pages/index/index_controller.dart';
import 'package:rogapp/routes/app_pages.dart';
// 要検討:ログインボタンとサインアップボタンの配色を見直すことを検討してください。現在の配色では、ボタンの役割がわかりにくい可能性があります。
// エラーメッセージをローカライズすることを検討してください。
// ログイン処理中にエラーが発生した場合のエラーハンドリングを追加することをお勧めします。
//
class LoginPage extends StatelessWidget {
final IndexController indexController = Get.find<IndexController>();

View File

@ -3,6 +3,10 @@ import 'package:get/get.dart';
import 'package:rogapp/pages/index/index_controller.dart';
import 'package:rogapp/routes/app_pages.dart';
// 要検討:ログインボタンとサインアップボタンの配色を見直すことを検討してください。現在の配色では、ボタンの役割がわかりにくい可能性があります。
// エラーメッセージをローカライズすることを検討してください。
// ポップアップを閉じるボタンを追加することを検討してください。
//
class LoginPopupPage extends StatelessWidget {
LoginPopupPage({Key? key}) : super(key: key);

View File

@ -70,6 +70,8 @@ class _PermissionHandlerScreenState extends State<PermissionHandlerScreen> {
));
}
// 要検討:位置情報の許可が拒否された場合、適切なエラーメッセージを表示することを検討してください。
//
Future<void> requestPermission() async {
PermissionStatus permission = await Permission.location.status;
if (permission == PermissionStatus.permanentlyDenied) {
@ -93,6 +95,8 @@ class _PermissionHandlerScreenState extends State<PermissionHandlerScreen> {
);
}
// 要検討:ユーザーが位置情報の許可を拒否し続けた場合の対処方法を明確にすることをお勧めします。
//
void showPermanentAlert() {
showDialog(
context: context,

View File

@ -1,6 +1,8 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:rogapp/model/auth_user.dart';
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import '../utils/const.dart';
@ -90,16 +92,35 @@ class AuthService {
String url = '$serverUrl/api/login/';
//print('++++++++$url');
//String url = 'http://localhost:8100/api/login/';
final http.Response response = await http.post(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{'email': email, 'password': password}),
);
try {
final http.Response response = await http.post(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(
<String, String>{'email': email, 'password': password}),
);
if (response.statusCode == 200) {
cats = json.decode(utf8.decode(response.bodyBytes));
if (response.statusCode == 200) {
cats = json.decode(utf8.decode(response.bodyBytes));
} else {
print('Login failed with status code: ${response.statusCode}');
cats = {};
}
} catch( e ){
print('Error in login: $e');
Get.snackbar("通信エラーがおきました", "サーバーと通信できませんでした");
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,
);
cats = {};
}
return cats;
}

View File

@ -1,5 +1,5 @@
import 'dart:io';
//import 'package:device_info/device_info.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:device_info_plus/device_info_plus.dart';

View File

@ -9,7 +9,7 @@ class ErrorService {
final String errorMessage = error.toString();
final String stackTraceString = stackTrace.toString();
final String estimatedCause = _estimateErrorCause(errorMessage);
// final String deviceInfo = await _getDeviceInfo();
//final String deviceInfo = await _getDeviceInfo();
final Uri url = Uri.parse('https://rogaining.sumasen.net/report-error');
final response = await http.post(
@ -44,5 +44,17 @@ class ErrorService {
return '不明なエラー';
}
}
/*
// 2024-4-8 Akira: メモリ使用量のチェックのため追加 See #2810
//
static void reportMemoryError(String message, StackTrace stackTrace) async {
final errorDetails = FlutterErrorDetails(
exception: Exception(message),
stack: stackTrace,
);
await reportError(errorDetails.exception, errorDetails.stack ?? StackTrace.current, deviceInfo);
}
*/
}

View File

@ -114,64 +114,69 @@ class ExternalService {
//print("~~~~ before calling api 1 ~~~~");
final http.Response response = await http.post(
Uri.parse(url1),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Token $token'
},
// 'id', 'user', 'goalimage', 'goaltime', 'team_name', 'event_code','cp_number'
body: jsonEncode(<String, String>{
'user': userId.toString(),
'team_name': teamname,
'event_code': eventcode,
'checkinimage': im1_64,
'checkintime': checkinTime,
'cp_number': cp.toString()
}),
);
res = json.decode(utf8.decode(response.bodyBytes));
//print("~~~~ api1 result $res ~~~~");
//print("-----@@@@@ checkin $_res -----");
if (response.statusCode == 201) {
//print("~~~~ image from api1 ${res["checkinimage"].toString()} ~~~~");
//print('---- toekn is ${token} -----');
//print("~~~~ token is $token ~~~~");
//print("~~~~ before callling api2 ~~~~");
final http.Response response2 = await http.post(
Uri.parse(url),
try {
final http.Response response = await http.post(
Uri.parse(url1),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Token $token'
},
// 'id', 'user', 'goalimage', 'goaltime', 'team_name', 'event_code','cp_number'
body: jsonEncode(<String, String>{
'user': userId.toString(),
'team_name': teamname,
'event_code': eventcode,
'checkinimage': im1_64,
'checkintime': checkinTime,
'cp_number': cp.toString()
}),
);
res = json.decode(utf8.decode(response.bodyBytes));
//print("~~~~ api1 result $res ~~~~");
//print("-----@@@@@ checkin $_res -----");
if (response.statusCode == 201) {
//print("~~~~ image from api1 ${res["checkinimage"].toString()} ~~~~");
//print('---- toekn is ${token} -----');
//print("~~~~ token is $token ~~~~");
//print("~~~~ before callling api2 ~~~~");
final http.Response response2 = await http.post(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
'team_name': teamname,
'cp_number': cp.toString(),
'event_code': eventcode,
'image': res["checkinimage"].toString().replaceAll(
'http://localhost:8100', 'http://rogaining.sumasen.net')
}),
);
var vv = jsonEncode(<String, String>{
'team_name': teamname,
'cp_number': cp.toString(),
'event_code': eventcode,
'image': res["checkinimage"].toString().replaceAll(
'http://localhost:8100', 'http://rogaining.sumasen.net')
}),
);
var vv = jsonEncode(<String, String>{
'team_name': teamname,
'cp_number': cp.toString(),
'event_code': eventcode,
'image': res["checkinimage"].toString().replaceAll(
'http://localhost:8100', 'http://rogaining.sumasen.net')
});
//print("~~~~ api 2 values $vv ~~~~");
//print("--json-- $vv");
//print("--- checnin response ${response2.statusCode}----");
if (response2.statusCode == 200) {
res = json.decode(utf8.decode(response2.bodyBytes));
//print('----checkin res _res : $res ----');
if (res["status"] == "ERROR") {
Get.snackbar("エラーがおきました", res["detail"]);
});
//print("~~~~ api 2 values $vv ~~~~");
//print("--json-- $vv");
//print("--- checnin response ${response2.statusCode}----");
if (response2.statusCode == 200) {
res = json.decode(utf8.decode(response2.bodyBytes));
//print('----checkin res _res : $res ----');
if (res["status"] == "ERROR") {
Get.snackbar("エラーがおきました", res["detail"]);
}
}
} else {
Get.snackbar("サーバーエラーがおきました", "サーバーと通信できませんでした");
}
} else {
Get.snackbar("エラーがおきました", "サーバーに更新できませんでした");
} catch( e ) {
print('Error in makeCheckpoint: $e');
Get.snackbar("通信エラーがおきました", "サーバーと通信できませんでした");
}
}
} else {

View File

@ -77,6 +77,9 @@ class LocationController extends GetxController {
// エラーが発生した場合は、locationMarkerPositionStreamControllerにエラーを追加します。
// ストリームが一時停止中の場合は、ストリームを再開します。
//
// 2024-4-8 Akira : See 2809
// stopPositionStreamメソッドを追加して、既存のストリームをキャンセルするようにしました。また、ストリームが完了したらnullに設定し、エラー発生時にストリームをキャンセルするようにしました。
//
void startPositionStream() async {
// Check for location service and permissions before starting the stream
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
@ -161,8 +164,7 @@ class LocationController extends GetxController {
await positionStream?.cancel();
positionStream =
Geolocator.getPositionStream(locationSettings: locationOptions).listen(
positionStream = Geolocator.getPositionStream(locationSettings: locationOptions).listen(
(Position? position) {
if (position != null) {
final LocationMarkerPosition locationMarkerPosition =
@ -170,14 +172,18 @@ class LocationController extends GetxController {
latitude: position.latitude,
longitude: position.longitude,
accuracy: position.accuracy);
locationMarkerPositionStreamController.add(locationMarkerPosition);
} else {
locationMarkerPositionStreamController.add(null);
}
},
onError: (e) {
locationMarkerPositionStreamController.addError(e);
},
locationMarkerPositionStreamController.add(locationMarkerPosition);
} else {
locationMarkerPositionStreamController.add(null);
}
},
onError: (e) {
locationMarkerPositionStreamController.addError(e);
},
onDone: () {
positionStream = null; // ストリームが完了したらnullに設定
},
cancelOnError: true // エラー発生時にストリームをキャンセル
);
// Resume stream if it was paused previously
@ -192,10 +198,14 @@ class LocationController extends GetxController {
// positionStreamが存在する場合、ストリームを一時停止します。
// isStreamPausedフラグをtrueに設定します。
//
void stopPositionStream() {
void stopPositionStream() async {
if (positionStream != null) {
positionStream!.pause();
isStreamPaused = true;
// updated Akira 2024-4-8
await positionStream!.cancel();
positionStream = null;
//positionStream!.pause();
//isStreamPaused = true;
}
}

View File

@ -0,0 +1,9 @@
import 'package:flutter/material.dart';
class CustomIcons {
static const _fontFamily = 'CustomIcons';
static const IconData gps_signal_low = IconData(0xe900, fontFamily: _fontFamily);
static const IconData gps_signal_middle = IconData(0xe913, fontFamily: _fontFamily);
static const IconData gps_signal_high = IconData(0xe91d, fontFamily: _fontFamily);
}

View File

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rogapp/widgets/GameState/Colors.dart';
import 'package:rogapp/pages/destination/destination_controller.dart';
//import 'package:rogapp/widgets/custom_icons.dart';
import 'package:rogapp/widgets/custom_icons.dart';
enum GPSStatus { high, middle, low }
@ -30,19 +30,19 @@ class GpsSignalStrengthIndicator extends StatelessWidget {
case 'high':
backgroundColor = Colors.green;
iconData = Icons.signal_cellular_alt;
//iconData = CustomIcons.gps_signal_high_0;
// iconData = CustomIcons.gps_signal_high;
text = 'GPS 強';
break;
case 'medium':
backgroundColor = Colors.orange;
iconData = Icons.signal_cellular_alt_2_bar;
//iconData = CustomIcons.gps_signal_middle_0;
// iconData = CustomIcons.gps_signal_middle;
text = 'GPS 中';
break;
default:
backgroundColor = Colors.grey; // Fallback color
iconData = Icons.signal_cellular_connected_no_internet_4_bar;
//iconData = CustomIcons.gps_signal_low_0;
// iconData = CustomIcons.gps_signal_low;
text = 'GPS 弱';
}

View File

@ -18,6 +18,21 @@ import 'package:rogapp/widgets/bottom_sheet_new.dart';
import 'package:rogapp/widgets/current_position_widget.dart';
import 'package:rogapp/widgets/game_state_view.dart';
// map_widget.dartファイルは、アプリ内の地図表示を担当するウィジェットを定義しています。以下に、主要な部分を解説します。
// 地図表示に関連する主要な機能を提供しています。以下のような機能が含まれています。
//
// 地図の表示と操作
// マーカーの表示とカスタマイズ
// ルートの表示
// 現在位置の表示
// アイドル状態の処理
// ローディングインジケーターの表示
// ゲーム状態の表示
// 現在位置ボタンの表示
// StatefulWidgetを継承したクラスで、地図表示のメインウィジェットです。
//
class MapWidget extends StatefulWidget {
MapWidget({Key? key}) : super(key: key);
@ -25,16 +40,23 @@ class MapWidget extends StatefulWidget {
State<MapWidget> createState() => _MapWidgetState();
}
// MapWidgetの状態を管理するクラスです。
//
class _MapWidgetState extends State<MapWidget> {
final IndexController indexController = Get.find<IndexController>();
// IndexControllerのインスタンスを保持します。
final DestinationController destinationController =
Get.find<DestinationController>();
// DestinationControllerのインスタンスを保持します。
final LocationController locationController = Get.find<LocationController>();
// LocationControllerのインスタンスを保持します。
StreamSubscription? subscription;
Timer? _timer;
StreamSubscription? subscription; // 地図イベントの購読を保持します。
Timer? _timer; // アイドル状態のタイマーを保持します。
// 地図上のマーカーのUIを生成するメソッドです。
// GeoJSONFeatureを受け取り、マーカーのUIを返します。
//
Widget getMarkerShape(GeoJSONFeature i, BuildContext context) {
GeoJSONMultiPoint p = i.geometry as GeoJSONMultiPoint;
//print("lat is ${p.geoSerie!.geoPoints[0].latitude} and lon is ${p.geoSerie!.geoPoints[0].longitude}");
@ -155,6 +177,9 @@ class _MapWidgetState extends State<MapWidget> {
);
}
// ルートポイントをLatLngのリストに変換するメソッドです。
// IndexControllerのroutePointsからLatLngのリストを生成しています。
//
List<LatLng>? getPoints() {
//print("##### --- route point ${indexController.routePoints.length}");
List<LatLng> pts = [];
@ -165,21 +190,31 @@ class _MapWidgetState extends State<MapWidget> {
return pts;
}
// ウィジェットの初期化時に呼び出されるメソッドです。
// _startIdleTimerメソッドを呼び出して、アイドル状態のタイマーを開始します。
//
@override
void initState() {
super.initState();
_startIdleTimer();
_startIdleTimer(); // _startIdleTimerメソッドを呼び出してアイドル状態のタイマーを開始しています。
}
// アイドル状態のタイマーを開始するメソッドです。
// 一定時間後に_centerMapOnUserメソッドを呼び出すようにタイマーを設定しています。
//
void _startIdleTimer() {
_timer = Timer(const Duration(milliseconds: (1000 * 10)), _centerMapOnUser);
}
// アイドル状態のタイマーをリセットするメソッドです。
//
void _resetTimer() {
_timer?.cancel();
_startIdleTimer();
}
// 地図をユーザーの現在位置に中央揃えするメソッドです。
//
void _centerMapOnUser() {
assert(() {
print("showBottomSheet ${destinationController.shouldShowBottomSheet}");
@ -192,6 +227,13 @@ class _MapWidgetState extends State<MapWidget> {
//}
}
// ウィジェットのUIを構築するメソッドです。
// FlutterMapウィジェットを使用して地図を表示します。
// layersプロパティに、ベースレイヤー、ルートレイヤー、現在位置レイヤー、マーカーレイヤーを設定します。
// PopupControllerを使用して、ポップアップの制御を行います。
// IndexControllerのisLoading変数に基づいて、ローディングインジケーターを表示します。
// GameStateWidgetとCurrentPositionウィジェットを重ねて表示します。
//
@override
Widget build(BuildContext context) {
// print(
@ -200,18 +242,22 @@ class _MapWidgetState extends State<MapWidget> {
final PopupController popupController = PopupController();
return Stack(
children: [
// IndexControllerのisLoading変数に基づいて、ローディングインジケーターを表示します。
// isLoadingがtrueの場合はCircularProgressIndicatorを表示し、falseの場合は地図を表示します。
Obx(() => indexController.isLoading.value == true
? const Padding(
padding: EdgeInsets.only(top: 60.0),
child: CircularProgressIndicator(),
)
: FlutterMap(
// 地図の表示を担当
mapController: indexController.mapController,
options: MapOptions(
// 地図の初期設定(最大ズームレベル、初期位置、ズームレベルなど)を行っています。
maxZoom: 18.4,
onMapReady: () {
// print("Map controller ready!!"); ... working corretly
indexController.isMapControllerReady.value = true; // Added Akira,2024-4-6 for #2800
// print("Map controller ready!!"); ... working corretly
indexController.isMapControllerReady.value = true; // Added Akira,2024-4-6 for #2800
subscription = indexController.mapController.mapEventStream
.listen((MapEvent mapEvent) {
@ -235,6 +281,7 @@ class _MapWidgetState extends State<MapWidget> {
InteractiveFlag.pinchZoom | InteractiveFlag.drag,
onPositionChanged: (MapPosition pos, hasGesture) {
// 地図の位置が変更された際の処理を行います。
//print("map position changed ${pos.center!.latitude}");
if (hasGesture) {
_resetTimer();
@ -249,9 +296,10 @@ class _MapWidgetState extends State<MapWidget> {
Obx(
() => indexController.routePointLenght > 0
? PolylineLayer(
// ルートの表示を担当
polylines: [
Polyline(
points: getPoints()!,
points: getPoints()!, // ルートのポイントを設定しています。
strokeWidth: 6.0,
color: Colors.indigo),
],
@ -259,11 +307,14 @@ class _MapWidgetState extends State<MapWidget> {
: Container(),
),
CurrentLocationLayer(
// 現在位置の表示を担当
positionStream: locationController
.locationMarkerPositionStreamController.stream,
// locationMarkerPositionStreamController.streamを設定して、現在位置の更新を監視しています。
alignDirectionOnUpdate: AlignOnUpdate.never,
turnOnHeadingUpdate: TurnOnHeadingUpdate.never,
style: const LocationMarkerStyle(
// styleプロパティで、現在位置のマーカーのスタイルを設定しています。
marker: DefaultLocationMarker(
child: Icon(
Icons.navigation,
@ -277,6 +328,7 @@ class _MapWidgetState extends State<MapWidget> {
indexController.locations.isNotEmpty &&
indexController.locations[0].features.isNotEmpty
? MarkerLayer(
// マーカーの表示を担当
markers:
indexController.locations[0].features.map((i) {
//print("i si ${i.properties!['location_id']}");
@ -293,13 +345,29 @@ class _MapWidgetState extends State<MapWidget> {
point: LatLng(
p.coordinates[0][1], p.coordinates[0][0]),
child: getMarkerShape(i, context));
// マーカーのUIを生成しています。
// マーカーのアイコン、ラベル、色などをカスタマイズしています。
}).toList(),
)
: const Center(child: CircularProgressIndicator()),
],
)),
const Positioned(top: 0, left: 0, child: GameStateWidget()),
const Positioned(bottom: 10, right: 10, child: CurrentPosition())
// ゲーム状態の表示を担当。ゲームの状態(開始、終了など)を表示するカスタムウィジェットです。
const Positioned(bottom: 10, right: 10, child: CurrentPosition()),
// 現在位置ボタンの表示を担当。現在位置に移動するためのボタンを表示するカスタムウィジェットです。
StreamBuilder<LocationMarkerPosition?>(
stream: locationController.locationMarkerPositionStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
print("Display current marker");
} else {
print("Not display current marker");
}
return Container();
},
)
// const Positioned(
// bottom: 10,
// left: 0,