temporary update

This commit is contained in:
2024-09-08 18:16:51 +09:00
parent 2c0bb06e74
commit e37c4ceebd
32 changed files with 1235 additions and 1189 deletions

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:io';
//import 'dart:convert';
//import 'dart:developer';
import 'package:flutter_map/flutter_map.dart';
import 'package:gifunavi/model/gps_data.dart';
//import 'package:gifunavi/pages/home/home_page.dart';
import 'package:gifunavi/utils/database_gps.dart';
@ -198,6 +199,16 @@ void _showEventSelectionWarning() {
);
}
// main.dart の上部に追加
const bool isDebugMode = true; // リリース時にfalseに変更
// 各ファイルで使用
void debugLog(String message) {
if (isDebugMode) {
debugPrint('DEBUG: $message');
}
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
@ -216,10 +227,7 @@ void main() async {
};
try {
await initServices();
runApp(const ProviderScope(child: MyApp()));
}catch(e, stackTrace){
print('Error during initialization: $e');
@ -230,33 +238,89 @@ void main() async {
}
}
Future <void> _initializeControllers() async {
final stopwatch = Stopwatch()..start();
const timeout = Duration(seconds: 30); // タイムアウト時間を10秒から30秒に延長
try {
while (!_areAllControllersRegistered()) {
if (stopwatch.elapsed > timeout) {
throw TimeoutException('Controller initialization timed out');
}
await Future.delayed(const Duration(milliseconds: 100));
}
final LocationController _locationController = Get.find<LocationController>();
final IndexController _indexController = Get.find<IndexController>();
final DestinationController _destinationController = Get.find<DestinationController>();
final PermissionController _permissionController = Get.find<PermissionController>();
print('All controllers initialized successfully');
} catch (e) {
print('Error initializing controllers: $e');
_handleInitializationError();
}
}
bool _areAllControllersRegistered() {
return Get.isRegistered<LocationController>() &&
Get.isRegistered<IndexController>() &&
Get.isRegistered<DestinationController>() &&
Get.isRegistered<PermissionController>();
}
void _handleInitializationError() {
// エラーハンドリングのロジックをここに実装
// 例: エラーダイアログの表示、アプリの再起動など
print("_handleInitializationError");
}
Future<void> initServices() async {
print('Starting services ...');
try {
//await Get.putAsync(() => ApiService().init());
await _initApiService();
await Get.putAsync(() => ApiService().init());
//await _initApiService();
debugPrint("1: start ApiService");
// コントローラーを初期化
Get.put(IndexController(apiService: Get.find<ApiService>()), permanent: true);
Get.put(SettingsController(), permanent: true);
Get.put(DestinationController(), permanent: true);
Get.put(LocationController(), permanent: true);
if (Platform.isIOS ) {
// コントローラーを初期化
/*
Get.put(IndexController(apiService: Get.find<ApiService>()), permanent: true);
Get.put(SettingsController(), permanent: true);
Get.put(DestinationController(), permanent: true);
Get.put(LocationController(), permanent: true);
*/
// すべてのコントローラーとサービスを非同期で初期化
Get.lazyPut(() => IndexController(apiService: Get.find<ApiService>()));
debugPrint("2: start IndexController");
debugPrint("2: Controllers initialized");
/*
// すべてのコントローラーとサービスを非同期で初期化
Get.lazyPut(() => IndexController(apiService: Get.find<ApiService>()));
debugPrint("2: start IndexController");
Get.lazyPut(() => MapController());
debugPrint("2: start MapController");
// その他のコントローラーを遅延初期化
Get.lazyPut(() => SettingsController());
debugPrint("2: start SettingsController");
Get.lazyPut(() => DestinationController());
debugPrint("3: start DestinationController");
Get.lazyPut(() => LocationController());
debugPrint("4: start LocationController");
// その他のコントローラーを遅延初期化
Get.lazyPut(() => SettingsController());
debugPrint("2: start SettingsController");
Get.lazyPut(() => DestinationController());
debugPrint("3: start DestinationController");
Get.lazyPut(() => LocationController());
debugPrint("4: start LocationController");
*/
//await _initializeControllers();
debugPrint("2: Controllers initialized");
}else {
// すべてのコントローラーとサービスを非同期で初期化
Get.lazyPut(() => IndexController(apiService: Get.find<ApiService>()));
debugPrint("2: start IndexController");
// その他のコントローラーを遅延初期化
Get.lazyPut(() => SettingsController());
debugPrint("2: start SettingsController");
Get.lazyPut(() => DestinationController());
debugPrint("3: start DestinationController");
Get.lazyPut(() => LocationController());
debugPrint("4: start LocationController");
Get.lazyPut(() => PermissionController());
}
// 非同期処理を並列実行
await Future.wait([
@ -267,10 +331,11 @@ Future<void> initServices() async {
print('=== 5. Initialized TimeZone...');
print('=== 6. CacheProvider started...');
Get.put(PermissionController());
await _checkPermissions();
debugPrint("7: start PermissionController");
//await PermissionController.checkAndRequestPermissions();
}catch(e){
print('Error initializing : $e');
}
@ -294,7 +359,17 @@ Future<void> _initCacheProvider() async {
}
Future<void> _checkPermissions() async {
await PermissionController.checkAndRequestPermissions();
int attempts = 0;
while (Get.context == null && attempts < 10) {
await Future.delayed(const Duration(milliseconds: 100));
attempts++;
}
if (Get.context != null) {
await PermissionController.checkAndRequestPermissions();
} else {
print('Context is still null, cannot check permissions');
}
//await PermissionController.checkAndRequestPermissions(); // main._checkPermissions
}
Future<void> _initApiService() async {
@ -474,7 +549,9 @@ Future<void> startBackgroundTracking() async {
try {
// 位置情報の権限が許可されているかを確認
await PermissionController.checkAndRequestPermissions();
WidgetsBinding.instance.addPostFrameCallback((_) {
PermissionController.checkAndRequestPermissions();
});
} catch (e) {
print('Error starting background tracking: $e');
}
@ -569,10 +646,29 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
Future <void> _initializeControllers() async {
while (!Get.isRegistered<LocationController>() ) {
print("LocationController is not up... ");
await Future.delayed(const Duration(milliseconds: 100));
}
while (!Get.isRegistered<IndexController>() ) {
print("IndexController is not up...");
await Future.delayed(const Duration(milliseconds: 100));
}
while (!Get.isRegistered<DestinationController>() ) {
print("DestinationController is not up...");
await Future.delayed(const Duration(milliseconds: 100));
}
/*
while (!Get.isRegistered<PermissionController>() ) {
print("PermissionController is not up... ");
await Future.delayed(const Duration(milliseconds: 100));
}
*/
while (!Get.isRegistered<LocationController>() ||
!Get.isRegistered<IndexController>() ||
!Get.isRegistered<DestinationController>() ||
!Get.isRegistered<PermissionController>()) {
!Get.isRegistered<DestinationController>() ) {
print("LocationController status = Get.isRegistered<LocationController>() ");
await Future.delayed(const Duration(milliseconds: 100));
}
@ -580,7 +676,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
_locationController = Get.find<LocationController>();
_indexController = Get.find<IndexController>();
_destinationController = Get.find<DestinationController>();
_permissionController = Get.find<PermissionController>();
//_permissionController = Get.find<PermissionController>();
_isControllerInitialized = true;
}
@ -785,15 +881,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
@override
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
try {
if (!Get.isRegistered<IndexController>()) {
_indexController = Get.find<IndexController>();
}
if (!Get.isRegistered<LocationController>()) {
_locationController = Get.find<LocationController>();
}
if (!Get.isRegistered<DestinationController>()) {
_destinationController = Get.find<DestinationController>();
}
await _initializeControllers();
switch (state) {
case AppLifecycleState.resumed:
@ -835,6 +923,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
}
Future<void> _onResumed() async {
debugPrint("==(Status Changed)==> RESUMED");
try {
if (!_isControllerInitialized) {
@ -865,43 +954,76 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
}
Future<void> _onInactive() async {
debugPrint("==(Status Changed)==> INACTIVE");
if (Platform.isIOS && !_destinationController.isRunningBackgroundGPS) {
debugPrint(" ==(Status Changed)==> INACTIVE. 非アクティブ処理。");
_locationController.stopPositionStream();
_destinationController.isRunningBackgroundGPS = true;
await startBackgroundTracking();
} else if (Platform.isAndroid && !_destinationController.isRunningBackgroundGPS) {
// Android特有の処理があれば追加
debugPrint(" ==(Status Changed)==> INACTIVE. 非アクティブ処理。");
}else{
debugPrint("==(Status Changed)==> INACTIVE 不明状態");
try {
debugPrint("==(Status Changed)==> INACTIVE");
if (!_isControllerInitialized) {
await _initializeControllers();
}
if (Platform.isIOS && !_destinationController.isRunningBackgroundGPS) {
debugPrint(" ==(Status Changed)==> INACTIVE. 非アクティブ処理。");
_locationController.stopPositionStream();
_destinationController.isRunningBackgroundGPS = true;
await startBackgroundTracking();
} else if (Platform.isAndroid &&
!_destinationController.isRunningBackgroundGPS) {
// Android特有の処理があれば追加
debugPrint(" ==(Status Changed)==> INACTIVE. 非アクティブ処理。");
} else {
debugPrint("==(Status Changed)==> INACTIVE 不明状態");
}
await saveGameState();
} catch (e) {
print('Error in _onInactive: $e');
}
await saveGameState();
}
Future<void> _onPaused() async {
debugPrint(" ==(Status Changed)==> PAUSED. バックグラウンド処理。");
if (Platform.isAndroid && !_destinationController.isRunningBackgroundGPS) {
debugPrint(" ==(Status Changed)==> PAUSED. Android バックグラウンド処理。");
_locationController.stopPositionStream();
const platform = MethodChannel('location');
await platform.invokeMethod('startLocationService');
_destinationController.isRunningBackgroundGPS = true;
try {
debugPrint(" ==(Status Changed)==> PAUSED. バックグラウンド処理。");
if (!_isControllerInitialized) {
await _initializeControllers();
}
if (Platform.isAndroid &&
!_destinationController.isRunningBackgroundGPS) {
debugPrint(
" ==(Status Changed)==> PAUSED. Android バックグラウンド処理。");
_locationController.stopPositionStream();
const platform = MethodChannel('location');
await platform.invokeMethod('startLocationService');
_destinationController.isRunningBackgroundGPS = true;
}
await saveGameState();
} catch (e) {
print('Error in _onPaused: $e');
}
await saveGameState();
}
Future<void> _onDetached() async {
debugPrint(" ==(Status Changed)==> DETACHED アプリは終了します。");
await saveGameState();
// アプリ終了時の追加処理
try {
if (!_isControllerInitialized) {
await _initializeControllers();
}
debugPrint(" ==(Status Changed)==> DETACHED アプリは終了します。");
await saveGameState();
// アプリ終了時の追加処理
} catch (e) {
print('Error in _onDetached: $e');
}
}
Future<void> _onHidden() async {
debugPrint(" ==(Status Changed)==> Hidden アプリが隠れた");
await saveGameState();
try {
if (!_isControllerInitialized) {
await _initializeControllers();
}
debugPrint(" ==(Status Changed)==> Hidden アプリが隠れた");
await saveGameState();
} catch (e) {
print('Error in _onHidden: $e');
}
}
@override
@ -929,7 +1051,4 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
enableLog: true,
);
}
}

View File

@ -11,6 +11,8 @@ class Entry {
final DateTime? date;
final int zekkenNumber; // 新しく追加
final String owner;
bool hasParticipated;
bool hasGoaled;
Entry({
required this.id,
@ -20,6 +22,8 @@ class Entry {
required this.date,
required this.zekkenNumber,
required this.owner,
this.hasParticipated = false,
this.hasGoaled = false,
});
factory Entry.fromJson(Map<String, dynamic> json) {

View File

@ -179,7 +179,7 @@ class _AuthPageState extends ConsumerState<AuthPage> {
Widget buildControlls() {
if (_isLoginProgress) {
return const Center(
child: CircularProgressIndicator(),
child: CircularProgressIndicator(), // Auth_page
);
}

View File

@ -418,7 +418,7 @@ class CameraPage extends StatelessWidget {
},
child: Text("finish_goal".tr))
: const Center(
child: CircularProgressIndicator(),
child: CircularProgressIndicator(), // Camera page
)
: Container())
],

View File

@ -1126,7 +1126,7 @@ class DestinationController extends GetxController {
}
Future<String?> _saveImageToGallery(String imagePath) async {
final status = await PermissionController.checkStoragePermission();
final status = await PermissionController.checkStoragePermission(); // destinationController._saveImageToGallery
if(!status){
await PermissionController.requestStoragePermission();
}
@ -1333,12 +1333,6 @@ class DestinationController extends GetxController {
void onInit() async {
super.onInit();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await PermissionController.checkAndRequestPermissions();
});
startGPSCheckTimer();
// MapControllerの初期化完了を待機するフラグを設定
@ -1645,6 +1639,7 @@ class DestinationController extends GetxController {
final token = indexController.userToken;
if (token != null && token.isNotEmpty) {
await indexController.loadUserDetailsForToken(token);
await Future.delayed(Duration(milliseconds: 500)); // 短い遅延を追加
fixMapBound(token);
}else {
Get.toNamed(AppPages.LOGIN)!.then((value) {
@ -1665,6 +1660,9 @@ class DestinationController extends GetxController {
});
}
// MapControllerの準備が整うまで待機
await indexController.waitForMapControllerReady();
// 地図のイベントリスナーを設定
indexController.mapController.mapEventStream.listen((MapEvent mapEvent) {
if (mapEvent is MapEventMoveEnd) {
@ -1677,11 +1675,12 @@ class DestinationController extends GetxController {
// 地図の境界を修正する関数です。
//
void fixMapBound(String token) {
Future<void> fixMapBound(String token) async {
await indexController.waitForMapControllerReady();
//String _token = indexController.currentUser[0]["token"];
indexController.switchPage(AppPages.INDEX);
if (isMapControllerReady) {
if (indexController.isMapControllerReady.value) {
LocationService.getLocationsExt(token).then((value) {
if (value != null) {
//print("--- loc ext is - $value ----");
@ -1743,11 +1742,18 @@ class DestinationController extends GetxController {
// 地図を現在位置に中央揃えする関数です。
//
void centerMapToCurrentLocation() {
final indexController = Get.find<IndexController>();
if (indexController.isMapControllerReady.value && indexController.isMapControllerReady.value) {
// ... 現在位置へのセンタリングのロジック ...
indexController.mapController.move(LatLng(currentLat, currentLon), 17.0);
} else {
debugPrint('Map controller is not ready for centering');
}
//print("center is ${currentLat}, ${currentLon}");
// Akira ... 状況によって呼ぶか呼ばないか
if (currentLat != 0 || currentLon != 0) {
indexController.mapController.move(LatLng(currentLat, currentLon), 17.0);
}
//if (currentLat != 0 || currentLon != 0) {
//indexController.mapController.move(LatLng(currentLat, currentLon), 17.0);
//}
}
// 接続状態が変更されたときに呼び出される関数です。
@ -1920,7 +1926,7 @@ class DestinationController extends GetxController {
barrierDismissible: false,
builder: (BuildContext context) {
return const Center(
child: CircularProgressIndicator(),
child: CircularProgressIndicator(), // Destination COntroller
);
});
}

View File

@ -27,6 +27,8 @@ class EntryController extends GetxController {
final currentEntry = Rx<Entry?>(null);
final isLoading = true.obs;
final activeEvents = <Event>[].obs; //有効なイベントリスト
@override
void onInit() async {
super.onInit();
@ -52,14 +54,15 @@ class EntryController extends GetxController {
fetchTeams(),
fetchCategories(),
]);
updateActiveEvents(); // イベント取得後にアクティブなイベントを更新
if (Get.arguments != null && Get.arguments['entry'] != null) {
currentEntry.value = Get.arguments['entry'];
initializeEditMode(currentEntry.value!);
} else {
// 新規作成モードの場合、最初のイベントを選択
if (events.isNotEmpty) {
selectedEvent.value = events.first;
selectedDate.value = events.first.startDatetime;
selectedEvent.value = activeEvents.first;
selectedDate.value = activeEvents.first.startDatetime;
}
}
} catch(e) {
@ -70,6 +73,10 @@ class EntryController extends GetxController {
}
}
void updateActiveEvents() {
final now = tz.TZDateTime.now(tz.getLocation('Asia/Tokyo'));
activeEvents.assignAll(events.where((event) => event.deadlineDateTime.isAfter(now)));
}
void initializeEditMode(Entry entry) {
currentEntry.value = entry;
@ -143,6 +150,7 @@ class EntryController extends GetxController {
deadlineDateTime: deadlineDateTime,
);
}).toList());
updateActiveEvents();
} catch (e) {
print('Error fetching events: $e');
Get.snackbar('Error', 'Failed to fetch events');

View File

@ -39,10 +39,10 @@ class EntryDetailPage extends GetView<EntryController> {
children: [
_buildDropdown<Event>(
label: 'イベント',
items: controller.events,
items: controller.activeEvents,
selectedId: controller.selectedEvent.value?.id,
onChanged: (eventId) => controller.updateEvent(
controller.events.firstWhere((e) => e.id == eventId)
controller.activeEvents.firstWhere((e) => e.id == eventId)
),
getDisplayName: (event) => event.eventName,
getId: (event) => event.id,

View File

@ -7,6 +7,8 @@ import 'package:gifunavi/pages/entry/entry_controller.dart';
import 'package:gifunavi/routes/app_pages.dart';
import 'package:timezone/timezone.dart' as tz;
import '../../model/entry.dart';
class EntryListPage extends GetView<EntryController> {
const EntryListPage({super.key});
@ -28,11 +30,35 @@ class EntryListPage extends GetView<EntryController> {
child: Text('表示するエントリーがありません。'),
);
}
final sortedEntries = controller.entries.toList()
..sort((b, a) => a.date!.compareTo(b.date!));
return ListView.builder(
itemCount: controller.entries.length,
itemCount: sortedEntries.length,
itemBuilder: (context, index) {
final entry = controller.entries[index];
final entry = sortedEntries[index];
final now = DateTime.now();
final isEntryInFuture = _compareDatesOnly(entry.date!, now) >= 0;
//final isEntryInFuture = entry.date!.isAfter(now) || entry.date!.isAtSameMomentAs(now);
Widget? leadingIcon;
if (!isEntryInFuture) {
if (entry.hasParticipated) {
if (entry.hasGoaled) {
leadingIcon =
const Icon(Icons.check_circle, color: Colors.green);
} else {
leadingIcon = const Icon(Icons.warning, color: Colors.yellow);
}
} else {
leadingIcon = const Icon(Icons.cancel, color: Colors.red);
}
}
return ListTile(
leading: leadingIcon,
title: Row(
children: [
Expanded(
@ -49,9 +75,16 @@ class EntryListPage extends GetView<EntryController> {
Text('ゼッケン: ${entry.zekkenNumber ?? "未設定"}'),
],
),
onTap: () =>
onTap: () {
if (isEntryInFuture) {
Get.toNamed(AppPages.ENTRY_DETAIL,
arguments: {'mode': 'edit', 'entry': entry}),
arguments: {'mode': 'edit', 'entry': entry});
} else if (entry.hasParticipated) {
Get.toNamed(AppPages.EVENT_RESULT, arguments: {'entry': entry});
} else {
_showNonParticipationDialog(context, entry);
}
}
);
},
);
@ -59,6 +92,11 @@ class EntryListPage extends GetView<EntryController> {
);
}
// 新しく追加するメソッド
int _compareDatesOnly(DateTime a, DateTime b) {
return DateTime(a.year, a.month, a.day).compareTo(DateTime(b.year, b.month, b.day));
}
String _formatDate(DateTime? date) {
if (date == null) {
return '日時未設定';
@ -66,6 +104,26 @@ class EntryListPage extends GetView<EntryController> {
final jstDate = tz.TZDateTime.from(date, tz.getLocation('Asia/Tokyo'));
return DateFormat('yyyy-MM-dd').format(jstDate);
}
void _showNonParticipationDialog(BuildContext context, Entry entry) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(entry.event.eventName),
content: Text('${_formatDate(entry.date)}\n\n不参加でした'),
actions: <Widget>[
TextButton(
child: const Text('閉じる'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
}
class EntryListPage_old extends GetView<EntryController> {
@ -85,7 +143,7 @@ class EntryListPage_old extends GetView<EntryController> {
),
body: Obx((){
if (controller.isLoading.value) {
return const Center(child: CircularProgressIndicator());
return const Center(child: CircularProgressIndicator()); // EntryList
}
// エントリーを日付昇順にソート

View File

@ -0,0 +1,155 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:gifunavi/model/entry.dart';
import 'package:gifunavi/pages/gps/gps_controller.dart';
import 'package:gifunavi/pages/history/history_controller.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import 'package:gifunavi/pages/gps/gps_controller.dart';
import 'package:gifunavi/pages/history/history_controller.dart';
import 'package:intl/intl.dart';
class EventResultPage extends StatefulWidget {
final Entry entry;
const EventResultPage({super.key, required this.entry});
@override
State<EventResultPage> createState() => _EventResultPageState();
}
class _EventResultPageState extends State<EventResultPage> {
late GpsController gpsController;
late HistoryController historyController;
@override
void initState() {
super.initState();
gpsController = Get.put(GpsController());
historyController = Get.put(HistoryController());
}
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: Text('${widget.entry.event.eventName} 結果'),
bottom: const TabBar(
tabs: [
Tab(text: 'ランキング'),
Tab(text: '走行経路'),
Tab(text: 'チェックポイント'),
],
),
),
body: TabBarView(
children: [
_buildFutureTab(), //_buildRankingTab(),
_buildFutureTab(), //_buildRouteTab(),
_buildFutureTab(), //_buildCheckpointTab(),
],
),
),
);
}
Widget _buildFutureTab() {
// ランキングの表示ロジックを実装
return const Center(child: Text('近日公開予定当面、HPを参照ください。'));
}
Widget _buildRankingTab() {
// ランキングの表示ロジックを実装
return const Center(child: Text('ランキング表示(実装が必要)'));
}
Widget _buildRouteTab() {
return Obx(() {
if (gpsController.gpsData.isEmpty) {
return const Center(child: CircularProgressIndicator());
}
return FlutterMap(
options: MapOptions(
center: LatLng(gpsController.gpsData[0].lat, gpsController.gpsData[0].lon),
zoom: 13.0,
),
children: [
TileLayer(
urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
subdomains: ['a', 'b', 'c'],
),
PolylineLayer(
polylines: [
Polyline(
points: gpsController.gpsData
.map((data) => LatLng(data.lat, data.lon))
.toList(),
color: Colors.red,
strokeWidth: 3.0,
),
],
),
MarkerLayer(
markers: gpsController.gpsData
.map((data) => Marker(
width: 80.0,
height: 80.0,
point: LatLng(data.lat, data.lon),
child: const Icon(Icons.location_on, color: Colors.red),
))
.toList(),
),
],
);
});
}
Widget _buildCheckpointTab() {
return Obx(() {
if (historyController.checkpoints.isEmpty) {
return const Center(child: Text('チェックポイント履歴がありません'));
}
return ListView.builder(
itemCount: historyController.checkpoints.length,
itemBuilder: (context, index) {
final checkpoint = historyController.checkpoints[index];
return ListTile(
title: Text('CP ${checkpoint.cp_number ?? 'Unknown'}'),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('通過時刻: ${_formatDateTime(checkpoint.checkintime)}'),
Text('チーム: ${checkpoint.team_name ?? 'Unknown'}'),
Text('イベント: ${checkpoint.event_code ?? 'Unknown'}'),
],
),
leading: checkpoint.image != null
? Image.file(
File(checkpoint.image!),
width: 50,
height: 50,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
print('Error loading image: $error');
return const Icon(Icons.error);
},
)
: const Icon(Icons.image_not_supported),
);
}
);
});
}
}
String _formatDateTime(int? microsecondsSinceEpoch) {
if (microsecondsSinceEpoch == null) return 'Unknown';
final dateTime = DateTime.fromMicrosecondsSinceEpoch(microsecondsSinceEpoch);
return DateFormat('yyyy-MM-dd HH:mm:ss').format(dateTime);
}

View File

@ -0,0 +1,22 @@
import 'package:get/get.dart';
import 'package:gifunavi/model/gps_data.dart';
import 'package:gifunavi/utils/database_gps.dart';
import 'package:gifunavi/pages/index/index_controller.dart';
class GpsController extends GetxController {
final gpsData = <GpsData>[].obs;
@override
void onInit() {
super.onInit();
loadGpsData();
}
void loadGpsData() async {
final teamName = Get.find<IndexController>().currentUser[0]["user"]['team_name'];
final eventCode = Get.find<IndexController>().currentUser[0]["user"]["event_code"];
GpsDatabaseHelper db = GpsDatabaseHelper.instance;
var data = await db.getGPSData(teamName, eventCode);
gpsData.value = data;
}
}

View File

@ -0,0 +1,22 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:gifunavi/model/rog.dart';
import 'package:gifunavi/utils/database_helper.dart';
class HistoryController extends GetxController {
final checkpoints = <Rog>[].obs; // Rog オブジェクトのリストに変更
@override
void onInit() {
super.onInit();
loadCheckpoints();
}
void loadCheckpoints() async {
DatabaseHelper db = DatabaseHelper.instance;
var data = await db.allRogianing();
checkpoints.value = data;
}
}

View File

@ -27,7 +27,7 @@ class _HistoryPageState extends State<HistoryPage> {
future: db.getDestinations(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
return const Center(child: CircularProgressIndicator()); //History page
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
@ -167,7 +167,7 @@ class CustomWidget extends StatelessWidget {
print('Error loading image path: ${snapshot.error}');
return const Icon(Icons.error);
} else {
return const CircularProgressIndicator();
return const CircularProgressIndicator(); // History page 2
}
},
);

View File

@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:io';
import 'package:get/get.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
@ -8,7 +9,6 @@ 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:gifunavi/model/destination.dart';
import 'package:gifunavi/model/entry.dart';
@ -34,6 +34,8 @@ import 'package:timezone/data/latest.dart' as tz;
import '../permission/permission.dart';
//enum MapState { initializing, ready, error }
class IndexController extends GetxController with WidgetsBindingObserver {
List<GeoJSONFeatureCollection> locations = <GeoJSONFeatureCollection>[].obs;
List<GeoJSONFeature> currentFeature = <GeoJSONFeature>[].obs;
@ -60,10 +62,16 @@ class IndexController extends GetxController with WidgetsBindingObserver {
var isCustomAreaSelected = false.obs;
RxBool isMapControllerReady = RxBool(false); // MapControllerの初期化状態を管理するフラグ
//final mapControllerReadyStream = Stream<bool>.value(false); // MapControllerの初期化状態を通知するためのストリーム
MapController mapController = MapController();
// 複数の状態変数_isLoading, _isMapInitialized, indexController.isMapControllerReadyを一元管理します。
//final Rx<MapState> mapState = MapState.initializing.obs;
final Completer<void> mapControllerCompleter = Completer<void>();
late MapController mapController; // = Rx<MapController?>(null);
final RxBool isMapControllerReady = false.obs; // MapControllerの初期化状態を管理するフラグ
//MapController mapController = MapController();
MapController rogMapController = MapController();
LogManager logManager = LogManager();
@ -96,7 +104,43 @@ class IndexController extends GetxController with WidgetsBindingObserver {
final selectedEventName = 'add_location'.tr.obs;
RxBool isLoadingLocations = true.obs;
RxBool isLoadingLocations = false.obs;
// ユーザーの参加状況を追跡する
final RxBool isUserParticipating = false.obs;
// ユーザーのゴール到達状況を追跡する
final RxBool hasUserReachedGoal = false.obs;
void initMapController() {
mapController = MapController();
// MapEventMoveEndイベントを使用して初期化完了を検出
mapController.mapEventStream.listen((event) {
if (event is MapEventMoveEnd && !isMapControllerReady.value) {
// MapEventMoveEndイベントが発生したら、MapControllerの準備完了とみなす
if (!isMapControllerReady.value) {
print('MapController is ready');
isMapControllerReady.value = true;
if (!mapControllerCompleter.isCompleted) {
mapControllerCompleter.complete();
}
}
}
});
}
// ユーザーの参加状況を更新するメソッド
void updateUserParticipationStatus(bool isParticipating) {
isUserParticipating.value = isParticipating;
update(); // GetX の update() メソッドを呼び出してUIを更新
}
// ユーザーのゴール到達状況を更新するメソッド
void updateUserGoalStatus(bool hasReachedGoal) {
hasUserReachedGoal.value = hasReachedGoal;
update(); // GetX の update() メソッドを呼び出してUIを更新
}
void setSelectedEventName(String eventName) {
selectedEventName.value = eventName;
@ -242,13 +286,14 @@ class IndexController extends GetxController with WidgetsBindingObserver {
void onInit() {
try {
super.onInit();
//mapController = MapController();
//ever(isMapControllerReady, (_) => _onMapControllerReady());
initMapController();
initConnectivity();
_connectivitySubscription = _connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
WidgetsBinding.instance.addPostFrameCallback((_) async {
await PermissionController.checkAndRequestPermissions();
});
WidgetsBinding.instance.addObserver(this);
_startLocationService(); // アプリ起動時にLocationServiceを開始する
@ -259,7 +304,7 @@ class IndexController extends GetxController with WidgetsBindingObserver {
tz.initializeTimeZones();
//teamController = Get.find<TeamController>();
loadLocations();
//loadLocations();
}catch(e,stacktrace){
print('Error in IndexController.onInit: $e');
@ -270,18 +315,33 @@ class IndexController extends GetxController with WidgetsBindingObserver {
}
}
void _onMapControllerReady() {
if (isMapControllerReady.value) {
loadLocations();
}
}
Future<void> loadLocations() async {
if (isLoadingLocations.value) return;
isLoadingLocations.value = true;
try {
await waitForMapControllerReady();
debugPrint('IndexController: Starting to load locations');
await waitForMapControllerReady(); // loadLocations
debugPrint('IndexController: Permission granted, loading locations');
String eventCode = currentUser.isNotEmpty ? currentUser[0]["user"]["event_code"] ?? "" : "";
await loadLocationsBound(eventCode);
debugPrint('IndexController: Locations loaded successfully');
} catch (e) {
print('Error loading locations: $e');
// エラーハンドリングを追加(例:スナックバーでユーザーに通知)
Get.snackbar('エラー', '位置情報の取得に失敗しました: ${e.toString()}');
} finally {
isLoadingLocations.value = false;
}
}
void _updateConnectionStatus(List<ConnectivityResult> results) {
@ -497,17 +557,20 @@ class IndexController extends GetxController with WidgetsBindingObserver {
//
Future<void> login(String email, String password) async {
try {
isLoading.value = true;
final value = await AuthService.login(email, password);
if (value.isNotEmpty && value['token'] != null) {
await changeUser(value);
await _initializeUserData();
Get.offAllNamed(AppPages.INDEX);
} else {
Get.snackbar('Login Failed', 'Invalid credentials');
Get.snackbar('ログイン失敗', 'メールアドレスまたはパスワードが間違っています');
}
} catch (e) {
print('Login error: $e');
Get.snackbar('Login Failed', 'An error occurred. Please try again.');
Get.snackbar('ログイン失敗', 'エラーが発生しました。もう一度お試しください。');
} finally {
isLoading.value = false;
}
}
@ -897,27 +960,28 @@ class IndexController extends GetxController with WidgetsBindingObserver {
return;
}
// MapControllerの初期化が完了するまで待機
await waitForMapControllerReady();
try {
// MapControllerの初期化が完了するまで待機
await waitForMapControllerReady();
// null チェックを追加
if (mapController.bounds == null) {
print("MapController bounds are null");
return;
}
// null チェックを追加
if (mapController.bounds == null) {
print("MapController bounds are null");
return;
}
// バウンドが有効かどうかを確認する
LatLngBounds bounds = mapController.bounds!;
if (!_isValidBounds(bounds)) {
print("MapController bounds are not valid");
return;
}
// バウンドが有効かどうかを確認する
LatLngBounds bounds = mapController.bounds!;
if (!_isValidBounds(bounds)) {
print("MapController bounds are not valid");
return;
}
locations.clear();
String cat = currentCat.isNotEmpty ? currentCat[0] : "";
if (currentCat.isNotEmpty && currentCat[0] == "-all-") {
cat = "";
}
locations.clear();
String cat = currentCat.isNotEmpty ? currentCat[0] : "";
if (currentCat.isNotEmpty && currentCat[0] == "-all-") {
cat = "";
}
/*
// Akira Add 2024-4-6
if( mapController.controller == null ) {
@ -928,13 +992,13 @@ class IndexController extends GetxController with WidgetsBindingObserver {
//
*/
//LatLngBounds bounds = mapController.bounds!;
//LatLngBounds bounds = mapController.bounds!;
currentBound.clear();
currentBound.add(bounds);
//isLoading.value = true; // ローディング状態をtrueに設定
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の場合のエラーハンドリングが不十分です。適切なエラーメッセージを表示するなどの処理を追加してください。
@ -950,7 +1014,7 @@ class IndexController extends GetxController with WidgetsBindingObserver {
bounds.southEast.latitude,
bounds.southEast.longitude,
cat,
eventCode
eventCode
);
/*
if (value == null) {
@ -979,7 +1043,8 @@ class IndexController extends GetxController with WidgetsBindingObserver {
"please zoom in",
backgroundColor: Colors.yellow,
colorText: Colors.white,
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),
);
@ -1011,12 +1076,15 @@ class IndexController extends GetxController with WidgetsBindingObserver {
locations.add(value);
}
*/
} catch ( e) {
} catch (e) {
print("Error in loadLocationsBound: $e");
// エラーが発生した場合のリトライ処理や適切なエラーメッセージの表示を行う
// 例えば、一定時間後に再度loadLocationsBound()を呼び出すなど
}
} catch (e) {
print('Error in loadLocationsBound: $e');
//Get.snackbar('エラー', '位置情報の読み込みに失敗しました。もう一度お試しください。');
}
}
// バウンドが有効かどうかを確認するヘルパーメソッド
@ -1029,20 +1097,44 @@ class IndexController extends GetxController with WidgetsBindingObserver {
bounds.southWest.latitude < bounds.northEast.latitude;
}
static const int maxRetries = 5;
int retryCount = 0;
//===Akira 追加:2024-4-6 #2800
// 要検討MapControllerの初期化が完了するまで待機していますが、タイムアウトを設定することを検討してください。
// 初期化に時間がかかりすぎる場合、ユーザーにわかりやすいメッセージを表示するようにしてください。
//
Future<void> waitForMapControllerReady() async {
if (!isMapControllerReady.value) {
await Future.doWhile(() async {
await Future.delayed(const Duration(milliseconds: 100));
return !isMapControllerReady.value;
});
if (isMapControllerReady.value) return;
//await isMapControllerReady.firstWhere((value) => value == true);
const timeout = Duration(seconds: 30);
try {
await Future.any([
mapControllerCompleter.future,
Future.delayed(timeout).then((_) => throw TimeoutException('MapController initialization timed out')),
]);
isMapControllerReady.value = true;
} catch (e) {
print('Warning waiting for MapController: $e');
// タイムアウト後もマップが機能している場合は、強制的に準備完了とマーク
if (!isMapControllerReady.value) {
isMapControllerReady.value = true;
if (!mapControllerCompleter.isCompleted) {
mapControllerCompleter.complete();
}
}
}
}
//===Akira 追加:2024-4-6 #2800
void retryMapInitialization() {
// マップの再初期化ロジック
initMapController();
// 他の必要な再初期化処理
}
void setBound(LatLngBounds bounds) {
currentBound.clear();
currentBound.add(bounds);

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:gifunavi/pages/destination/destination_controller.dart';
import 'package:gifunavi/pages/drawer/drawer_page.dart';
@ -34,22 +35,45 @@ class IndexPage extends StatefulWidget {
}
class _IndexPageState extends State<IndexPage> {
final IndexController indexController = Get.find<IndexController>();
final DestinationController destinationController = Get.find<DestinationController>();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await _ensureControllersAreInitialized();
await PermissionController.checkAndRequestPermissions();
});
_checkPermissionAndInitialize();
}
Future<void> _ensureControllersAreInitialized() async {
while (!Get.isRegistered<IndexController>() ||
!Get.isRegistered<DestinationController>() ||
!Get.isRegistered<LocationController>()) {
await Future.delayed(const Duration(milliseconds: 100));
}
Future<void> _checkPermissionAndInitialize() async {
// 位置情報の許可が得られた場合の処理
await _initializeMap();
}
Future<void> _initializeMap() async {
await indexController.loadLocations();
setState(() {}); // 状態を更新してUIを再構築
}
void _showLocationServiceDisabledOrDeniedError() {
Get.snackbar(
'エラー',
'位置情報サービスが無効か、許可されていません。設定を確認してください。',
duration: const Duration(seconds: 5),
snackPosition: SnackPosition.BOTTOM,
mainButton: TextButton(
child: const Text('設定'),
onPressed: () => Geolocator.openLocationSettings(),
),
);
}
void _showPermissionDeniedError() {
Get.snackbar(
'エラー',
'位置情報の許可が必要です。設定から許可してください。',
duration: const Duration(seconds: 5),
snackPosition: SnackPosition.BOTTOM,
);
}
void checkEventAndNavigate() async {
@ -112,11 +136,11 @@ class _IndexPageState extends State<IndexPage> {
void _showEventSelectionWarning() {
Get.dialog(
AlertDialog(
title: Text('警告'),
content: Text('イベントを選択してください。'),
title: const Text('警告'),
content: const Text('イベントを選択してください。'),
actions: [
TextButton(
child: Text('OK'),
child: const Text('OK'),
onPressed: () => Get.back(),
),
],
@ -124,24 +148,27 @@ class _IndexPageState extends State<IndexPage> {
);
}
// class IndexPage extends GetView<IndexController> {
// IndexPage({Key? key}) : super(key: key);
// IndexControllerとDestinationControllerのインスタンスを取得しています。
//
final LocationController locationController = Get.find<LocationController>();
final IndexController indexController = Get.find<IndexController>();
final DestinationController destinationController =
Get.find<DestinationController>();
// buildメソッドは、ウィジェットのUIを構築するメソッドです。
// ここでは、WillPopScopeウィジェットを使用して、端末の戻るボタンが押された際の動作を制御しています。
//
@override
Widget build(BuildContext context) {
/*
return PopScope(
canPop: false,
child: Scaffold(
*/
return Scaffold(
//
// Scaffoldウィジェットを使用して、アプリのメインページのレイアウトを構築しています。
//
@ -150,24 +177,8 @@ class _IndexPageState extends State<IndexPage> {
title: Obx(() => Text(indexController.selectedEventName.value)),
//title: Text("add_location".tr),
actions: [
// IconButton(
// onPressed: () {
// DatabaseService ds = DatabaseService();
// ds.updateDatabase();
// },
// icon: const Icon(Icons.ten_k_sharp)),
//
// AppBarには、タイトルとアクションアイコンが含まれています。
// アクションアイコンには、GPSデータの表示、履歴の表示、マップの更新、検索などの機能が含まれています。
//
IconButton(
onPressed: () async {
// GpsDatabaseHelper db = GpsDatabaseHelper.instance;
// List<GpsData> data = await db.getGPSData(
// indexController.currentUser[0]["user"]['team_name'],
// indexController.currentUser[0]["user"]["event_code"]);
// print("GPS data is ${data.length}");
Get.toNamed(AppPages.GPS);
},
icon: const Icon(Icons.telegram)),
@ -201,102 +212,22 @@ class _IndexPageState extends State<IndexPage> {
),
),
//CatWidget(indexController: indexController,),
//
// デバッグ時のみリロードボタンの横にGPS信号レベルの設定ボタンを設置し、
// タップすることでGPS信号の強弱をシミュレーションできるようにする
// Akira 2024-4-5
//
/*
Obx(() {
if (locationController.isSimulationMode) {
return DropdownButton<String>(
value: locationController.getSimulatedSignalStrength(),
onChanged: (value) {
//debugPrint("DropDown changed!");
locationController.setSimulatedSignalStrength(value!);
},
items: ['low', 'medium', 'high', 'real']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
} else {
return Container();
}
}),
*/
],
),
// bottomNavigationBar: BottomAppBar(
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: <Widget>[
// Obx(
// () => destinationController.isInRog.value == true
// ? IconButton(
// onPressed: () {},
// icon: const Icon(
// Icons.run_circle,
// size: 44,
// color: Colors.green,
// ))
// : IconButton(
// onPressed: () {},
// icon: const Icon(
// Icons.run_circle,
// size: 44,
// color: Colors.black12,
// )),
// ),
// Padding(
// padding:
// const EdgeInsets.only(right: 10.0, top: 4.0, bottom: 4.0),
// child: InkWell(
// child: Obx(() => destinationController
// .isGpsSelected.value ==
// true
// ? Padding(
// padding: const EdgeInsets.only(
// right: 10.0, top: 4.0, bottom: 4.0),
// child: InkWell(
// child: const Image(
// image:
// AssetImage('assets/images/route3_off.png'),
// width: 35,
// height: 35,
// ),
// onTap: () {
// //indexController.switchPage(AppPages.TRAVEL);
// },
// ),
// )
// : Padding(
// padding: const EdgeInsets.only(
// right: 10.0, top: 4.0, bottom: 4.0),
// child: InkWell(
// child: const Image(
// image:
// AssetImage('assets/images/route2_on.png'),
// width: 35,
// height: 35,
// ),
// onTap: () {
// //indexController.switchPage(AppPages.TRAVEL);
// },
// ),
// ))),
// ),
// ],
// ),
// ),
//
body: SafeArea(
child:Obx(() {
if (indexController.isLoadingLocations.value) {
return const Center(child: CircularProgressIndicator()); // Index Controller
} else {
return indexController.mode.value == 0
? const MapWidget()
: const ListWidget();
}
}),
),
// マップモードとリストモードを切り替えるためのボタンです。
//
floatingActionButton: FloatingActionButton(
onPressed: () {
indexController.toggleMode();
@ -312,22 +243,6 @@ class _IndexPageState extends State<IndexPage> {
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
//
// bodyには、SafeAreaウィジェットを使用して、画面の安全な領域内にUIを構築しています。
//
body: SafeArea(
child: Column(
children: [
Expanded(
child: Obx(
() => indexController.mode.value == 0
? const MapWidget()
: const ListWidget(),
))
],
),
),
),
);
);
}
}

View File

@ -182,7 +182,7 @@ class _LoginPageState extends State<LoginPage> {
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(40)),
child: const CircularProgressIndicator(),
child: const CircularProgressIndicator(), // Login page
)
: Column(
children: [
@ -199,24 +199,19 @@ class _LoginPageState extends State<LoginPage> {
.tr,
backgroundColor: Colors.red,
colorText: Colors.white,
icon: const Icon(
Icons
.assistant_photo_outlined,
size: 40.0,
color: Colors.blue),
snackPosition:
SnackPosition.TOP,
duration: const Duration(
seconds: 3),
icon: const Icon(Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
);
return;
}
indexController.isLoading.value =
true;
indexController.login(
emailController.text,
passwordController.text
);
try {
//indexController.isLoading.value = true;
indexController.login(emailController.text, passwordController.text);
} catch (e) {
print('Error during login: $e');
// エラーハンドリングは IndexController 内で行われるため、ここでは何もしない
}
},
color: Colors.indigoAccent[400],
shape: RoundedRectangleBorder(

View File

@ -2,23 +2,87 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'dart:async';
import 'package:shared_preferences/shared_preferences.dart';
class PermissionController {
static bool? _locationPermissionGranted;
static bool _isRequestingPermission = false;
static Completer<bool>? _permissionCompleter;
/*
bool? _isRequestingPermission=false;
final Completer<PermissionController> _permissionCompleter = Completer<PermissionController>();
*/
static Future<bool> checkAndRequestPermissions() async {
if (_isRequestingPermission) {
return _permissionCompleter!.future;
static Future<bool> checkAndRequestPermissions_new() async {
try {
if (_locationPermissionGranted != null) {
return _locationPermissionGranted!;
}
var serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
Get.snackbar(
'エラー', '位置情報サービスが無効です。設定から有効にしてください。');
// 位置情報サービスが無効の場合、ユーザーに有効化を促す
return false;
}
debugPrint("====> Geolocator.checkPermission");
var permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
Get.snackbar('エラー', '位置情報の許可が拒否されました。');
return false;
}
}
if (permission == LocationPermission.deniedForever) {
Get.snackbar('エラー',
'位置情報の許可が永久に拒否されました。設定から許可してください。');
return false;
}
_locationPermissionGranted = true;
return true;
} catch (e) {
print('Error checking permissions: $e');
Get.snackbar('エラー', '権限の確認中にエラーが発生しました。');
return false;
}
}
static void resetPermissionCache() {
_locationPermissionGranted = null;
}
static Future<bool> _requestLocationPermission() async {
BuildContext? context;
int attempts = 0;
const maxAttempts = 10;
debugPrint("====> _requestLocationPermission");
// コンテキストが利用可能になるまで待機するロジック
while (Get.context == null && attempts < maxAttempts) {
context = Get.context;
if (context == null) {
await Future.delayed(const Duration(milliseconds: 500));
attempts++;
}
}
_isRequestingPermission = true;
_permissionCompleter = Completer<bool>();
if (Get.context == null) {
print('Context is still null after waiting, cannot proceed with permission check');
return false;
}
try {
bool hasPermissions = await _checkLocationPermissions();
@ -36,18 +100,16 @@ class PermissionController {
SystemNavigator.pop();
}
}
return hasPermissions;
_isRequestingPermission = false;
_permissionCompleter!.complete(hasPermissions);
} catch (e) {
print('Error in permission request: $e');
_isRequestingPermission = false;
_permissionCompleter!.complete(false);
return false;
}
return _permissionCompleter!.future;
}
static Future<bool> _checkLocationPermissions() async {
final locationPermission = await Permission.location.status;
final whenInUsePermission = await Permission.locationWhenInUse.status;
@ -118,20 +180,30 @@ class PermissionController {
(whenInUsePermission == PermissionStatus.granted || alwaysPermission == PermissionStatus.granted);
}
static Future<bool> checkAndRequestPermissions_old() async {
static Future<bool> checkAndRequestPermissions() async {
/*
if (_isRequestingPermission) {
return _permissionCompleter!.future;
}
_isRequestingPermission = true;
_permissionCompleter = Completer<bool>();
*/
try {
if (_locationPermissionGranted != null) {
return _locationPermissionGranted!;
}
bool hasPermissions = await _checkLocationPermissions();
if (!hasPermissions) {
bool userAgreed = await showLocationDisclosure();
if (userAgreed) {
hasPermissions = await _requestAllLocationPermissions();
_locationPermissionGranted = true;
debugPrint("Finish checkAndRequestPermissions...");
return true;
} else {
print('User did not agree to location usage');
hasPermissions = false;
@ -140,15 +212,18 @@ class PermissionController {
}
}
_isRequestingPermission = false;
_permissionCompleter!.complete(hasPermissions);
//_isRequestingPermission = false;
//_permissionCompleter!.complete(hasPermissions);
} catch( e ) {
print('Error in permission request: $e');
_isRequestingPermission = false;
_permissionCompleter!.complete(false);
//_isRequestingPermission = false;
//_permissionCompleter!.complete(false);
}
debugPrint("Finish checkAndRequestPermissions...");
return _permissionCompleter!.future;
//return _permissionCompleter!.future;
return false;
}
static Future<void> requestAllLocationPermissions() async {
@ -171,10 +246,10 @@ class PermissionController {
print('Context is null, cannot show dialog');
return false;
}
if (Get.isDialogOpen ?? false) {
print('A dialog is already open');
return false;
}
//if (Get.isDialogOpen ?? false) {
// print('A dialog is already open');
// return false;
//}
try {
final result = await Get.dialog<bool>(

View File

@ -9,6 +9,12 @@ class SettingsController extends GetxController {
var autoReturnDisabled = false.obs;
final MapResetController mapResetController = Get.put(MapResetController());
@override
void onInit() {
super.onInit();
ever(timerDuration, (_) => resetIdleTimer());
}
void updateTimerDuration(int seconds) {
timerDuration.value = Duration(seconds: seconds);
}

View File

@ -1,3 +1,4 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get/get_navigation/src/routes/get_route.dart';
import 'package:gifunavi/pages/changepassword/change_password_page.dart';
@ -34,6 +35,10 @@ import 'package:gifunavi/pages/entry/event_entries_page.dart';
import 'package:gifunavi/pages/entry/event_entries_binding.dart';
import 'package:gifunavi/pages/register/user_detail_page.dart';
import 'package:gifunavi/pages/entry/event_result_page.dart';
import '../model/entry.dart';
part 'app_routes.dart';
class AppPages {
@ -70,6 +75,7 @@ class AppPages {
static const EVENT_ENTRY = Routes.EVENT_ENTRIES;
static const USER_DETAILS_EDIT = Routes.USER_DETAILS_EDIT;
static const EVENT_RESULT = Routes.EVENT_RESULT;
static final routes = [
GetPage(
@ -175,6 +181,23 @@ class AppPages {
name: Routes.USER_DETAILS_EDIT,
page: () => const UserDetailsEditPage(),
),
GetPage(
name: Routes.EVENT_RESULT,
page: () {
final args = Get.arguments;
if (args is Map<String, dynamic> && args.containsKey('entry')) {
return EventResultPage(entry: args['entry'] as Entry);
} else {
// エントリーが提供されていない場合のフォールバック
// 例: エラーページを表示するか、ホームページにリダイレクトする
return const Scaffold(
body: Center(
child: Text('エラー: イベント結果を表示できません。'),
),
);
}
},
),
];
}

View File

@ -38,4 +38,5 @@ abstract class Routes {
static const EVENT_ENTRIES = '/event-entries';
static const USER_DETAILS_EDIT = '/user-details-edit';
static const EVENT_RESULT = '/event-result';
}

View File

@ -61,7 +61,7 @@ class ApiService extends GetxService{
final indexController = Get.find<IndexController>();
if (indexController.currentUser.isNotEmpty) {
token = indexController.currentUser[0]['token'] ?? '';
print("Get token = $token");
//print("Get token = $token");
}else{
token = "";
}
@ -77,7 +77,7 @@ class ApiService extends GetxService{
final indexController = Get.find<IndexController>();
if (indexController.currentUser.isNotEmpty) {
token = indexController.currentUser[0]['token'] ?? '';
print("Get token = $token");
//print("Get token = $token");
}else{
token = "";
}
@ -87,7 +87,7 @@ class ApiService extends GetxService{
Future<dynamic> _handleRequest(Future<http.Response> Function() request) async {
try {
final response = await request();
if (response.statusCode == 200) {
if (response.statusCode == 200 || response.statusCode == 201 ) {
return json.decode(utf8.decode(response.bodyBytes));
} else if (response.statusCode == 401) {
await _handleUnauthorized();
@ -117,42 +117,6 @@ class ApiService extends GetxService{
}
Future<List<Team>> getTeams_old() async {
init();
final token = await getToken2();
try {
final response = await http.get(
Uri.parse('$baseUrl/teams/'),
headers: {'Authorization': 'Token $token',"Content-Type": "application/json; charset=UTF-8"},
);
if (response.statusCode == 200) {
// UTF-8でデコード
final decodedResponse = utf8.decode(response.bodyBytes);
//print('User Response body: $decodedResponse');
List<dynamic> teamsJson = json.decode(decodedResponse);
List<Team> teams = [];
for (var teamJson in teamsJson) {
//print('\nTeam Data:');
//_printDataComparison(teamJson, Team);
teams.add(Team.fromJson(teamJson));
}
return teams;
} else {
throw Exception('Failed to load teams. Status code: ${response.statusCode}');
}
} catch (e, stackTrace) {
print('Error in getTeams: $e');
print('Stack trace: $stackTrace');
rethrow;
}
}
Future<List<NewCategory>> getCategories() async {
init();
getToken();
@ -165,7 +129,7 @@ class ApiService extends GetxService{
if (response.statusCode == 200) {
final decodedResponse = utf8.decode(response.bodyBytes);
print('User Response body: $decodedResponse');
//print('User Response body: $decodedResponse');
List<dynamic> categoriesJson = json.decode(decodedResponse);
List<NewCategory> categories = [];
@ -200,26 +164,6 @@ class ApiService extends GetxService{
)).then((data) => NewCategory.fromJson(data));
}
Future<NewCategory> getZekkenNumber_old(int categoryId) async {
try {
final response = await http.post(
Uri.parse('$baseUrl/categories-viewset/$categoryId/get_zekken_number/'),
headers: {'Authorization': 'Token $token',"Content-Type": "application/json; charset=UTF-8"},
);
if (response.statusCode == 200) {
final decodedResponse = utf8.decode(response.bodyBytes);
print('User Response body: $decodedResponse');
final categoriesJson = json.decode(decodedResponse);
return NewCategory.fromJson(categoriesJson);
} else {
throw Exception('Failed to increment category number');
}
} catch (e) {
throw Exception('Error incrementing category number: $e');
}
}
Future<User> getCurrentUser() async {
init();
final token = getToken();
@ -324,30 +268,6 @@ class ApiService extends GetxService{
)).then((data) => Team.fromJson(data));
}
Future<Team> createTeam_old(String teamName, int categoryId) async {
init();
getToken();
final response = await http.post(
Uri.parse('$baseUrl/teams/'),
headers: {
'Authorization': 'Token $token',
"Content-Type": "application/json; charset=UTF-8",
},
body: json.encode({
'team_name': teamName,
'category': categoryId,
}),
);
if (response.statusCode == 201) {
final decodedResponse = utf8.decode(response.bodyBytes);
return Team.fromJson(json.decode(decodedResponse));
} else {
throw Exception('Failed to create team');
}
}
Future<Team> updateTeam(int teamId, String teamName, int categoryId) async {
final token = await getToken2();
return _handleRequest(() => http.put(
@ -357,31 +277,6 @@ class ApiService extends GetxService{
)).then((data) => Team.fromJson(data));
}
Future<Team> updateTeam_old(int teamId, String teamName, int categoryId) async {
init();
getToken();
final response = await http.put(
Uri.parse('$baseUrl/teams/$teamId/'),
headers: {
'Authorization': 'Token $token',
'Content-Type': 'application/json; charset=UTF-8',
},
body: json.encode({
'team_name': teamName,
'category': categoryId,
}),
);
if (response.statusCode == 200) {
final decodedResponse = utf8.decode(response.bodyBytes);
return Team.fromJson(json.decode(decodedResponse));
} else {
throw Exception('Failed to update team');
}
}
Future<void> deleteTeam(int teamId) async {
final token = await getToken2();
await _handleRequest(() => http.delete(
@ -390,24 +285,6 @@ class ApiService extends GetxService{
));
}
Future<void> deleteTeamold_(int teamId) async {
init();
getToken();
final response = await http.delete(
Uri.parse('$baseUrl/teams/$teamId/'),
headers: {'Authorization': 'Token $token','Content-Type': 'application/json; charset=UTF-8'},
);
if( response.statusCode == 400) {
final decodedResponse = utf8.decode(response.bodyBytes);
print('User Response body: $decodedResponse');
throw Exception('まだメンバーが残っているので、チームを削除できません。');
}else if (response.statusCode != 204) {
throw Exception('Failed to delete team');
}
}
Future<List<User>> getTeamMembers(int teamId) async {
final token = await getToken2();
return _handleRequest(() => http.get(
@ -416,26 +293,6 @@ class ApiService extends GetxService{
)).then((data) => (data as List).map((json) => User.fromJson(json)).toList());
}
Future<List<User>> getTeamMembers_old(int teamId) async {
init();
getToken();
final response = await http.get(
Uri.parse('$baseUrl/teams/$teamId/members/'),
headers: {'Authorization': 'Token $token','Content-Type': 'application/json; charset=UTF-8'},
);
if (response.statusCode == 200) {
final decodedResponse = utf8.decode(response.bodyBytes);
print('User Response body: $decodedResponse');
List<dynamic> membersJson = json.decode(decodedResponse);
return membersJson.map((json) => User.fromJson(json)).toList();
} else {
throw Exception('Failed to load team members');
}
}
Future<User> createTeamMember(int teamId, String? email, String? firstname, String? lastname, DateTime? dateOfBirth, bool? female) async {
final token = await getToken2();
String? formattedDateOfBirth = dateOfBirth != null ? DateFormat('yyyy-MM-dd').format(dateOfBirth) : null;
@ -452,45 +309,6 @@ class ApiService extends GetxService{
)).then((data) => User.fromJson(data));
}
Future<User> createTeamMember_old(int teamId, String? email, String? firstname, String? lastname, DateTime? dateOfBirth,bool? female) async {
init();
getToken();
// emailが値を持っている場合の処理
if (email != null && email.isNotEmpty) {
firstname ??= "dummy";
lastname ??= "dummy";
dateOfBirth ??= DateTime.now();
female ??= false;
}
String? formattedDateOfBirth;
if (dateOfBirth != null) {
formattedDateOfBirth = DateFormat('yyyy-MM-dd').format(dateOfBirth);
}
final response = await http.post(
Uri.parse('$baseUrl/teams/$teamId/members/'),
headers: {
'Authorization': 'Token $token',
'Content-Type': 'application/json; charset=UTF-8',
},
body: json.encode({
'email': email,
'firstname': firstname,
'lastname': lastname,
'date_of_birth': formattedDateOfBirth,
'female': female,
}),
);
if (response.statusCode == 200 || response.statusCode == 201) {
final decodedResponse = utf8.decode(response.bodyBytes);
return User.fromJson(json.decode(decodedResponse));
} else {
throw Exception('Failed to create team member');
}
}
Future<User> updateTeamMember(int teamId, int? memberId, String firstname, String lastname, DateTime? dateOfBirth, bool? female) async {
final token = await getToken2();
@ -507,37 +325,6 @@ class ApiService extends GetxService{
)).then((data) => User.fromJson(data));
}
Future<User> updateTeamMember_old(int teamId,int? memberId, String firstname, String lastname, DateTime? dateOfBirth,bool? female) async {
init();
getToken();
String? formattedDateOfBirth;
if (dateOfBirth != null) {
formattedDateOfBirth = DateFormat('yyyy-MM-dd').format(dateOfBirth);
}
final response = await http.put(
Uri.parse('$baseUrl/teams/$teamId/members/$memberId/'),
headers: {
'Authorization': 'Token $token',
'Content-Type': 'application/json; charset=UTF-8',
},
body: json.encode({
'firstname': firstname,
'lastname': lastname,
'date_of_birth': formattedDateOfBirth,
'female': female,
}),
);
if (response.statusCode == 200) {
final decodedResponse = utf8.decode(response.bodyBytes);
return User.fromJson(json.decode(decodedResponse));
} else {
throw Exception('Failed to update team member');
}
}
Future<void> deleteTeamMember(int teamId, int memberId) async {
final token = await getToken2();
await _handleRequest(() => http.delete(
@ -546,20 +333,6 @@ class ApiService extends GetxService{
));
}
Future<void> deleteTeamMember_old(int teamId,int memberId) async {
init();
getToken();
final response = await http.delete(
Uri.parse('$baseUrl/teams/$teamId/members/$memberId/'),
headers: {'Authorization': 'Token $token'},
);
if (response.statusCode != 204) {
throw Exception('Failed to delete team member');
}
}
Future<void> deleteAllTeamMembers(int teamId) async {
final response = await http.delete(
Uri.parse('$baseUrl/teams/$teamId/members/destroy_all/?confirm=true'),
@ -594,24 +367,6 @@ class ApiService extends GetxService{
)).then((data) => (data as List).map((json) => Entry.fromJson(json)).toList());
}
Future<List<Entry>> getEntries_old() async {
init();
getToken();
final response = await http.get(
Uri.parse('$baseUrl/entry/'),
headers: {'Authorization': 'Token $token', 'Content-Type': 'application/json; charset=UTF-8',
},
);
if (response.statusCode == 200) {
final decodedResponse = utf8.decode(response.bodyBytes);
List<dynamic> entriesJson = json.decode(decodedResponse);
return entriesJson.map((json) => Entry.fromJson(json)).toList();
} else {
throw Exception('Failed to load entries');
}
}
Future<List<Entry>> getTeamEntries(int teamId) async {
try {
@ -632,6 +387,21 @@ class ApiService extends GetxService{
}
}
Future<Entry> updateEntryStatus(int entryId, bool hasParticipated, bool hasGoaled) async {
final token = await getToken2();
return _handleRequest(() => http.patch(
Uri.parse('$baseUrl/entries/$entryId/update-status/'),
headers: {
'Authorization': 'Token $token',
'Content-Type': 'application/json; charset=UTF-8'
},
body: json.encode({
'hasParticipated': hasParticipated,
'hasGoaled': hasGoaled,
}),
)).then((data) => Entry.fromJson(data));
}
Future<List<Event>> getEvents() async {
init();
getToken();
@ -669,39 +439,6 @@ class ApiService extends GetxService{
)).then((data) => Entry.fromJson(data));
}
Future<Entry> createEntry_old(int teamId, int eventId, int categoryId, DateTime date,String zekkenNumber) async {
init();
getToken();
String? formattedDate;
formattedDate = DateFormat('yyyy-MM-dd').format(date);
final response = await http.post(
Uri.parse('$baseUrl/entry/'),
headers: {
'Authorization': 'Token $token',
'Content-Type': 'application/json; charset=UTF-8',
},
body: json.encode({
'team': teamId,
'event': eventId,
'category': categoryId,
'date': formattedDate,
'zekken_number':zekkenNumber,
}),
);
if (response.statusCode == 201) {
final decodedResponse = utf8.decode(response.bodyBytes);
return Entry.fromJson(json.decode(decodedResponse));
} else {
final decodedResponse = utf8.decode(response.bodyBytes);
print("decodedResponse = $decodedResponse");
final errorInfo = json.decode(decodedResponse);
throw Exception(errorInfo['error']);
}
}
Future<void> updateUserInfo(int userId, Entry entry) async {
final token = await getToken2();
@ -719,40 +456,6 @@ class ApiService extends GetxService{
));
}
Future<void> updateUserInfo_old(int userId, Entry entry) async {
init();
getToken();
final entryId = entry.id;
DateTime? date = entry.date;
String? formattedDate;
formattedDate = DateFormat('yyyy-MM-dd').format(date!);
final response = await http.put(
Uri.parse('$baseUrl/userinfo/$userId/'),
headers: {
'Authorization': 'Token $token',
'Content-Type': 'application/json; charset=UTF-8',
},
body: json.encode({
'zekken_number': entry.zekkenNumber,
'event_code': entry.event.eventName,
'group': entry.team.category.categoryName,
'team_name': entry.team.teamName,
'date': formattedDate,
}),
);
if (response.statusCode == 200) {
final decodedResponse = utf8.decode(response.bodyBytes);
final updatedUserInfo = json.decode(decodedResponse);
//Get.find<IndexController>().updateUserInfo(updatedUserInfo);
} else {
throw Exception('Failed to update entry');
}
}
Future<Entry> updateEntry(int entryId, int teamId, int eventId, int categoryId, DateTime date,int zekkenNumber) async {
init();

View File

@ -87,8 +87,7 @@ class AuthService {
return changePassword;
}
static Future<Map<String, dynamic>> login(
String email, String password) async {
static Future<Map<String, dynamic>> login(String email, String password) async {
//print("------- in logged email $email pwd $password ###### --------");
Map<String, dynamic> cats = {};
String serverUrl = ConstValues.currentServer();
@ -116,17 +115,12 @@ class AuthService {
var errorBody = json.decode(utf8.decode(response.bodyBytes));
errorMessage = errorBody['non_field_errors']?[0] ?? 'パスワードが正しくありません。';
}
Get.snackbar(
"エラー",
errorMessage,
backgroundColor: Colors.red,
colorText: Colors.white,
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
);
cats = {};
throw Exception(errorMessage);
}
} catch( e ){
print('Error in login: $e');
throw e; // エラーを上位に伝播させる
/*
print('Error in login: $e');
Get.snackbar("通信エラーがおきました", "サーバーと通信できませんでした",
backgroundColor: Colors.red,
@ -142,7 +136,8 @@ class AuthService {
duration: const Duration(seconds: 3),
//backgroundColor: Colors.yellow,
);
cats = {};
*/
//cats = {};
}
return cats;
}
@ -171,7 +166,7 @@ class AuthService {
Map<String, dynamic> cats = {};
String serverUrl = ConstValues.currentServer();
String url = '$serverUrl/api/register/';
debugPrint('++++++++$url');
//debugPrint('++++++++$url');
final http.Response response = await http.post(
Uri.parse(url),
headers: <String, String>{

View File

@ -1,6 +1,8 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:gifunavi/pages/entry/entry_controller.dart';
import 'package:gifunavi/services/api_service.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
import 'package:gifunavi/model/rog.dart';
@ -33,6 +35,8 @@ class ExternalService {
Future<Map<String, dynamic>> startRogaining() async {
final IndexController indexController = Get.find<IndexController>();
//final TeamController teamController = Get.find<TeamController>();
final ApiService apiService = Get.find<ApiService>();
final EntryController entryController = Get.find<EntryController>();
debugPrint("== startRogaining ==");
@ -68,23 +72,42 @@ class ExternalService {
} else {
debugPrint("== startRogaining processing==");
String serverUrl = ConstValues.currentServer();
String url = '$serverUrl/gifuroge/start_from_rogapp';
print('++++++++$url');
final http.Response response = await http.post(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(
<String, String>{'team_name': team, 'event_code': eventCode}),
);
try {
// 新しい API 呼び出しを使用
final updatedEntry = await apiService.updateEntryStatus(
entryController.currentEntry.value!.id!,
true, // hasParticipated を true に設定
false // hasGoaled は変更しない
);
print("---- start rogianing api status ---- ${response.statusCode}");
// start_rogaining を本サーバーに送る。 TODO
// // 'has_participated': true ... Server 側の修正が必要 TODO
if (response.statusCode == 200) {
res = json.decode(utf8.decode(response.bodyBytes));
//print('----_res : $res ----');
String serverUrl = ConstValues.currentServer();
String url = '$serverUrl/gifuroge/start_from_rogapp';
//print('++++++++$url');
final http.Response response = await http.post(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(
<String, String>{'team_name': team, 'event_code': eventCode}),
// 'has_participated': true ... Server 側の修正が必要 TODO
);
print("---- start rogianing api status ---- ${response.statusCode}");
if (response.statusCode == 200) {
res = json.decode(utf8.decode(response.bodyBytes));
//print('----_res : $res ----');
// ローカルのユーザーデータを更新
indexController.updateUserParticipationStatus(true);
}
}catch (e) {
print('Error in startRogaining: $e');
Get.snackbar('エラー', 'ロゲイニングの開始に失敗しました');
}
}
return res;
@ -226,6 +249,8 @@ class ExternalService {
final IndexController indexController = Get.find<IndexController>();
final DestinationController destinationController =
Get.find<DestinationController>();
final ApiService apiService = Get.find<ApiService>();
final EntryController entryController = Get.find<EntryController>();
// チームIDを取得
@ -253,65 +278,83 @@ class ExternalService {
final im1Bytes = File(image).readAsBytesSync();
String im1_64 = base64Encode(im1Bytes);
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(), //userId.toString(),
'team_name': teamname,
'event_code': eventcode,
'goaltime': goalTime,
'goalimage': im1_64,
'cp_number': "-1"
}),
);
try {
// 新しい API呼び出しを使用
final updatedEntry = await apiService.updateEntryStatus(
entryController.currentEntry.value!.id!,
true, // hasParticipated を true に設定
true // hasGoaled を true に設定
);
//String serverUrl = ConstValues.currentServer();
String url = '$serverUrl/gifuroge/goal_from_rogapp';
//print('++++++++$url');
if (response.statusCode == 201) {
try {
Map<String, dynamic> res = json.decode(utf8.decode(response.bodyBytes));
// print('----_res : $res ----');
// print('---- image url ${res["goalimage"]} ----');
final http.Response response2 = await http.post(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
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(), //userId.toString(),
'team_name': teamname,
'event_code': eventcode,
'goaltime': goalTime,
'goalimage': im1_64,
'cp_number': "-1"
}),
);
//String serverUrl = ConstValues.currentServer();
String url = '$serverUrl/gifuroge/goal_from_rogapp';
//print('++++++++$url');
if (response.statusCode == 201) {
try {
Map<String, dynamic> res = json.decode(
utf8.decode(response.bodyBytes));
// print('----_res : $res ----');
// print('---- image url ${res["goalimage"]} ----');
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,
'event_code': eventcode,
'goal_time': goalTime,
'image': res["goalimage"].toString().replaceAll(
'http://localhost:8100', serverUrl)
// 'has_goaled': true ... サーバー側の修正が必要 TODO
//'http://rogaining.sumasen.net')
}),
);
String rec = jsonEncode(<String, String>{
'team_name': teamname,
'event_code': eventcode,
'goal_time': goalTime,
'image': res["goalimage"].toString().replaceAll(
'http://localhost:8100', serverUrl)
'image': res["goalimage"]
.toString()
.replaceAll('http://localhost:8100', serverUrl)
//'http://rogaining.sumasen.net')
}),
);
String rec = jsonEncode(<String, String>{
'team_name': teamname,
'event_code': eventcode,
'goal_time': goalTime,
'image': res["goalimage"]
.toString()
.replaceAll('http://localhost:8100', serverUrl)
//'http://rogaining.sumasen.net')
});
//print("-- json -- $rec");
//print('----- response2 is $response2 --------');
if (response2.statusCode == 200) {
res2 = json.decode(utf8.decode(response2.bodyBytes));
} else {
res2 = json.decode(utf8.decode(response2.bodyBytes));
});
//print("-- json -- $rec");
//print('----- response2 is $response2 --------');
if (response2.statusCode == 200) {
res2 = json.decode(utf8.decode(response2.bodyBytes));
// ローカルのユーザーデータを更新
indexController.updateUserGoalStatus(true);
} else {
res2 = json.decode(utf8.decode(response2.bodyBytes));
// ここはどうする? TODO
}
} catch (e) {
print("Error {$e}");
}
} catch(e){
print( "Error {$e}" );
}
}catch(e) {
print("Error in makeGoal: $e");
Get.snackbar('エラー', 'ゴール処理に失敗しました');
}
//}
destinationController.resetRogaining(isgoal: true);
return res2;

View File

@ -17,6 +17,19 @@ class MotionService {
}
}
Future<dynamic> _handleMotionData(MethodCall call) async {
switch (call.method) {
case 'onMotionData':
final Map<String, dynamic> motionData = call.arguments;
// ここでモーションデータを処理します
print('Received motion data: $motionData');
// 例: データを状態管理システムに渡す、UIを更新する等
break;
default:
print('Unknown method ${call.method}');
}
}
static Future<void> stopMotionUpdates() async {
if (Platform.isIOS) {
try {

View File

@ -32,6 +32,8 @@ class LocationController extends GetxController {
// Reactive variable to hold the current position
Rx<Position?> currentPosition = Rx<Position?>(null);
// 現在の位置情報を保持するReactive変数です。Rx<Position?>型で宣言されています。
final locationMarkerPositionStreamController = StreamController<LocationMarkerPosition?>.broadcast();
// Subscription to the position stream
StreamSubscription<Position>? positionStream;
@ -154,8 +156,8 @@ class LocationController extends GetxController {
//===== Akira Added 2024-4-9 end
final locationMarkerPositionStreamController =
StreamController<LocationMarkerPosition?>.broadcast();
//final locationMarkerPositionStreamController =
// StreamController<LocationMarkerPosition?>.broadcast();
// 位置マーカーの位置情報を送信するためのStreamControllerです。
// StreamController<LocationMarkerPosition?>型で宣言されています。
@ -202,6 +204,7 @@ class LocationController extends GetxController {
}
startPositionStream();
debugPrint("Started startPositionStream");
} catch( e ){
print('Error initializing location service: $e');
}
@ -235,7 +238,12 @@ class LocationController extends GetxController {
accuracy: position.accuracy,
),
);
});
},
onError: (error) {
debugPrint('Error in Geolocator stream: $error');
locationMarkerPositionStreamController.addError(error);
},
);
}
void startPositionStream_old() async {
@ -248,7 +256,7 @@ class LocationController extends GetxController {
return;
}
await PermissionController.checkAndRequestPermissions();
//await PermissionController.checkAndRequestPermissions();
// 位置情報の設定を行います。z11
// Set up the location options

View File

@ -1,474 +1,153 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_location_marker/flutter_map_location_marker.dart';
import 'package:flutter_polyline_points/flutter_polyline_points.dart';
import 'package:geojson_vi/geojson_vi.dart';
import 'package:get/get.dart';
import 'package:latlong2/latlong.dart';
import 'package:gifunavi/pages/permission/permission.dart';
import 'package:gifunavi/pages/settings/settings_binding.dart';
import 'package:gifunavi/model/destination.dart';
import 'package:gifunavi/pages/destination/destination_controller.dart';
import 'package:gifunavi/pages/index/index_controller.dart';
import 'package:gifunavi/utils/database_helper.dart';
import 'package:gifunavi/utils/location_controller.dart';
import 'package:gifunavi/utils/text_util.dart';
import 'package:gifunavi/widgets/base_layer_widget.dart';
import 'package:gifunavi/widgets/bottom_sheet_new.dart';
import 'package:gifunavi/widgets/current_position_widget.dart';
import 'package:gifunavi/widgets/game_state_view.dart';
import 'package:gifunavi/pages/destination/destination_controller.dart';
import 'package:gifunavi/pages/settings/settings_controller.dart';
import 'package:gifunavi/widgets/base_layer_widget.dart';
import 'package:gifunavi/widgets/game_state_view.dart';
import 'package:gifunavi/widgets/current_position_widget.dart';
class MapResetController {
void Function()? resetIdleTimer;
}
class MapWidget extends StatefulWidget {
const MapWidget({super.key});
@override
State<MapWidget> createState() => _MapWidgetState();
}
class _MapWidgetState extends State<MapWidget> with WidgetsBindingObserver {
//class _MapWidgetState extends State<MapWidget> {
class MapWidget extends StatelessWidget {
final IndexController indexController = Get.find<IndexController>();
final DestinationController destinationController =
Get.find<DestinationController>();
final DestinationController destinationController = Get.find<DestinationController>();
final LocationController locationController = Get.find<LocationController>();
final SettingsController settingsController = Get.find<SettingsController>();
late MapController mapController;
final Completer<MapController> mapControllerCompleter = Completer<MapController>();
MapWidget({Key? key}) : super(key: key) {
_initializeControllers();
}
StreamSubscription? subscription;
Timer? _timer;
bool curr_marker_display = false;
final Map<LatLng, Marker> _markerCache = {};
List<Marker> _markers = [];
@override
void initState() {
super.initState();
// 追加
WidgetsBinding.instance.addPostFrameCallback((_) {
PermissionController.checkAndRequestPermissions();
});
debugPrint('MapWidget: initState called');
SettingsBinding().dependencies(); // これを追加
_startIdleTimer();
mapController = MapController();
indexController.mapController = mapController;
// added by Akira
WidgetsBinding.instance.addObserver(this);
void _initializeControllers() {
indexController.initMapController();
_startIdleTimer();
// マップの操作イベントをリッスンして、_resetTimerを呼び出す
mapController.mapEventStream.listen((MapEvent mapEvent) {
if (mapEvent is MapEventMove || mapEvent is MapEventFlingAnimation) {
_resetTimer();
ever(indexController.isMapControllerReady, (_) {
if (indexController.isMapControllerReady.value) {
_initMarkers();
}
});
// MapControllerの初期化が完了するまで待機
WidgetsBinding.instance.addPostFrameCallback((_) {
debugPrint("MapControllerの初期化が完了");
setState(() {
indexController.isMapControllerReady.value = true;
});
// MapControllerの初期化が完了したら、IndexControllerのonInitを呼び出す
//indexController.checkPermission();
PermissionController.checkAndRequestPermissions();
});
late MapResetController mapResetController = MapResetController();
mapResetController.resetIdleTimer = _resetIdleTimer;
Get.put(mapResetController);
// Add this debug subscription
subscription = locationController.locationMarkerPositionStreamController.stream.listen(
(LocationMarkerPosition? position) {
if (position != null) {
//debugPrint('Location update received: lat=${position.latitude}, lon=${position.longitude}');
} else {
debugPrint('Received null location update');
}
},
onError: (error) {
debugPrint('Error in location stream: $error');
},
onDone: () {
debugPrint('Location stream closed');
},
);
// indexController.mapController = MapController(initCompleter: mapControllerCompleter);
}
void _resetIdleTimer() {
debugPrint("_resetIdleTimer...");
_timer?.cancel();
_startIdleTimer();
}
@override
void dispose() {
debugPrint('MapWidget: dispose called');
WidgetsBinding.instance.removeObserver(this); // added
mapController.dispose();
_timer?.cancel();
super.dispose();
}
// added by Akira
/*
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
debugPrint("MapWidget:didChangeAppLifecycleState...state=${state}");
if (state == AppLifecycleState.resumed) {
_resetTimer();
}
}
*/
// _centerMapOnUser を10秒間でコール
Future<void> _startIdleTimer() async {
//debugPrint("_startIdleTimer ....");
SettingsController settingsController;
// SettingsControllerが利用可能になるまで待機
while (true) {
try {
settingsController = Get.find<SettingsController>();
break; // SettingsControllerが見つかったらループを抜ける
} catch (e) {
// SettingsControllerがまだ利用可能でない場合は少し待ってから再試行
await Future.delayed(const Duration(milliseconds: 100));
}
}
void _startIdleTimer() {
if (!settingsController.autoReturnDisabled.value) {
_timer = Timer(settingsController.timerDuration.value, _centerMapOnUser);
Future.delayed(settingsController.timerDuration.value, _centerMapOnUser);
}
}
// タイマーをリセットして_startIdleTimer をコール
void _resetTimer() {
//debugPrint("_resetTimer ....");
_timer?.cancel();
_startIdleTimer();
}
// マッぷを現在位置を中心にする。
void _centerMapOnUser() {
//debugPrint("_centerMapOnUser ....");
if (mounted) {
//debugPrint("_centerMapOnUser => centering ....");
destinationController.centerMapToCurrentLocation();
}
destinationController.centerMapToCurrentLocation();
_startIdleTimer();
}
Future<void> _initMarkers() async {
List<Marker> markers = await _getMarkers();
setState(() {
_markers = markers;
});
indexController.markers.value = await _getMarkers();
}
Future<List<Marker>> _getMarkers() async {
debugPrint('Getting markers...');
if (indexController.isLoadingLocations.value) {
await Future.doWhile(() async {
await Future.delayed(const Duration(milliseconds: 100));
return indexController.isLoadingLocations.value;
});
await indexController.waitForLocationsToLoad();
}
debugPrint('Getting markers...');
List<Marker> markers = [];
List<Marker> markers = [];
if (indexController.locations.isNotEmpty && indexController.locations[0].features.isNotEmpty) {
for (var feature in indexController.locations[0].features) {
GeoJSONMultiPoint point = feature!.geometry as GeoJSONMultiPoint;
LatLng latLng = LatLng(point.coordinates[0][1], point.coordinates[0][0]);
markers.add(Marker(
point: latLng,
width: 30.0,
height: 30.0,
child: getMarkerShape(feature),
));
// マーカーの作成ロジック
// 実際のマーカー作成ロジックをここに実装してください
}
}else{
} else {
debugPrint('No locations or features available');
}
return markers;
}
// Widget getMarkerShape(GeoJSONFeature i, BuildContext context) {
Widget getMarkerShape(GeoJSONFeature i) {
GeoJSONMultiPoint p = i.geometry as GeoJSONMultiPoint;
return InkWell(
onTap: () {
GeoJSONFeature? fs = indexController.getFeatureForLatLong(
p.coordinates[0][1], p.coordinates[0][0]);
if (fs != null) {
indexController.currentFeature.clear();
indexController.currentFeature.add(fs);
Destination des = destinationController.festuretoDestination(fs);
DatabaseHelper db = DatabaseHelper.instance;
db.getDestinationByLatLon(des.lat!, des.lon!).then((value) {
destinationController.shouldShowBottomSheet = false;
showModalBottomSheet(
constraints:
BoxConstraints.loose(Size(Get.width, Get.height * 0.85)),
context: context,
isScrollControlled: true,
isDismissible: true,
builder: ((context) => BottomSheetNew(
destination: des, isAlreadyCheckedIn: value.isNotEmpty)),
).whenComplete(() {
destinationController.shouldShowBottomSheet = true;
destinationController.skipGps = false;
});
});
}
},
child: Stack(
fit: StackFit.expand,
children: [
Container( // マーカー
height: 32,
width: 32,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.transparent,
border: Border.all(
color: i.properties!['buy_point'] > 0
? Colors.blue
: Colors.red,
width: 3,
style: BorderStyle.solid,
),
),
child: Stack(
alignment: Alignment.center,
children: [
const Icon(
Icons.circle,
size: 6.0,
),
i.properties!['cp'] <= 0 ? Transform.translate
(
offset: const Offset(-3, 0), //-3
child: Transform.rotate(
alignment: Alignment.centerLeft,
origin: Offset.fromDirection(1, 26),
angle: 270 * pi / 180,
child: const Icon(
Icons.play_arrow_outlined,
color: Colors.red,
size: 70,
)),
)
: Container(
color: Colors.transparent,
),
],
),
),
Transform.translate(
offset: const Offset(30, 0), // 30,0
child: Align(
alignment: Alignment.center,
child: Container (
//width: 80, // 幅を指定
//height: 60, // 40
//color: Colors.purple.withOpacity(0.2),
color: Colors.transparent,
//child: Text(' ').
//constraints: const BoxConstraints(maxWidth: 60.0), // 最大幅を設定
//constraints: BoxConstraints(maxWidth: maxWidth), // 最大幅を設定
//color: Colors.purple.withOpacity(0.2),
child: Stack(
children: <Widget>[
Text( // アウトライン
TextUtils.getDisplayTextFeture(i),
style: TextStyle(
fontSize: 16, // 16
fontWeight: FontWeight.w700,
overflow: TextOverflow.visible,
//height: 1.2,
foreground: Paint()
..style = PaintingStyle.stroke
..strokeWidth = 1 // 2
..color = Colors.white,
),
maxLines: 1, // テキストを1行に制限
softWrap: false, // テキストの折り返しを無効化
),
Text( // テキスト
TextUtils.getDisplayTextFeture(i),
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
overflow: TextOverflow.visible,
//fontWeight: FontWeight.bold,
//height: 1.2,
color: Colors.black,
),
maxLines: 1, // テキストを1行に制限
softWrap: false, // テキストの折り返しを無効化
),
],
),
),
),
)
],
),
);
}
List<LatLng>? getPoints() {
List<LatLng> pts = [];
for (PointLatLng p in indexController.routePoints) {
LatLng l = LatLng(p.latitude, p.longitude);
pts.add(l);
}
return pts;
return indexController.routePoints.map((p) => LatLng(p.latitude, p.longitude)).toList();
}
@override
Widget build(BuildContext context) {
final settingsController = Get.find<SettingsController>(); // これを追加
//final PopupController popupController = PopupController();
return Stack(
children: [
Obx(() => indexController.isLoading.value == true
? const Padding(
padding: EdgeInsets.only(top: 60.0),
child: CircularProgressIndicator(),
)
Obx(() => indexController.isLoading.value
? const Center(child: CircularProgressIndicator())
: FlutterMap(
mapController: mapController,
//mapController: indexController.mapController,
options: MapOptions(
maxZoom: 18.4,
onMapReady: () {
_initMarkers();
//indexController.isMapControllerReady.value = true;
},
initialCenter:
const LatLng(37.15319600454702, 139.58765950528198),
bounds: indexController.currentBound.isNotEmpty
? indexController.currentBound[0]
: LatLngBounds.fromPoints([
const LatLng(35.03999881162295, 136.40587119778962),
const LatLng(36.642756778706904, 137.95226720406063)
]),
initialZoom: 1,
interactiveFlags:
InteractiveFlag.pinchZoom | InteractiveFlag.drag,
onPositionChanged: (MapPosition pos, hasGesture) {
if (hasGesture) {
_resetTimer();
}
indexController.currentBound = [pos.bounds!];
},
onMapEvent: (MapEvent mapEvent) {
//debugPrint('Map event: ${mapEvent.runtimeType}');
if (mapEvent is MapEventMove) {
destinationController.shouldShowBottomSheet = true;
}
},
//onTap: (_, __) => popupController.hideAllPopups(),
mapController: indexController.mapController,
options: MapOptions(
maxZoom: 18.4,
onMapReady: () {
indexController.isMapControllerReady.value = true;
},
initialCenter: const LatLng(37.15319600454702, 139.58765950528198),
bounds: indexController.currentBound.isNotEmpty
? indexController.currentBound[0]
: LatLngBounds.fromPoints([
const LatLng(35.03999881162295, 136.40587119778962),
const LatLng(36.642756778706904, 137.95226720406063)
]),
initialZoom: 1,
interactiveFlags: InteractiveFlag.pinchZoom | InteractiveFlag.drag,
onPositionChanged: (MapPosition pos, bool hasGesture) {
if (hasGesture) {
_startIdleTimer();
}
indexController.currentBound = [pos.bounds!];
},
onMapEvent: (MapEvent mapEvent) {
if (mapEvent is MapEventMove) {
destinationController.shouldShowBottomSheet = true;
}
},
),
children: [
const BaseLayer(),
Obx(() => indexController.routePointLenght > 0
? PolylineLayer(
polylines: [
Polyline(
points: getPoints()!,
strokeWidth: 6.0,
color: Colors.indigo,
),
children: [
const BaseLayer(),
// ルートのポリライン表示
Obx(
() => indexController.routePointLenght > 0
? PolylineLayer(
polylines: [
Polyline(
points: getPoints()!,
strokeWidth: 6.0,
color: Colors.indigo,
),
],
)
: Container(),
),
// 現在位置のマーカー
CurrentLocationLayer(
positionStream: locationController
.locationMarkerPositionStreamController.stream,
//alignDirectionOnUpdate: AlignOnUpdate.never,
style: const LocationMarkerStyle(
marker: Stack(
children: [
CircleAvatar(
radius: 13.5,
backgroundColor: Colors.blue,
child: Icon(Icons.navigation, color: Colors.white),
),
],
),
markerSize: Size(27, 27),
markerDirection: MarkerDirection.heading,
],
)
: Container()),
CurrentLocationLayer(
positionStream: locationController.locationMarkerPositionStream,
style: const LocationMarkerStyle(
marker: Stack(
children: [
CircleAvatar(
radius: 13.5,
backgroundColor: Colors.blue,
child: Icon(Icons.navigation, color: Colors.white),
),
//child: const Icon(Icons.navigation),
),
FutureBuilder<List<Marker>>(
future: indexController.locations.isNotEmpty ? _getMarkers() : null,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return const Center(child: Text('マーカーの読み込みに失敗しました'));
} else {
return MarkerLayer(markers: snapshot.data ?? []);
}
},
),
//MarkerLayer(markers: indexController.locations.isNotEmpty ? _getMarkers() : []),
],
)),
],
),
markerSize: Size(27, 27),
markerDirection: MarkerDirection.heading,
),
),
Obx(() => MarkerLayer(markers: indexController.markers)),
],
)),
const Positioned(top: 0, left: 0, child: GameStateWidget()),
const Positioned(bottom: 10, right: 10, child: CurrentPosition()),
StreamBuilder<LocationMarkerPosition?>(
stream: locationController.locationMarkerPositionStream,
builder: (context, snapshot) {
if (!snapshot.hasData) {
//debugPrint("====== Not display current marker");
curr_marker_display = true;
}else if(curr_marker_display){
debugPrint("====== Displayed current marker");
curr_marker_display = false;
}
return Container();
},
)
Obx(() => indexController.currentMarkerPosition.value != null
? Container() // 現在のマーカー位置が更新されたときの処理
: Container()),
],
);
}
}
class MapResetController extends GetxController {
void Function()? resetIdleTimer;
void setResetIdleTimer(void Function() resetFunction) {
resetIdleTimer = resetFunction;
}
}

View File

@ -14,9 +14,6 @@ class _PermissionHandlerScreenState extends State<PermissionHandlerScreen> {
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await PermissionController.checkAndRequestPermissions();
});
}
@override