大幅変更&環境バージョンアップ
This commit is contained in:
@ -1,4 +1,5 @@
|
||||
// ignore: non_constant_identifier_names
|
||||
// 不要
|
||||
String location_line_date = """
|
||||
{
|
||||
"type": "FeatureCollection",
|
||||
|
||||
663
lib/main.dart
663
lib/main.dart
@ -1,44 +1,600 @@
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
//import 'dart:convert';
|
||||
//import 'dart:developer';
|
||||
import 'package:gifunavi/model/gps_data.dart';
|
||||
//import 'package:gifunavi/pages/home/home_page.dart';
|
||||
import 'package:gifunavi/utils/database_gps.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
|
||||
//import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/index/index_binding.dart';
|
||||
import 'package:rogapp/routes/app_pages.dart';
|
||||
import 'package:rogapp/utils/string_values.dart';
|
||||
//import 'package:vm_service/vm_service.dart';
|
||||
//import 'package:dart_vm_info/dart_vm_info.dart';
|
||||
import 'package:timezone/data/latest.dart' as tz;
|
||||
|
||||
import 'package:gifunavi/pages/settings/settings_controller.dart';
|
||||
|
||||
import 'package:gifunavi/pages/destination/destination_controller.dart';
|
||||
import 'package:gifunavi/pages/index/index_binding.dart';
|
||||
import 'package:gifunavi/pages/index/index_controller.dart';
|
||||
import 'package:gifunavi/routes/app_pages.dart';
|
||||
import 'package:gifunavi/utils/location_controller.dart';
|
||||
import 'package:gifunavi/utils/string_values.dart';
|
||||
import 'package:gifunavi/widgets/debug_widget.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
// import 'package:is_lock_screen/is_lock_screen.dart';
|
||||
|
||||
//import 'package:gifunavi/services/device_info_service.dart';
|
||||
import 'package:gifunavi/services/error_service.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
//import 'dart:async';
|
||||
//import 'package:get/get.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
String? userToken;
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
import 'pages/permission/permission.dart';
|
||||
import 'package:gifunavi/services/api_service.dart';
|
||||
|
||||
import 'package:gifunavi/provider/cached_tile_provider.dart';
|
||||
import 'package:gifunavi/pages/entry/entry_controller.dart';
|
||||
import 'package:gifunavi/pages/team/team_controller.dart';
|
||||
|
||||
Map<String, dynamic> deviceInfo = {};
|
||||
|
||||
/*
|
||||
void saveGameState() async {
|
||||
DestinationController destinationController =
|
||||
Get.find<DestinationController>();
|
||||
SharedPreferences pref = await SharedPreferences.getInstance();
|
||||
pref.setBool("is_in_rog", destinationController.isInRog.value);
|
||||
pref.setBool(
|
||||
"rogaining_counted", destinationController.rogainingCounted.value);
|
||||
pref.setBool("ready_for_goal", DestinationController.ready_for_goal);
|
||||
}
|
||||
*/
|
||||
|
||||
// 現在のユーザーのIDも一緒に保存するようにします。
|
||||
void saveGameState() async {
|
||||
DestinationController destinationController =
|
||||
Get.find<DestinationController>();
|
||||
IndexController indexController = Get.find<IndexController>();
|
||||
SharedPreferences pref = await SharedPreferences.getInstance();
|
||||
debugPrint("indexController.currentUser = ${indexController.currentUser}");
|
||||
if(indexController.currentUser.isNotEmpty) {
|
||||
pref.setInt("user_id", indexController.currentUser[0]["user"]["id"]);
|
||||
}else{
|
||||
debugPrint("User is empty....");
|
||||
}
|
||||
pref.setBool("is_in_rog", destinationController.isInRog.value);
|
||||
pref.setBool(
|
||||
"rogaining_counted", destinationController.rogainingCounted.value);
|
||||
pref.setBool("ready_for_goal", DestinationController.ready_for_goal);
|
||||
}
|
||||
|
||||
/*
|
||||
void restoreGame() async {
|
||||
SharedPreferences pref = await SharedPreferences.getInstance();
|
||||
DestinationController destinationController =
|
||||
Get.find<DestinationController>();
|
||||
destinationController.skipGps = false;
|
||||
destinationController.isInRog.value = pref.getBool("is_in_rog") ?? false;
|
||||
destinationController.rogainingCounted.value =
|
||||
pref.getBool("rogaining_counted") ?? false;
|
||||
DestinationController.ready_for_goal =
|
||||
pref.getBool("ready_for_goal") ?? false;
|
||||
//print(
|
||||
// "--restored -- destinationController.isInRog.value ${pref.getBool("is_in_rog")} -- ${pref.getBool("rogaining_counted")}");
|
||||
}
|
||||
*/
|
||||
|
||||
void restoreGame() async {
|
||||
SharedPreferences pref = await SharedPreferences.getInstance();
|
||||
IndexController indexController = Get.find<IndexController>();
|
||||
int? savedUserId = pref.getInt("user_id");
|
||||
//int? currUserId = indexController.currentUser[0]['user']['id'];
|
||||
//debugPrint("savedUserId=${savedUserId}, currentUser=${currUserId}");
|
||||
if (indexController.currentUser.isNotEmpty &&
|
||||
indexController.currentUser[0]["user"]["id"] == savedUserId) {
|
||||
DestinationController destinationController =
|
||||
Get.find<DestinationController>();
|
||||
destinationController.skipGps = false;
|
||||
destinationController.isInRog.value = pref.getBool("is_in_rog") ?? false;
|
||||
destinationController.rogainingCounted.value =
|
||||
pref.getBool("rogaining_counted") ?? false;
|
||||
DestinationController.ready_for_goal =
|
||||
pref.getBool("ready_for_goal") ?? false;
|
||||
await Get.putAsync(() => ApiService().init());
|
||||
}
|
||||
}
|
||||
|
||||
void main() async {
|
||||
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
await FlutterMapTileCaching.initialise();
|
||||
final StoreDirectory instanceA = FMTC.instance('OpenStreetMap (A)');
|
||||
await instanceA.manage.createAsync();
|
||||
await instanceA.metadata.addAsync(
|
||||
key: 'sourceURL',
|
||||
value: 'https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png',
|
||||
);
|
||||
await instanceA.metadata.addAsync(
|
||||
key: 'validDuration',
|
||||
value: '14',
|
||||
);
|
||||
await instanceA.metadata.addAsync(
|
||||
key: 'behaviour',
|
||||
value: 'cacheFirst',
|
||||
);
|
||||
|
||||
runApp(const MyApp());
|
||||
Get.put(LocationController());
|
||||
/*
|
||||
Get.put(ApiService());
|
||||
Get.put(TeamController());
|
||||
Get.put(EntryController());
|
||||
Get.put(IndexController());
|
||||
*/
|
||||
/*
|
||||
await FlutterMapTileCaching.initialise();
|
||||
|
||||
final StoreDirectory instanceA = FMTC.instance('OpenStreetMap (A)');
|
||||
await instanceA.manage.createAsync();
|
||||
await instanceA.metadata.addAsync(
|
||||
key: 'sourceURL',
|
||||
value: 'https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png',
|
||||
);
|
||||
await instanceA.metadata.addAsync(
|
||||
key: 'validDuration',
|
||||
value: '14',
|
||||
);
|
||||
await instanceA.metadata.addAsync(
|
||||
key: 'behaviour',
|
||||
value: 'cacheFirst',
|
||||
);
|
||||
*/
|
||||
// 新しいキャッシュプロバイダーの初期化
|
||||
await CacheProvider.initialize();
|
||||
|
||||
// 使用不可
|
||||
//deviceInfo = await DeviceInfoService.getDeviceInfo();
|
||||
|
||||
FlutterError.onError = (FlutterErrorDetails details) {
|
||||
FlutterError.presentError(details);
|
||||
Get.log('Flutter error: ${details.exception}');
|
||||
Get.log('Stack trace: ${details.stack}');
|
||||
ErrorService.reportError(details.exception, details.stack ?? StackTrace.current, deviceInfo, LogManager().operationLogs);
|
||||
};
|
||||
|
||||
//Get.put(LocationController());
|
||||
|
||||
//await PermissionController.checkAndRequestPermissions();
|
||||
//requestLocationPermission();
|
||||
|
||||
|
||||
|
||||
// startMemoryMonitoring(); // 2024-4-8 Akira: メモリ使用量のチェックを開始 See #2810
|
||||
Get.put(SettingsController()); // これを追加
|
||||
|
||||
|
||||
/*
|
||||
runZonedGuarded(() {
|
||||
runApp(const ProviderScope(child: MyApp()));
|
||||
}, (error, stackTrace) {
|
||||
ErrorService.reportError(error, stackTrace, deviceInfo);
|
||||
});
|
||||
*/
|
||||
|
||||
FlutterError.onError = (FlutterErrorDetails details) {
|
||||
FlutterError.presentError(details);
|
||||
debugPrint('Flutter error: ${details.exception}');
|
||||
debugPrint('Stack trace: ${details.stack}');
|
||||
};
|
||||
|
||||
try {
|
||||
tz.initializeTimeZones();
|
||||
|
||||
// ApiServiceを初期化
|
||||
//await Get.putAsync(() => ApiService().init());
|
||||
await initServices();
|
||||
|
||||
runApp(const ProviderScope(child: MyApp()));
|
||||
//runApp(HomePage()); // MyApp()からHomePage()に変更
|
||||
//runApp(const MyApp());
|
||||
}catch(e, stackTrace){
|
||||
print('Error during initialization: $e');
|
||||
print('Stack trace: $stackTrace');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initServices() async {
|
||||
print('Starting services ...');
|
||||
try {
|
||||
await Get.putAsync(() => ApiService().init());
|
||||
print('All services started...');
|
||||
}catch(e){
|
||||
print('Error initializing ApiService: $e');
|
||||
}
|
||||
|
||||
try {
|
||||
Get.put(SettingsController());
|
||||
print('SettingsController initialized successfully');
|
||||
} catch (e) {
|
||||
print('Error initializing SettingsController: $e');
|
||||
}
|
||||
|
||||
print('All services started...');
|
||||
|
||||
}
|
||||
|
||||
Future<void> requestLocationPermission() async {
|
||||
try {
|
||||
final status = await Permission.locationAlways.request();
|
||||
if (status == PermissionStatus.granted) {
|
||||
print('Location permission granted');
|
||||
} else {
|
||||
print('Location permission denied');
|
||||
//await showLocationPermissionDeniedDialog(); // 追加
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error requesting location permission: $e');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({Key? key}) : super(key: key);
|
||||
|
||||
// メモリ使用量の解説: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'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
StreamSubscription<Position>? positionStream;
|
||||
bool background=false;
|
||||
DateTime lastGPSCollectedTime=DateTime.now();
|
||||
String team_name="";
|
||||
String event_code="";
|
||||
|
||||
Future<void> startBackgroundTracking() async {
|
||||
if (Platform.isIOS && background==false) {
|
||||
|
||||
final IndexController indexController = Get.find<IndexController>();
|
||||
if(indexController.currentUser.isNotEmpty) {
|
||||
team_name = indexController.currentUser[0]["user"]['team_name'];
|
||||
event_code = indexController.currentUser[0]["user"]["event_code"];
|
||||
}
|
||||
background = true;
|
||||
debugPrint("バックグラウンド処理を開始しました。");
|
||||
const LocationSettings locationSettings = LocationSettings(
|
||||
accuracy: LocationAccuracy.high,
|
||||
distanceFilter: 100,
|
||||
);
|
||||
|
||||
try {
|
||||
positionStream = Geolocator.getPositionStream(locationSettings: locationSettings)
|
||||
.listen((Position? position) async {
|
||||
if (position != null) {
|
||||
final lat = position.latitude;
|
||||
final lng = position.longitude;
|
||||
//final timestamp = DateTime.now();
|
||||
final accuracy = position.accuracy;
|
||||
|
||||
// GPS信号強度がlowの場合はスキップ
|
||||
if (accuracy > 100) {
|
||||
debugPrint("GPS signal strength is low. Skipping data saving.");
|
||||
return;
|
||||
}
|
||||
|
||||
Duration difference = lastGPSCollectedTime.difference(DateTime.now())
|
||||
.abs();
|
||||
// 最後にGPS信号を取得した時刻から10秒以上経過、かつ10m以上経過(普通に歩くスピード)
|
||||
//debugPrint("時間差:${difference}");
|
||||
if (difference.inSeconds >= 10 ) {
|
||||
debugPrint("バックグラウンドでのGPS取得時の処理(10secおき) count=${difference.inSeconds}, time=${DateTime.now()}");
|
||||
|
||||
// DBにGPSデータを保存 pages/destination/destination_controller.dart
|
||||
await addGPStoDB(lat, lng);
|
||||
|
||||
lastGPSCollectedTime = DateTime.now();
|
||||
}
|
||||
}
|
||||
}, onError: (error) {
|
||||
if (error is LocationServiceDisabledException) {
|
||||
print('Location services are disabled');
|
||||
} else if (error is PermissionDeniedException) {
|
||||
print('Location permissions are denied');
|
||||
} else {
|
||||
print('Location Error: $error');
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
print('Error starting background tracking: $e');
|
||||
}
|
||||
}else if (Platform.isAndroid && background == false) {
|
||||
background = true;
|
||||
debugPrint("バックグラウンド処理を開始しました。");
|
||||
|
||||
try {
|
||||
// 位置情報の権限が許可されているかを確認
|
||||
await PermissionController.checkAndRequestPermissions();
|
||||
}catch(e){
|
||||
print('Error starting background tracking: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> addGPStoDB(double la, double ln) async {
|
||||
//debugPrint("in addGPStoDB ${indexController.currentUser}");
|
||||
GpsDatabaseHelper db = GpsDatabaseHelper.instance;
|
||||
try {
|
||||
GpsData gpsData = GpsData(
|
||||
id: 0,
|
||||
team_name: team_name,
|
||||
event_code: event_code,
|
||||
lat: la,
|
||||
lon: ln,
|
||||
is_checkin: 0,
|
||||
created_at: DateTime.now().millisecondsSinceEpoch);
|
||||
var res = await db.insertGps(gpsData);
|
||||
debugPrint("バックグラウンドでのGPS保存:");
|
||||
} catch (err) {
|
||||
print("errr ready gps $err");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> stopBackgroundTracking() async {
|
||||
if (Platform.isIOS && background==true) {
|
||||
background=false;
|
||||
debugPrint("バックグラウンド処理:停止しました。");
|
||||
await positionStream?.cancel();
|
||||
positionStream = null;
|
||||
}else if(Platform.isAndroid && background==true){
|
||||
background=false;
|
||||
debugPrint("バックグラウンド処理:停止しました。");
|
||||
const platform = MethodChannel('location');
|
||||
try {
|
||||
await platform.invokeMethod('stopLocationService');
|
||||
} on PlatformException catch (e) {
|
||||
print("Failed to stop location service: '${e.message}'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MyApp extends StatefulWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
State<MyApp> createState() => _MyAppState();
|
||||
}
|
||||
|
||||
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
||||
// This widget is the root of your application.
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (!Get.isRegistered<LocationController>()) {
|
||||
Get.put(LocationController());
|
||||
}
|
||||
|
||||
if (context.mounted) {
|
||||
restoreGame();
|
||||
}
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
||||
// ウィジェットが構築された後に権限をチェック
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
PermissionController.checkAndRequestPermissions();
|
||||
});
|
||||
|
||||
debugPrint("Start MyAppState...");
|
||||
}
|
||||
|
||||
/*
|
||||
void showPermissionRequiredDialog() {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('権限が必要です'),
|
||||
content: Text('このアプリは機能するために位置情報の権限が必要です。設定で権限を許可してください。'),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: Text('設定を開く'),
|
||||
onPressed: () {
|
||||
openAppSettings();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text('アプリを終了'),
|
||||
onPressed: () {
|
||||
// アプリを終了
|
||||
Navigator.of(context).pop();
|
||||
// よりクリーンな終了のために 'flutter_exit_app' のようなプラグインを使用することをお勧めします
|
||||
// 今回は単にすべてのルートをポップします
|
||||
Navigator.of(context).popUntil((route) => false);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// void saveGameState() async {
|
||||
// DestinationController destinationController = Get.find<DestinationController>();
|
||||
// SharedPreferences pref = await SharedPreferences.getInstance();
|
||||
// pref.setBool("is_in_rog", destinationController.is_in_rog.value);
|
||||
// pref.setBool("rogaining_counted", destinationController.rogaining_counted.value);
|
||||
// }
|
||||
|
||||
@override
|
||||
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
|
||||
try {
|
||||
LocationController locationController = Get.find<LocationController>();
|
||||
|
||||
DestinationController destinationController = Get.find<
|
||||
DestinationController>();
|
||||
|
||||
//DestinationController destinationController =
|
||||
// Get.find<DestinationController>();
|
||||
switch (state) {
|
||||
case AppLifecycleState.resumed:
|
||||
// 追加 2024.8.13.
|
||||
await stopBackgroundTracking();
|
||||
destinationController.restartGPS();
|
||||
// 追加 2024.8.13.
|
||||
|
||||
// バックグラウンド処理を停止
|
||||
if (Platform.isIOS && destinationController.isRunningBackgroundGPS) {
|
||||
// Foreground に戻った時の処理
|
||||
debugPrint(
|
||||
" ==(Status Changed)==> RESUMED. フォアグラウンドに戻りました");
|
||||
locationController.resumePositionStream();
|
||||
//print("RESUMED");
|
||||
restoreGame();
|
||||
|
||||
stopBackgroundTracking();
|
||||
destinationController.isRunningBackgroundGPS = false;
|
||||
destinationController.restartGPS();
|
||||
} else if (Platform.isAndroid) {
|
||||
if (destinationController.isRunningBackgroundGPS) {
|
||||
const platform = MethodChannel('location');
|
||||
platform.invokeMethod('stopLocationService');
|
||||
destinationController.isRunningBackgroundGPS = false;
|
||||
destinationController.restartGPS();
|
||||
debugPrint("stopped android location service..");
|
||||
}
|
||||
|
||||
debugPrint(
|
||||
"==(Status Changed)==> RESUMED. android フォアグラウンドに戻りました");
|
||||
locationController.resumePositionStream();
|
||||
//print("RESUMED");
|
||||
restoreGame();
|
||||
} else {
|
||||
debugPrint("==(Status Changed)==> RESUMED 不明状態");
|
||||
}
|
||||
break;
|
||||
case AppLifecycleState.inactive:
|
||||
// アプリが非アクティブになったときに発生します。
|
||||
|
||||
if (Platform.isIOS && !destinationController
|
||||
.isRunningBackgroundGPS) { // iOSはバックグラウンドでもフロントの処理が生きている。
|
||||
// これは、別のアプリやシステムのオーバーレイ(着信通話やアラームなど)によって一時的に中断された状態です。
|
||||
debugPrint(" ==(Status Changed)==> INACTIVE. 非アクティブ処理。");
|
||||
//locationController.resumePositionStream();
|
||||
|
||||
// 追加: フロントエンドのGPS信号のlistenを停止
|
||||
locationController.stopPositionStream();
|
||||
|
||||
destinationController.isRunningBackgroundGPS = true;
|
||||
startBackgroundTracking();
|
||||
} else if (Platform.isAndroid &&
|
||||
!destinationController.isRunningBackgroundGPS) {
|
||||
debugPrint(" ==(Status Changed)==> INACTIVE. 非アクティブ処理。");
|
||||
} else {
|
||||
debugPrint("==(Status Changed)==> INACTIVE 不明状態");
|
||||
}
|
||||
saveGameState();
|
||||
break;
|
||||
case AppLifecycleState.paused:
|
||||
// バックグラウンドに移行したときの処理
|
||||
//locationController.resumePositionStream();
|
||||
debugPrint(" ==(Status Changed)==> PAUSED. バックグラウンド処理。");
|
||||
if (Platform.isIOS && !destinationController.isRunningBackgroundGPS) {
|
||||
debugPrint(
|
||||
"iOS already running background GPS processing when it's inactive");
|
||||
} else if (Platform.isAndroid &&
|
||||
!destinationController.isRunningBackgroundGPS) {
|
||||
debugPrint(
|
||||
" ==(Status Changed)==> PAUSED. Android バックグラウンド処理。");
|
||||
locationController.stopPositionStream();
|
||||
const platform = MethodChannel('location');
|
||||
platform.invokeMethod('startLocationService');
|
||||
//platform.invokeMethod('stopLocationService');
|
||||
destinationController.isRunningBackgroundGPS = true;
|
||||
//startBackgroundTracking();
|
||||
}
|
||||
saveGameState();
|
||||
break;
|
||||
case AppLifecycleState.detached:
|
||||
// アプリが終了する直前に発生します。この状態では、アプリはメモリから解放される予定です。
|
||||
//locationController.resumePositionStream();
|
||||
debugPrint(" ==(Status Changed)==> DETACHED アプリは終了します。");
|
||||
saveGameState();
|
||||
break;
|
||||
case AppLifecycleState.hidden:
|
||||
// Web用の特殊な状態で、モバイルアプリでは発生しません。
|
||||
//locationController.resumePositionStream();
|
||||
debugPrint(" ==(Status Changed)==> Hidden アプリが隠れた");
|
||||
saveGameState();
|
||||
break;
|
||||
}
|
||||
}catch(e){
|
||||
print('Error finding LocationController: $e');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return GetMaterialApp(
|
||||
translations: StringValues(),
|
||||
locale: const Locale('ja', 'JP'),
|
||||
@ -47,7 +603,7 @@ class MyApp extends StatelessWidget {
|
||||
title: 'ROGAINING',
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: const Color.fromARGB(255, 4, 88, 161)),
|
||||
seedColor: const Color.fromARGB(255, 36, 135, 221)),
|
||||
useMaterial3: true,
|
||||
),
|
||||
debugShowCheckedModeBanner: false,
|
||||
@ -55,58 +611,13 @@ class MyApp extends StatelessWidget {
|
||||
opaqueRoute: Get.isOpaqueRouteDefault,
|
||||
popGesture: Get.isPopGestureEnable,
|
||||
transitionDuration: const Duration(milliseconds: 230),
|
||||
initialBinding: IndexBinding(userToken), //HomeBinding(),
|
||||
initialBinding: IndexBinding(), //HomeBinding(),
|
||||
initialRoute: AppPages.PERMISSION,
|
||||
getPages: AppPages.routes,
|
||||
enableLog: true,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// class MyApp extends StatelessWidget {
|
||||
// MyApp({Key? key}) : super(key: key);
|
||||
|
||||
// // This widget is the root of your application.
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return MaterialApp(
|
||||
// title: 'Flutter Demo',
|
||||
// theme: ThemeData(
|
||||
// primaryColor: Color(0xfff00B074),
|
||||
// textTheme: const TextTheme(
|
||||
// bodyText1: TextStyle(
|
||||
// fontSize: 18.0,
|
||||
// fontFamily: 'Barlow-Medium',
|
||||
// color: Color(0xff464255)),
|
||||
// ),
|
||||
// ),
|
||||
// home: PermissionHandlerScreen(),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
// class SplashScreen extends StatelessWidget {
|
||||
// const SplashScreen({Key? key}) : super(key: key);
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return WillPopScope(
|
||||
// onWillPop: () async {
|
||||
// SystemNavigator.pop();
|
||||
// return true;
|
||||
// },
|
||||
// child: Scaffold(
|
||||
// body: Center(
|
||||
// child: Text(
|
||||
// "Splash Screen",
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
27
lib/model/auth_user.dart
Normal file
27
lib/model/auth_user.dart
Normal file
@ -0,0 +1,27 @@
|
||||
// プロパティの型がString?やint?などのオプショナル型になっています。
|
||||
// これらのプロパティが常に値を持つことが保証されている場合は、非オプショナル型を使用することで、不要なnullチェックを回避できます。
|
||||
//
|
||||
class AuthUser {
|
||||
AuthUser();
|
||||
|
||||
//AuthUser.from({required this.id, required this.email, required this.is_rogaining, required this.group, required this.zekken_number, required this.event_code, required this.team_name});
|
||||
|
||||
AuthUser.fromMap(Map<String, dynamic> map)
|
||||
: id = int.parse(map["id"].toString()),
|
||||
email = map["email"].toString(),
|
||||
is_rogaining = bool.parse(map["is_rogaining"].toString()),
|
||||
group = map["group"].toString(),
|
||||
zekken_number = map["zekken_number"].toString(),
|
||||
event_code = map["event_code"].toString(),
|
||||
team_name = map["team_name"].toString(),
|
||||
auth_token = map["token"];
|
||||
|
||||
int? id;
|
||||
String? email;
|
||||
bool? is_rogaining;
|
||||
String? group;
|
||||
String? zekken_number;
|
||||
String? event_code;
|
||||
String? team_name;
|
||||
String? auth_token;
|
||||
}
|
||||
80
lib/model/category.dart
Normal file
80
lib/model/category.dart
Normal file
@ -0,0 +1,80 @@
|
||||
// lib/models/category.dart
|
||||
|
||||
class NewCategory {
|
||||
final int id;
|
||||
final String categoryName;
|
||||
final int categoryNumber;
|
||||
final Duration duration;
|
||||
final int numOfMember;
|
||||
final bool family;
|
||||
final bool female;
|
||||
final String? time;
|
||||
|
||||
NewCategory({
|
||||
required this.id,
|
||||
required this.categoryName,
|
||||
this.time,
|
||||
required this.categoryNumber,
|
||||
required this.duration,
|
||||
required this.numOfMember,
|
||||
required this.family,
|
||||
required this.female,
|
||||
});
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is NewCategory &&
|
||||
runtimeType == other.runtimeType &&
|
||||
id == other.id;
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode;
|
||||
|
||||
factory NewCategory.fromJson(Map<String, dynamic> json) {
|
||||
final fullCategoryName = json['category_name'] as String;
|
||||
final parts = fullCategoryName.split('-');
|
||||
final baseName = parts[0].trim();
|
||||
final time = parts.length > 1 ? parts[1].trim() : null;
|
||||
|
||||
return NewCategory(
|
||||
id: json['id'] ?? 0,
|
||||
categoryName: json['category_name'] ?? 'Unknown Category',
|
||||
time: time,
|
||||
categoryNumber: json['category_number'] ?? 0,
|
||||
duration: parseDuration(json['duration']),
|
||||
numOfMember: json['num_of_member'] ?? 1,
|
||||
family: json['family'] ?? false,
|
||||
female: json['female'] ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
String get baseCategory => categoryName.split('-')[0].trim();
|
||||
|
||||
static Duration parseDuration(String s) {
|
||||
int hours = 0;
|
||||
int minutes = 0;
|
||||
int micros;
|
||||
List<String> parts = s.split(':');
|
||||
if (parts.length > 2) {
|
||||
hours = int.parse(parts[parts.length - 3]);
|
||||
}
|
||||
if (parts.length > 1) {
|
||||
minutes = int.parse(parts[parts.length - 2]);
|
||||
}
|
||||
micros = (double.parse(parts[parts.length - 1]) * 1000000).round();
|
||||
return Duration(hours: hours, minutes: minutes, microseconds: micros);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'category_name': categoryName,
|
||||
'category_number': categoryNumber,
|
||||
'duration': duration.inSeconds,
|
||||
'num_of_member': numOfMember,
|
||||
'family': family,
|
||||
'female': female,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,113 +1,138 @@
|
||||
|
||||
|
||||
// プロパティの型がString?やint?などのオプショナル型になっています。
|
||||
// これらのプロパティが常に値を持つことが保証されている場合は、非オプショナル型を使用することで、不要なnullチェックを回避できます。
|
||||
//
|
||||
class Destination {
|
||||
String? name;
|
||||
String? address;
|
||||
String? phone;
|
||||
String? email;
|
||||
String? webcontents;
|
||||
String? videos;
|
||||
String? category;
|
||||
int? series;
|
||||
double? lat;
|
||||
double? lon;
|
||||
String? sub_loc_id;
|
||||
int? location_id;
|
||||
int? list_order;
|
||||
String? photos;
|
||||
double? checkin_radious;
|
||||
int? auto_checkin;
|
||||
bool? selected = false;
|
||||
bool? checkedin = false;
|
||||
double? cp;
|
||||
double? checkin_point;
|
||||
double? buy_point;
|
||||
int? hidden_location;
|
||||
String? name;
|
||||
String? address;
|
||||
String? phone;
|
||||
String? email;
|
||||
String? webcontents;
|
||||
String? videos;
|
||||
String? category;
|
||||
int? series;
|
||||
double? lat;
|
||||
double? lon;
|
||||
String? sub_loc_id;
|
||||
int? location_id;
|
||||
int? list_order;
|
||||
String? photos;
|
||||
double? checkin_radious;
|
||||
int? auto_checkin;
|
||||
bool? selected = false;
|
||||
bool? checkedin = false;
|
||||
double? cp;
|
||||
double? checkin_point;
|
||||
double? buy_point;
|
||||
int? hidden_location;
|
||||
String? checkin_image;
|
||||
String? buypoint_image;
|
||||
bool forced_checkin = false;
|
||||
int recipt_times = 0;
|
||||
String? tags;
|
||||
|
||||
Destination({
|
||||
this.name,
|
||||
this.address,
|
||||
this.phone,
|
||||
this.email,
|
||||
this.webcontents,
|
||||
this.videos,
|
||||
this.category,
|
||||
this.series,
|
||||
this.lat,
|
||||
this.lon,
|
||||
this.sub_loc_id,
|
||||
this.location_id,
|
||||
this.list_order,
|
||||
this.photos,
|
||||
this.checkin_radious,
|
||||
this.auto_checkin,
|
||||
this.selected,
|
||||
this.checkedin,
|
||||
this.cp,
|
||||
this.checkin_point,
|
||||
this.buy_point,
|
||||
this.hidden_location
|
||||
});
|
||||
bool use_qr_code = false; // QR code を使用するかどうか。default=false
|
||||
|
||||
factory Destination.fromMap(Map<String, dynamic> json) {
|
||||
Destination(
|
||||
{this.name,
|
||||
this.address,
|
||||
this.phone,
|
||||
this.email,
|
||||
this.webcontents,
|
||||
this.videos,
|
||||
this.category,
|
||||
this.series,
|
||||
this.lat,
|
||||
this.lon,
|
||||
this.sub_loc_id,
|
||||
this.location_id,
|
||||
this.list_order,
|
||||
this.photos,
|
||||
this.checkin_radious,
|
||||
this.auto_checkin,
|
||||
this.selected,
|
||||
this.checkedin,
|
||||
this.cp,
|
||||
this.checkin_point,
|
||||
this.buy_point,
|
||||
this.hidden_location,
|
||||
this.checkin_image,
|
||||
this.buypoint_image,
|
||||
this.forced_checkin = false,
|
||||
this.recipt_times = 0,
|
||||
this.tags}); //, ... use_qr_code をDBに追加したらオープン
|
||||
// this.use_qr_code = false});
|
||||
|
||||
bool selec = json['selected'] == 0 ? false : true;
|
||||
bool checkin = json['checkedin'] == 0 ? false : true;
|
||||
factory Destination.fromMap(Map<String, dynamic> json) {
|
||||
bool selec = json['selected'] == 0 ? false : true;
|
||||
bool checkin = json['checkedin'] == 0 ? false : true;
|
||||
bool forcedCheckin = json['forced_checkin'] == 0 ? false : true;
|
||||
bool useQrCode = json['use_qr_code'] == 1 ? true : false;
|
||||
//print("-----tags model----- ${json}");
|
||||
|
||||
return Destination(
|
||||
name: json['name'],
|
||||
address: json['address'],
|
||||
phone: json['phone'],
|
||||
email: json['email'],
|
||||
webcontents: json['webcontents'],
|
||||
videos: json['videos'],
|
||||
category: json['category'],
|
||||
series: json['series'],
|
||||
lat: json['lat'],
|
||||
lon: json['lon'],
|
||||
sub_loc_id : json['sub_loc_id'],
|
||||
location_id: json['location_id'],
|
||||
list_order: json['list_order'],
|
||||
photos: json['photos'],
|
||||
checkin_radious: json['checkin_radious'],
|
||||
auto_checkin:json['auto_checkin'],
|
||||
selected: selec,
|
||||
checkedin: checkin,
|
||||
cp: json['cp'],
|
||||
checkin_point: json['checkin_point'],
|
||||
buy_point: json['buy_point'],
|
||||
hidden_location: json['hidden_location']
|
||||
);
|
||||
}
|
||||
return Destination(
|
||||
name: json['name'],
|
||||
address: json['address'],
|
||||
phone: json['phone'],
|
||||
email: json['email'],
|
||||
webcontents: json['webcontents'],
|
||||
videos: json['videos'],
|
||||
category: json['category'],
|
||||
series: json['series'],
|
||||
lat: json['lat'],
|
||||
lon: json['lon'],
|
||||
sub_loc_id: json['sub_loc_id'],
|
||||
location_id: json['location_id'],
|
||||
list_order: json['list_order'],
|
||||
photos: json['photos'],
|
||||
checkin_radious: json['checkin_radious'],
|
||||
auto_checkin: json['auto_checkin'],
|
||||
selected: selec,
|
||||
checkedin: checkin,
|
||||
cp: json['cp'],
|
||||
checkin_point: json['checkin_point'],
|
||||
buy_point: json['buy_point'],
|
||||
hidden_location: json['hidden_location'],
|
||||
checkin_image: json['checkin_image'],
|
||||
buypoint_image: json["buypoint_image"],
|
||||
forced_checkin: forcedCheckin,
|
||||
recipt_times: json["recipt_times"],
|
||||
tags: json["tags"] ); //,
|
||||
// use_qr_code: useQrCode);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap(){
|
||||
int sel = selected == false ? 0 : 1;
|
||||
int check = checkedin == false ? 0 : 1;
|
||||
return {
|
||||
'name':name,
|
||||
'address': address,
|
||||
'phone': phone,
|
||||
'email': email,
|
||||
'webcontents': webcontents,
|
||||
'videos': videos,
|
||||
'category':category,
|
||||
'series':series,
|
||||
'lat':lat,
|
||||
'lon':lon,
|
||||
'sub_loc_id': sub_loc_id,
|
||||
'location_id':location_id,
|
||||
'list_order':list_order,
|
||||
'photos':photos,
|
||||
'checkin_radious': checkin_radious,
|
||||
'auto_checkin': auto_checkin,
|
||||
'selected': sel,
|
||||
'checkedin': check,
|
||||
'cp' : cp,
|
||||
'checkin_point' : checkin_point,
|
||||
'buy_point' : buy_point,
|
||||
'hidden_location' : hidden_location
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Map<String, dynamic> toMap() {
|
||||
int sel = selected == false ? 0 : 1;
|
||||
int check = checkedin == false ? 0 : 1;
|
||||
int forcedCheckin = forced_checkin == false ? 0 : 1;
|
||||
return {
|
||||
'name': name,
|
||||
'address': address,
|
||||
'phone': phone,
|
||||
'email': email,
|
||||
'webcontents': webcontents,
|
||||
'videos': videos,
|
||||
'category': category,
|
||||
'series': series,
|
||||
'lat': lat,
|
||||
'lon': lon,
|
||||
'sub_loc_id': sub_loc_id,
|
||||
'location_id': location_id,
|
||||
'list_order': list_order,
|
||||
'photos': photos,
|
||||
'checkin_radious': checkin_radious,
|
||||
'auto_checkin': auto_checkin,
|
||||
'selected': sel,
|
||||
'checkedin': check,
|
||||
'cp': cp,
|
||||
'checkin_point': checkin_point,
|
||||
'buy_point': buy_point,
|
||||
'hidden_location': hidden_location,
|
||||
'checkin_image': checkin_image,
|
||||
'buypoint_image': buypoint_image,
|
||||
'forced_checkin': forcedCheckin,
|
||||
'recipt_times': recipt_times,
|
||||
'tags': tags //,
|
||||
//'use_qr_code': use_qr_code
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
50
lib/model/entry.dart
Normal file
50
lib/model/entry.dart
Normal file
@ -0,0 +1,50 @@
|
||||
// lib/models/entry.dart
|
||||
import 'event.dart';
|
||||
import 'team.dart';
|
||||
import 'category.dart';
|
||||
|
||||
class Entry {
|
||||
final int id;
|
||||
final Team team;
|
||||
final Event event;
|
||||
final NewCategory category;
|
||||
final DateTime? date;
|
||||
final int zekkenNumber; // 新しく追加
|
||||
final String owner;
|
||||
|
||||
Entry({
|
||||
required this.id,
|
||||
required this.team,
|
||||
required this.event,
|
||||
required this.category,
|
||||
required this.date,
|
||||
required this.zekkenNumber,
|
||||
required this.owner,
|
||||
});
|
||||
|
||||
factory Entry.fromJson(Map<String, dynamic> json) {
|
||||
return Entry(
|
||||
id: json['id'],
|
||||
team: Team.fromJson(json['team']),
|
||||
event: Event.fromJson(json['event']),
|
||||
category: NewCategory.fromJson(json['category']),
|
||||
date: json['date'] != null
|
||||
? DateTime.tryParse(json['date'])
|
||||
: null,
|
||||
zekkenNumber: json['zekken_number'], // 新しく追加
|
||||
owner: json['owner'] is Map ? json['owner']['name'] ?? '' : json['owner'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'team': team.toJson(),
|
||||
'event': event.toJson(),
|
||||
'category': category.toJson(),
|
||||
'date': date?.toIso8601String(),
|
||||
'zekken_number': zekkenNumber, // 新しく追加
|
||||
'owner': owner,
|
||||
};
|
||||
}
|
||||
}
|
||||
40
lib/model/event.dart
Normal file
40
lib/model/event.dart
Normal file
@ -0,0 +1,40 @@
|
||||
// lib/models/event.dart
|
||||
|
||||
class Event {
|
||||
final int id;
|
||||
final String eventName;
|
||||
final DateTime startDatetime;
|
||||
final DateTime endDatetime;
|
||||
final DateTime deadlineDateTime; // 新しく追加
|
||||
|
||||
Event({
|
||||
required this.id,
|
||||
required this.eventName,
|
||||
required this.startDatetime,
|
||||
required this.endDatetime,
|
||||
required this.deadlineDateTime,
|
||||
});
|
||||
|
||||
factory Event.fromJson(Map<String, dynamic> json) {
|
||||
final endDatetime = DateTime.parse(json['end_datetime']);
|
||||
return Event(
|
||||
id: json['id'],
|
||||
eventName: json['event_name'],
|
||||
startDatetime: DateTime.parse(json['start_datetime']),
|
||||
endDatetime: DateTime.parse(json['end_datetime']),
|
||||
deadlineDateTime: json['deadline_datetime'] != null
|
||||
? DateTime.parse(json['deadline_datetime'])
|
||||
: endDatetime.subtract(const Duration(days: 7)), // 仮の実装
|
||||
// deadlineDateTime: DateTime.parse(json['deadline_datetime']),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'event_name': eventName,
|
||||
'start_datetime': startDatetime.toIso8601String(),
|
||||
'end_datetime': endDatetime.toIso8601String(),
|
||||
};
|
||||
}
|
||||
}
|
||||
8
lib/model/game_state_instance.dart
Normal file
8
lib/model/game_state_instance.dart
Normal file
@ -0,0 +1,8 @@
|
||||
enum LocationState { noGps, notInCheckin, withinCheckin }
|
||||
|
||||
enum GameState { notStarted, startedNotCounted, startedCounted, nodeGoal }
|
||||
|
||||
class GameInsStatetance {
|
||||
LocationState locationState = LocationState.noGps;
|
||||
GameState gameState = GameState.notStarted;
|
||||
}
|
||||
47
lib/model/gps_data.dart
Normal file
47
lib/model/gps_data.dart
Normal file
@ -0,0 +1,47 @@
|
||||
class GpsData {
|
||||
int id;
|
||||
String team_name;
|
||||
String event_code;
|
||||
double lat;
|
||||
double lon;
|
||||
int is_checkin;
|
||||
int created_at;
|
||||
int is_synced;
|
||||
|
||||
GpsData({
|
||||
required this.id,
|
||||
required this.team_name,
|
||||
required this.event_code,
|
||||
required this.lat,
|
||||
required this.lon,
|
||||
required this.created_at,
|
||||
this.is_checkin = 0,
|
||||
this.is_synced = 0,
|
||||
});
|
||||
|
||||
factory GpsData.fromMap(Map<String, dynamic> json) {
|
||||
return GpsData(
|
||||
id: json["id"],
|
||||
team_name: json["team_name"],
|
||||
event_code: json["event_code"],
|
||||
lat: json["lat"],
|
||||
lon: json["lon"],
|
||||
is_checkin: json["is_checkin"],
|
||||
created_at: json["created_at"],
|
||||
is_synced: json["is_synced"] ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'id': id,
|
||||
'team_name': team_name,
|
||||
'event_code': event_code,
|
||||
'lat': lat,
|
||||
'lon': lon,
|
||||
'is_checkin': is_checkin,
|
||||
'created_at': created_at,
|
||||
'is_synced': is_synced,
|
||||
};
|
||||
}
|
||||
}
|
||||
6
lib/model/map_state_instance.dart
Normal file
6
lib/model/map_state_instance.dart
Normal file
@ -0,0 +1,6 @@
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
|
||||
class MapStateInstance {
|
||||
MapController? mapController;
|
||||
LatLngBounds? currentBounds;
|
||||
}
|
||||
51
lib/model/team.dart
Normal file
51
lib/model/team.dart
Normal file
@ -0,0 +1,51 @@
|
||||
// lib/models/team.dart
|
||||
|
||||
import 'category.dart';
|
||||
import 'user.dart';
|
||||
|
||||
class Team {
|
||||
final int id;
|
||||
// final String zekkenNumber;
|
||||
final String teamName;
|
||||
final NewCategory category;
|
||||
final User owner;
|
||||
List<User> members; // membersフィールドを追加
|
||||
|
||||
Team({
|
||||
required this.id,
|
||||
// required this.zekkenNumber,
|
||||
required this.teamName,
|
||||
required this.category,
|
||||
required this.owner,
|
||||
this.members = const [], // デフォルト値を空のリストに設定
|
||||
});
|
||||
|
||||
factory Team.fromJson(Map<String, dynamic> json) {
|
||||
return Team(
|
||||
id: json['id'] ?? 0,
|
||||
//zekkenNumber: json['zekken_number'] ?? 'Unknown',
|
||||
teamName: json['team_name'] ?? 'Unknown Team',
|
||||
category: json['category'] != null
|
||||
? NewCategory.fromJson(json['category'])
|
||||
: NewCategory(id: 0, categoryName: 'Unknown', categoryNumber: 0, duration: Duration.zero, numOfMember: 1, family: false, female: false),
|
||||
owner: json['owner'] != null
|
||||
? User.fromJson(json['owner'])
|
||||
: User(id: 0, email: 'unknown@example.com', firstname: 'Unknown', lastname: 'User', dateOfBirth: null, female: false, isActive: false),
|
||||
members: json['members'] != null // membersフィールドを解析
|
||||
? List<User>.from(json['members'].map((x) => User.fromJson(x)))
|
||||
: [],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
//'zekken_number': zekkenNumber,
|
||||
'team_name': teamName,
|
||||
'category': category.toJson(),
|
||||
'owner': owner.toJson(),
|
||||
'members': members.map((member) => member.toJson()).toList(), // membersフィールドをJSONに変換
|
||||
};
|
||||
}
|
||||
}
|
||||
47
lib/model/user.dart
Normal file
47
lib/model/user.dart
Normal file
@ -0,0 +1,47 @@
|
||||
// lib/models/user.dart
|
||||
|
||||
class User {
|
||||
final int? id;
|
||||
final String? email;
|
||||
final String firstname;
|
||||
final String lastname;
|
||||
final DateTime? dateOfBirth;
|
||||
late final bool female;
|
||||
final bool isActive;
|
||||
|
||||
User({
|
||||
this.id,
|
||||
this.email,
|
||||
required this.firstname,
|
||||
required this.lastname,
|
||||
this.dateOfBirth,
|
||||
required this.female,
|
||||
required this.isActive,
|
||||
});
|
||||
|
||||
factory User.fromJson(Map<String, dynamic> json) {
|
||||
return User(
|
||||
id: json['id'],
|
||||
email: json['email'],
|
||||
firstname: json['firstname'] ?? 'Unknown',
|
||||
lastname: json['lastname'] ?? 'Unknown',
|
||||
dateOfBirth: json['date_of_birth'] != null
|
||||
? DateTime.tryParse(json['date_of_birth'])
|
||||
: null,
|
||||
female: json['female'] ?? false,
|
||||
isActive: json['is_active'] ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'email': email,
|
||||
'firstname': firstname,
|
||||
'lastname': lastname,
|
||||
'date_of_birth': dateOfBirth?.toIso8601String(),
|
||||
'female': female,
|
||||
'is_active': isActive,
|
||||
};
|
||||
}
|
||||
}
|
||||
227
lib/nrog/pages/auth_page.dart
Normal file
227
lib/nrog/pages/auth_page.dart
Normal file
@ -0,0 +1,227 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
import 'package:keyboard_dismisser/keyboard_dismisser.dart';
|
||||
import 'package:gifunavi/model/auth_user.dart';
|
||||
import 'package:gifunavi/nrog/pages/home_page.dart';
|
||||
import 'package:gifunavi/provider/auth_provider.dart';
|
||||
import 'package:gifunavi/services/auth_service.dart';
|
||||
import 'package:gifunavi/widgets/c_form_text_field.dart';
|
||||
import 'package:gifunavi/widgets/c_password_text_filed.dart';
|
||||
|
||||
class AuthPage extends ConsumerStatefulWidget {
|
||||
const AuthPage({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<AuthPage> createState() => _AuthPageState();
|
||||
}
|
||||
|
||||
class _AuthPageState extends ConsumerState<AuthPage> {
|
||||
final _formkey = GlobalKey<FormState>();
|
||||
final FocusNode focusEmail = FocusNode();
|
||||
final FocusNode focusPwd = FocusNode();
|
||||
var _authMode = 'login';
|
||||
bool _isLoginProgress = false;
|
||||
|
||||
final TextEditingController _emailTextEditingController =
|
||||
TextEditingController();
|
||||
final TextEditingController _passwordTextEditingController =
|
||||
TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_emailTextEditingController.addListener(() => setState(() {}));
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
checkUser();
|
||||
});
|
||||
}
|
||||
|
||||
void _submit() async {
|
||||
setState(() {
|
||||
_isLoginProgress = true;
|
||||
});
|
||||
if (_formkey.currentState!.validate()) {
|
||||
AuthService authService = AuthService();
|
||||
AuthUser? user = await authService.userLogin(
|
||||
_emailTextEditingController.text,
|
||||
_passwordTextEditingController.text);
|
||||
if (user != null) {
|
||||
setState(() {
|
||||
ref.read(authUserStateProvider.notifier).addLogin(user);
|
||||
});
|
||||
}
|
||||
}
|
||||
setState(() {
|
||||
_isLoginProgress = false;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _submitToken(String token) async {
|
||||
setState(() {
|
||||
_isLoginProgress = true;
|
||||
});
|
||||
AuthService authService = AuthService();
|
||||
AuthUser? user = await authService.userFromToken(token);
|
||||
//////////////print("---user is ${user} ---");
|
||||
if (user != null) {
|
||||
setState(() {
|
||||
_isLoginProgress = false;
|
||||
ref.read(authUserStateProvider.notifier).addLogin(user);
|
||||
});
|
||||
} else {}
|
||||
}
|
||||
|
||||
void checkUser() async {
|
||||
String? token =
|
||||
await ref.read(authUserStateProvider.notifier).tokenFromDevice();
|
||||
//////////////print("--- red token is ${token} ---");
|
||||
await _submitToken(token!);
|
||||
final id = ref.read(authUserStateProvider).id;
|
||||
if (id != null) {
|
||||
if (context.mounted) {
|
||||
Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (ctx) => const HomePage()));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (ref.read(authUserStateProvider).id != null) {
|
||||
Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (ctx) => const HomePage()));
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: true,
|
||||
body: KeyboardDismisser(
|
||||
gestures: const [
|
||||
GestureType.onTap,
|
||||
//GestureType.onVerticalDragDown
|
||||
],
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
buildAuthCard(),
|
||||
buildLogo(),
|
||||
],
|
||||
),
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Positioned buildLogo() {
|
||||
return Positioned(
|
||||
top: -170,
|
||||
left: MediaQuery.of(context).size.width / 2 - 100,
|
||||
child: Center(
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
width: 200,
|
||||
height: 200,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
image: DecorationImage(
|
||||
image: AssetImage('assets/images/appicon.png'),
|
||||
fit: BoxFit.fill),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildAuthCard() {
|
||||
return Card(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Form(
|
||||
key: _formkey,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 40, bottom: 10, left: 12, right: 12),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: CFormTextField(
|
||||
cFocus: focusEmail,
|
||||
cController: _emailTextEditingController),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: CPasswordTextField(
|
||||
cController: _passwordTextEditingController,
|
||||
cFocusNode: focusPwd,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
buildControlls(),
|
||||
// SizedBox(height: MediaQuery.of(context).viewInsets.bottom,)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildControlls() {
|
||||
if (_isLoginProgress) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
final usr = ref.read(authUserStateProvider);
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 12.0),
|
||||
child: ElevatedButton(
|
||||
onPressed: _submit,
|
||||
child: Text(_authMode == "login" ? "Submit" : "Register",
|
||||
style: GoogleFonts.lato(
|
||||
color: Theme.of(context).colorScheme.secondary))),
|
||||
)
|
||||
],
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
if (_authMode == 'login') {
|
||||
_authMode = 'register';
|
||||
} else {
|
||||
_authMode = 'login';
|
||||
}
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
_authMode == "login"
|
||||
? "${usr.id} Dont have account, please Register"
|
||||
: "Already Registered, Login",
|
||||
style: GoogleFonts.lato(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
decoration: TextDecoration.underline,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
119
lib/nrog/pages/home_page.dart
Normal file
119
lib/nrog/pages/home_page.dart
Normal file
@ -0,0 +1,119 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:flutter_map_location_marker/flutter_map_location_marker.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:gifunavi/provider/map_state_provider.dart';
|
||||
import 'package:gifunavi/widgets/base_layer_widget.dart';
|
||||
|
||||
class HomePage extends ConsumerStatefulWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<HomePage> createState() => _HomePageState();
|
||||
}
|
||||
|
||||
class _HomePageState extends ConsumerState<HomePage> {
|
||||
StreamSubscription? subscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final mapStateInstance = ref.watch(mapStateNotifierProvider);
|
||||
return Scaffold(
|
||||
//drawer: DrawerPage(),
|
||||
appBar: AppBar(
|
||||
title: const Text("Rogaining"),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
//Get.toNamed(AppPages.HISTORY);
|
||||
},
|
||||
icon: const Icon(Icons.history)),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
// final tk = indexController.currentUser[0]["token"];
|
||||
// if (tk != null) {
|
||||
// destinationController.fixMapBound(tk);
|
||||
// }
|
||||
},
|
||||
icon: const Icon(Icons.refresh)),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
//Get.toNamed(AppPages.SEARCH);
|
||||
},
|
||||
child: Container(
|
||||
height: 32,
|
||||
width: 75,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue,
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
),
|
||||
child: const Center(
|
||||
child: Icon(Icons.search),
|
||||
),
|
||||
),
|
||||
),
|
||||
//CatWidget(indexController: indexController,),
|
||||
],
|
||||
),
|
||||
body: Center(
|
||||
child: FlutterMap(
|
||||
mapController: mapStateInstance.mapController,
|
||||
options: MapOptions(
|
||||
maxZoom: 18.4,
|
||||
onMapReady: () {
|
||||
// indexController.is_mapController_loaded.value = true;
|
||||
subscription = mapStateInstance.mapController?.mapEventStream
|
||||
.listen((MapEvent mapEvent) {
|
||||
if (mapEvent is MapEventMoveStart) {
|
||||
//print(DateTime.now().toString() + ' [MapEventMoveStart] START');
|
||||
// do something
|
||||
}
|
||||
// if (mapEvent is MapEventMoveEnd &&
|
||||
// indexController.currentUser.isEmpty) {
|
||||
//print(DateTime.now().toString() + ' [MapEventMoveStart] END');
|
||||
// indexController.loadLocationsBound();
|
||||
//indexController.rogMapController!.move(c.center, c.zoom);
|
||||
// }
|
||||
});
|
||||
},
|
||||
center: const LatLng(37.15319600454702, 139.58765950528198),
|
||||
//bounds:
|
||||
zoom: 18,
|
||||
interactiveFlags: InteractiveFlag.pinchZoom | InteractiveFlag.drag,
|
||||
|
||||
onPositionChanged: (MapPosition pos, isvalue) {
|
||||
//indexController.currentBound = [pos.bounds!];
|
||||
},
|
||||
// onTap: (_, __) => popupController
|
||||
// .hideAllPopups(), // Hide popup when the map is tapped.
|
||||
),
|
||||
children: [
|
||||
const BaseLayer(),
|
||||
CurrentLocationLayer(
|
||||
followOnLocationUpdate: FollowOnLocationUpdate.once,
|
||||
turnOnHeadingUpdate: TurnOnHeadingUpdate.never,
|
||||
style: const LocationMarkerStyle(
|
||||
marker: DefaultLocationMarker(
|
||||
child: Icon(
|
||||
Icons.navigation,
|
||||
color: Colors.yellowAccent,
|
||||
),
|
||||
),
|
||||
markerSize: const Size(27, 27),
|
||||
markerDirection: MarkerDirection.heading,
|
||||
),
|
||||
),
|
||||
const MarkerLayer(markers: [])
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
126
lib/nrog/pages/permission_page.dart
Normal file
126
lib/nrog/pages/permission_page.dart
Normal file
@ -0,0 +1,126 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:gifunavi/nrog/pages/auth_page.dart';
|
||||
|
||||
class PermissionPage extends StatefulWidget {
|
||||
const PermissionPage({super.key});
|
||||
|
||||
@override
|
||||
State<PermissionPage> createState() => _PermissionPageState();
|
||||
}
|
||||
|
||||
class _PermissionPageState extends State<PermissionPage> {
|
||||
bool hasNavigated = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_checkPermissionStatus();
|
||||
});
|
||||
}
|
||||
|
||||
_checkPermissionStatus() async {
|
||||
PermissionStatus status = await Permission.location.status;
|
||||
|
||||
if (status.isGranted == false) {
|
||||
if (context.mounted) {
|
||||
showAlert(context);
|
||||
}
|
||||
} else if (status.isPermanentlyDenied) {
|
||||
await requestPermission();
|
||||
} else {
|
||||
if (mounted) {
|
||||
Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (_) => const AuthPage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
body: Text(""),
|
||||
);
|
||||
}
|
||||
|
||||
void showAlert(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) => AlertDialog(
|
||||
title: const Text('ロケーション許可'),
|
||||
content: const SingleChildScrollView(
|
||||
child: ListBody(
|
||||
children: <Widget>[
|
||||
Text('このアプリでは、位置情報の収集を行います。'),
|
||||
Text(
|
||||
'岐阜ナビアプリではチェックポイントの自動チェックインの機能を可能にするために、現在地のデータが収集されます。アプリを閉じている時や、使用していないときにも収集されます。位置情報は、個人を特定できない統計的な情報として、ユーザーの個人情報とは一切結びつかない形で送信されます。お知らせの配信、位置情報の利用を許可しない場合は、この後表示されるダイアログで「許可しない」を選択してください。'),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
ElevatedButton(
|
||||
child: const Text('OK'),
|
||||
onPressed: () {
|
||||
requestPermission();
|
||||
},
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> requestPermission() async {
|
||||
PermissionStatus permission = await Permission.location.status;
|
||||
if (permission == PermissionStatus.permanentlyDenied) {
|
||||
showPermanentAlert();
|
||||
} else {
|
||||
PermissionStatus newPermission = await Permission.location.request();
|
||||
if (newPermission != PermissionStatus.granted) {
|
||||
exit(0);
|
||||
} else {
|
||||
if (context.mounted) {
|
||||
Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (_) => const AuthPage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void showPermanentAlert() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) => AlertDialog(
|
||||
title: const Text('無効'),
|
||||
content: const SingleChildScrollView(
|
||||
child: ListBody(
|
||||
children: <Widget>[
|
||||
Text('位置情報が無効になっています'),
|
||||
Text(
|
||||
'このアプリケーションへの位置情報アクセスが無効になっています。続行するには設定>プライバシーとセキュリティ>位置情報サービス>岐阜ナビ で有効にしてください。'),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
ElevatedButton(
|
||||
child: const Text('OK'),
|
||||
onPressed: () async {
|
||||
await openAppSettings().then(
|
||||
(value) async {
|
||||
if (value) {
|
||||
if (await Permission
|
||||
.location.status.isPermanentlyDenied ==
|
||||
true &&
|
||||
await Permission.location.status.isGranted ==
|
||||
false) {
|
||||
requestPermission(); /* opens app settings until permission is granted */
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
22
lib/pages/WebView/WebView_page.dart
Normal file
22
lib/pages/WebView/WebView_page.dart
Normal file
@ -0,0 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
class WebViewPage extends StatelessWidget {
|
||||
final String url;
|
||||
|
||||
const WebViewPage({super.key, required this.url});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('WebView'),
|
||||
),
|
||||
body: WebViewWidget(
|
||||
controller: WebViewController()
|
||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||
..loadRequest(Uri.parse(url)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
185
lib/pages/camera/custom_camera_view.dart
Normal file
185
lib/pages/camera/custom_camera_view.dart
Normal file
@ -0,0 +1,185 @@
|
||||
import 'dart:io';
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:gifunavi/model/destination.dart';
|
||||
|
||||
class CustomCameraView extends StatefulWidget {
|
||||
final Function(String) onImageCaptured;
|
||||
final Destination? destination;
|
||||
|
||||
const CustomCameraView({super.key, required this.onImageCaptured, required this.destination});
|
||||
|
||||
@override
|
||||
_CustomCameraViewState createState() => _CustomCameraViewState();
|
||||
}
|
||||
|
||||
class _CustomCameraViewState extends State<CustomCameraView> {
|
||||
CameraController? _controller;
|
||||
late List<CameraDescription> _cameras;
|
||||
int _selectedCameraIndex = 0;
|
||||
double _currentScale = 1.0;
|
||||
FlashMode _currentFlashMode = FlashMode.off;
|
||||
Destination? destination;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initializeCamera();
|
||||
destination = widget.destination;
|
||||
}
|
||||
|
||||
Future<void> _initializeCamera() async {
|
||||
_cameras = await availableCameras();
|
||||
_controller = CameraController(_cameras[_selectedCameraIndex], ResolutionPreset.medium);
|
||||
await _controller!.initialize();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _toggleCameraLens() async {
|
||||
final newIndex = (_selectedCameraIndex + 1) % _cameras.length;
|
||||
await _controller!.dispose();
|
||||
|
||||
setState(() {
|
||||
_controller = null;
|
||||
_selectedCameraIndex = newIndex;
|
||||
});
|
||||
|
||||
_controller = CameraController(_cameras[_selectedCameraIndex], ResolutionPreset.medium);
|
||||
await _controller!.initialize();
|
||||
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void _toggleFlashMode() {
|
||||
setState(() {
|
||||
_currentFlashMode = (_currentFlashMode == FlashMode.off) ? FlashMode.torch : FlashMode.off;
|
||||
});
|
||||
_controller!.setFlashMode(_currentFlashMode);
|
||||
}
|
||||
|
||||
void _zoomIn() {
|
||||
setState(() {
|
||||
_currentScale += 0.1;
|
||||
if (_currentScale > 5.0) _currentScale = 5.0;
|
||||
});
|
||||
_controller!.setZoomLevel(_currentScale);
|
||||
}
|
||||
|
||||
void _zoomOut() {
|
||||
setState(() {
|
||||
_currentScale -= 0.1;
|
||||
if (_currentScale < 1.0) _currentScale = 1.0;
|
||||
});
|
||||
_controller!.setZoomLevel(_currentScale);
|
||||
}
|
||||
|
||||
void _captureImage() async {
|
||||
if (_controller!.value.isInitialized) {
|
||||
final Directory appDirectory = await getApplicationDocumentsDirectory();
|
||||
final String imagePath = path.join(appDirectory.path, '${DateTime.now()}.jpg');
|
||||
|
||||
final XFile imageFile = await _controller!.takePicture();
|
||||
await imageFile.saveTo(imagePath);
|
||||
|
||||
widget.onImageCaptured(imagePath);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_controller == null || !_controller!.value.isInitialized) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 60.0), // 上部に60ピクセルのパディングを追加
|
||||
child: CameraPreview(_controller!),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 120.0,
|
||||
left: 16.0,
|
||||
right: 16.0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
destination?.tags ?? '',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 16.0,
|
||||
left: 16.0,
|
||||
right: 16.0,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: _toggleFlashMode,
|
||||
icon: Icon(
|
||||
(_currentFlashMode == FlashMode.off) ? Icons.flash_off : Icons.flash_on,
|
||||
color: Colors.white,
|
||||
),
|
||||
iconSize: 32,
|
||||
color: Colors.orange,
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: _captureImage,
|
||||
child: Container(
|
||||
height: 80,
|
||||
width: 80,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.white,
|
||||
border: Border.all(color: Colors.red, width: 4),
|
||||
),
|
||||
child: const Icon(Icons.camera_alt, color: Colors.red, size: 40),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: _toggleCameraLens,
|
||||
icon: const Icon(Icons.flip_camera_ios, color: Colors.white),
|
||||
iconSize: 32,
|
||||
color: Colors.blue,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 16.0,
|
||||
right: 16.0,
|
||||
child: Column(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: _zoomIn,
|
||||
icon: const Icon(Icons.zoom_in, color: Colors.white),
|
||||
iconSize: 32,
|
||||
color: Colors.green,
|
||||
),
|
||||
IconButton(
|
||||
onPressed: _zoomOut,
|
||||
icon: const Icon(Icons.zoom_out, color: Colors.white),
|
||||
iconSize: 32,
|
||||
color: Colors.green,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CategoryPage extends StatelessWidget {
|
||||
const CategoryPage({Key? key}) : super(key: key);
|
||||
const CategoryPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/index/index_controller.dart';
|
||||
import 'package:gifunavi/pages/index/index_controller.dart';
|
||||
import 'package:gifunavi/widgets/debug_widget.dart';
|
||||
|
||||
class ChangePasswordPage extends StatelessWidget {
|
||||
ChangePasswordPage({Key? key}) : super(key: key);
|
||||
ChangePasswordPage({super.key});
|
||||
|
||||
LogManager logManager = LogManager();
|
||||
|
||||
IndexController indexController = Get.find<IndexController>();
|
||||
|
||||
@ -13,150 +16,174 @@ class ChangePasswordPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
resizeToAvoidBottomInset: false,
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
|
||||
backgroundColor: Colors.white,
|
||||
leading:
|
||||
IconButton( onPressed: (){
|
||||
Navigator.pop(context);
|
||||
},icon:const Icon(Icons.arrow_back_ios,size: 20,color: Colors.black,)),
|
||||
leading: IconButton(
|
||||
onPressed: () {
|
||||
logManager.addOperationLog('User clicked cancel button on the drawer');
|
||||
Navigator.pop(context);
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.arrow_back_ios,
|
||||
size: 20,
|
||||
color: Colors.black,
|
||||
)),
|
||||
),
|
||||
body:
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Container(
|
||||
child: Text("change_password".tr, style: const TextStyle(fontSize: 24.0),),
|
||||
),
|
||||
const SizedBox(height: 30,),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 40
|
||||
),
|
||||
child: Column(
|
||||
body: SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
makeInput(label: "old_password".tr, controller: oldPasswordController),
|
||||
makeInput(label: "new_password".tr, controller: newPasswordController, obsureText: true),
|
||||
Text(
|
||||
"change_password".tr,
|
||||
style: const TextStyle(fontSize: 24.0),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 30,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(top: 3,left: 3),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
),
|
||||
child: Obx((() =>
|
||||
indexController.is_loading == true ? MaterialButton(
|
||||
minWidth: double.infinity,
|
||||
height:60,
|
||||
onPressed: (){
|
||||
|
||||
},
|
||||
color: Colors.grey[400],
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(40)
|
||||
),
|
||||
child: const CircularProgressIndicator(),
|
||||
) :
|
||||
Column(
|
||||
children: [
|
||||
MaterialButton(
|
||||
minWidth: double.infinity,
|
||||
height:60,
|
||||
onPressed: (){
|
||||
if(oldPasswordController.text.isEmpty || newPasswordController.text.isEmpty){
|
||||
Get.snackbar(
|
||||
"no_values".tr,
|
||||
"values_required".tr,
|
||||
icon: const Icon(Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
|
||||
snackPosition: SnackPosition.TOP,
|
||||
duration: const Duration(milliseconds: 800),
|
||||
backgroundColor: Colors.yellow,
|
||||
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
||||
);
|
||||
return;
|
||||
}
|
||||
indexController.is_loading.value = true;
|
||||
indexController.changePassword(oldPasswordController.text, newPasswordController.text, context);
|
||||
//indexController.login(oldPasswordController.text, newPasswordController.text, context);
|
||||
},
|
||||
color: Colors.indigoAccent[400],
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(40)
|
||||
),
|
||||
child: const Text("ログイン",style: TextStyle(
|
||||
fontWeight: FontWeight.w600,fontSize: 16,color: Colors.white70
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10.0,),
|
||||
|
||||
],
|
||||
)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
child: Column(
|
||||
children: [
|
||||
makeInput(
|
||||
label: "old_password".tr,
|
||||
controller: oldPasswordController),
|
||||
makeInput(
|
||||
label: "new_password".tr,
|
||||
controller: newPasswordController,
|
||||
obsureText: true),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
const SizedBox(height: 20,),
|
||||
const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
|
||||
],
|
||||
)
|
||||
],
|
||||
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
);
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(top: 3, left: 3),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
),
|
||||
child: Obx(
|
||||
(() => indexController.isLoading.value == true
|
||||
? MaterialButton(
|
||||
minWidth: double.infinity,
|
||||
height: 60,
|
||||
onPressed: () {},
|
||||
color: Colors.grey[400],
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(40)),
|
||||
child: const CircularProgressIndicator(),
|
||||
)
|
||||
: Column(
|
||||
children: [
|
||||
MaterialButton(
|
||||
minWidth: double.infinity,
|
||||
height: 60,
|
||||
onPressed: () {
|
||||
if (oldPasswordController
|
||||
.text.isEmpty ||
|
||||
newPasswordController
|
||||
.text.isEmpty) {
|
||||
logManager.addOperationLog('User tried to login with blank old password ${oldPasswordController.text} or new password ${newPasswordController.text}.');
|
||||
Get.snackbar(
|
||||
"no_values".tr,
|
||||
"values_required".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(
|
||||
milliseconds: 800),
|
||||
//backgroundColor: Colors.yellow,
|
||||
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
||||
);
|
||||
return;
|
||||
}
|
||||
indexController.isLoading.value = true;
|
||||
indexController.changePassword(
|
||||
oldPasswordController.text,
|
||||
newPasswordController.text,
|
||||
context);
|
||||
//indexController.login(oldPasswordController.text, newPasswordController.text, context);
|
||||
},
|
||||
color: Colors.indigoAccent[400],
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(40)),
|
||||
child: Text(
|
||||
"login".tr,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
color: Colors.white70),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10.0,
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
)),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [],
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Widget makeInput({label, required TextEditingController controller, obsureText = false}){
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label,style:const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Colors.black87
|
||||
),),
|
||||
const SizedBox(height: 5,),
|
||||
TextField(
|
||||
controller: controller,
|
||||
obscureText: obsureText,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 0,horizontal: 10),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: (Colors.grey[400])!,
|
||||
Widget makeInput(
|
||||
{label, required TextEditingController controller, obsureText = false}) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 15, fontWeight: FontWeight.w400, color: Colors.black87),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
TextField(
|
||||
controller: controller,
|
||||
obscureText: obsureText,
|
||||
decoration: InputDecoration(
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(vertical: 0, horizontal: 10),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: (Colors.grey[400])!,
|
||||
),
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: (Colors.grey[400])!),
|
||||
),
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: (Colors.grey[400])!
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30.0,)
|
||||
],
|
||||
);
|
||||
const SizedBox(
|
||||
height: 30.0,
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CityPage extends StatelessWidget {
|
||||
const CityPage({Key? key}) : super(key: key);
|
||||
const CityPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
9
lib/pages/debug/debug_binding.dart
Normal file
9
lib/pages/debug/debug_binding.dart
Normal file
@ -0,0 +1,9 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gifunavi/pages/debug/debug_controller.dart';
|
||||
|
||||
class DebugBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<DebugController>(() => DebugController());
|
||||
}
|
||||
}
|
||||
47
lib/pages/debug/debug_controller.dart
Normal file
47
lib/pages/debug/debug_controller.dart
Normal file
@ -0,0 +1,47 @@
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gifunavi/utils/location_controller.dart';
|
||||
|
||||
class DebugController extends GetxController {
|
||||
final LocationController locationController = Get.find<LocationController>();
|
||||
final gpsSignalStrength = 'high'.obs;
|
||||
final currentPosition = Rx<Position?>(null);
|
||||
final isSimulationMode = false.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
// 位置情報の更新を監視
|
||||
locationController.locationMarkerPositionStream.listen((position) {
|
||||
if (position != null) {
|
||||
currentPosition.value = Position(
|
||||
latitude: position.latitude,
|
||||
longitude: position.longitude,
|
||||
accuracy: position.accuracy,
|
||||
altitudeAccuracy: 30,
|
||||
headingAccuracy: 30,
|
||||
heading: 0,
|
||||
altitude: 0,
|
||||
speed: 0,
|
||||
speedAccuracy: 0,
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void setGpsSignalStrength(String value) {
|
||||
gpsSignalStrength.value = value;
|
||||
locationController.setSimulatedSignalStrength(value);
|
||||
}
|
||||
|
||||
void toggleSimulationMode() {
|
||||
isSimulationMode.value = !isSimulationMode.value;
|
||||
locationController.setSimulationMode(isSimulationMode.value);
|
||||
if (!isSimulationMode.value) {
|
||||
// 標準モードに切り替えた場合は、シミュレートされた信号強度をリセット
|
||||
locationController.setSimulatedSignalStrength('low');
|
||||
gpsSignalStrength.value = 'low';
|
||||
}
|
||||
}
|
||||
}
|
||||
65
lib/pages/debug/debug_page.dart
Normal file
65
lib/pages/debug/debug_page.dart
Normal file
@ -0,0 +1,65 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gifunavi/pages/debug/debug_controller.dart';
|
||||
|
||||
class DebugPage extends GetView<DebugController> {
|
||||
const DebugPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('デバッグモード'),
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text('GPS信号強度'),
|
||||
const SizedBox(height: 20),
|
||||
Obx(
|
||||
() => DropdownButton<String>(
|
||||
value: controller.gpsSignalStrength.value,
|
||||
onChanged: (value) {
|
||||
controller.setGpsSignalStrength(value!);
|
||||
},
|
||||
items: ['high', 'medium', 'low']
|
||||
.map<DropdownMenuItem<String>>((String value) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value,
|
||||
child: Text(value),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const Text('現在のGPS座標'),
|
||||
const SizedBox(height: 10),
|
||||
Obx(
|
||||
() => Text(
|
||||
'緯度: ${controller.currentPosition.value?.latitude ?? '-'}, 経度: ${controller.currentPosition.value?.longitude ?? '-'}',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const Text('現在のGPS精度'),
|
||||
const SizedBox(height: 10),
|
||||
Obx(
|
||||
() => Text(
|
||||
'精度: ${controller.currentPosition.value?.accuracy.toStringAsFixed(2) ?? '-'} m',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Obx(
|
||||
() => ElevatedButton(
|
||||
onPressed: controller.toggleSimulationMode,
|
||||
child: Text(controller.isSimulationMode.value
|
||||
? 'シミュレーションモード'
|
||||
: '標準モード'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,11 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/destination/destination_controller.dart';
|
||||
import 'package:gifunavi/main.dart';
|
||||
import 'package:gifunavi/pages/destination/destination_controller.dart';
|
||||
|
||||
class DestinationBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.put<DestinationController>(DestinationController());
|
||||
restoreGame();
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,212 +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 DestnationPage extends StatelessWidget {
|
||||
DestnationPage({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);
|
||||
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) {
|
||||
final ColorScheme colorScheme = Theme.of(context).colorScheme;
|
||||
final Color oddItemColor = colorScheme.primary.withOpacity(0.05);
|
||||
final Color evenItemColor = colorScheme.primary.withOpacity(0.15);
|
||||
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 == 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 == 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.desination_mode == 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.is_in_rog == true ?
|
||||
Image.asset("assets/images/basic-walking.gif",height: 10.0,)
|
||||
:
|
||||
destinationController.is_at_goal == 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.is_gps_selected.value = !destinationController.is_gps_selected.value;
|
||||
if(destinationController.is_gps_selected.value){
|
||||
destinationController.chekcs = 0;
|
||||
destinationController.skip_gps = false;
|
||||
//destinationController.resetRogaining();
|
||||
}
|
||||
},
|
||||
isSelected: [destinationController.is_gps_selected.value],
|
||||
children: const <Widget>[
|
||||
Icon(Icons.explore, size: 35.0,
|
||||
)],
|
||||
),
|
||||
),
|
||||
// IconButton(onPressed: (){
|
||||
// showCurrentPosition();
|
||||
// },
|
||||
// icon: Icon(Icons.location_on_outlined))
|
||||
],
|
||||
),
|
||||
body: Obx(() =>
|
||||
indexController.desination_mode.value == 0 ?
|
||||
DestinationWidget():
|
||||
DestinationMapPage()
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,429 +1,207 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map/plugin_api.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:flutter_map_location_marker/flutter_map_location_marker.dart';
|
||||
import 'package:flutter_map_marker_popup/flutter_map_marker_popup.dart';
|
||||
//import 'package:flutter_map_marker_popup/flutter_map_marker_popup.dart';
|
||||
import 'package:flutter_polyline_points/flutter_polyline_points.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:rogapp/model/destination.dart';
|
||||
import 'package:rogapp/pages/destination/destination_controller.dart';
|
||||
import 'package:rogapp/pages/index/index_controller.dart';
|
||||
import 'package:rogapp/utils/text_util.dart';
|
||||
import 'package:rogapp/widgets/base_layer_widget.dart';
|
||||
import 'package:rogapp/widgets/bottom_sheet_new.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/text_util.dart';
|
||||
import 'package:gifunavi/widgets/base_layer_widget.dart';
|
||||
import 'package:gifunavi/widgets/bottom_sheet_new.dart';
|
||||
//import 'package:gifunavi/widgets/bottom_sheets/bottom_sheet_start.dart';
|
||||
//import 'package:gifunavi/widgets/bottom_sheets/bottom_sheet_goal.dart';
|
||||
//import 'package:gifunavi/widgets/bottom_sheets/bottom_sheet_normal_point.dart';
|
||||
|
||||
// FlutterMapウィジェットを使用して、地図を表示します。
|
||||
// IndexControllerから目的地のリストを取得し、マーカーとしてマップ上に表示します。
|
||||
// マーカーがタップされると、BottomSheetウィジェットを表示します。
|
||||
// 現在地の表示、ルートの表示、ベースレイヤーの表示などの機能を提供します。
|
||||
// 主なロジック:
|
||||
// FlutterMapウィジェットを使用して、地図を表示します。
|
||||
// IndexControllerから目的地のリストを取得し、MarkerLayerを使用してマーカーを表示します。
|
||||
// getMarkerShapeメソッドを使用して、マーカーの見た目をカスタマイズします。目的地の種類に応じて、異なるマーカーを表示します。
|
||||
// マーカーがタップされると、festuretoDestinationメソッドを使用してGeoJSONFeatureをDestinationオブジェクトに変換し、showModalBottomSheetを使用してBottomSheetウィジェットを表示します。
|
||||
// CurrentLocationLayerを使用して、現在地をマップ上に表示します。
|
||||
// PolylineLayerを使用して、ルートをマップ上に表示します。getPointsメソッドを使用して、ルートの座標を取得します。
|
||||
// BaseLayerを使用して、マップのベースレイヤーを表示します。
|
||||
//
|
||||
class DestinationMapPage extends StatelessWidget {
|
||||
DestinationMapPage({Key? key}) : super(key: key);
|
||||
DestinationMapPage({super.key});
|
||||
|
||||
final IndexController indexController = Get.find<IndexController>();
|
||||
|
||||
final DestinationController destinationController = Get.find<DestinationController>();
|
||||
final DestinationController destinationController =
|
||||
Get.find<DestinationController>();
|
||||
StreamSubscription? subscription;
|
||||
final PopupController _popupLayerController = PopupController();
|
||||
//final PopupController _popupLayerController = PopupController();
|
||||
|
||||
List<LatLng>? getPoints(){
|
||||
print("##### --- route point ${indexController.routePoints.length}");
|
||||
List<LatLng> pts = [];
|
||||
for(PointLatLng p in indexController.routePoints){
|
||||
LatLng l = LatLng(p.latitude, p.longitude);
|
||||
pts.add(l);
|
||||
}
|
||||
return pts;
|
||||
List<LatLng>? getPoints() {
|
||||
//print("##### --- route point ${indexController.routePoints.length}");
|
||||
List<LatLng> pts = [];
|
||||
for (PointLatLng p in indexController.routePoints) {
|
||||
LatLng l = LatLng(p.latitude, p.longitude);
|
||||
pts.add(l);
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
|
||||
List<Marker>? getMarkers() {
|
||||
List<Marker> pts = [];
|
||||
int index = -1;
|
||||
for (int i = 0; i < destinationController.destinations.length; i++) {
|
||||
Destination d = destinationController.destinations[i];
|
||||
print("^^^^ $d ^^^^");
|
||||
Marker m = Marker(
|
||||
// 要検討:マーカーのタップイベントを処理する際に、エラーハンドリングが不十分です。例外が発生した場合の処理を追加することをお勧めします。
|
||||
//
|
||||
List<Marker>? getMarkers() {
|
||||
List<Marker> pts = [];
|
||||
//int index = -1;
|
||||
for (int i = 0; i < destinationController.destinations.length; i++) {
|
||||
Destination d = destinationController.destinations[i];
|
||||
//print("^^^^ $d ^^^^");
|
||||
Marker m = Marker(
|
||||
point: LatLng(d.lat!, d.lon!),
|
||||
anchorPos: AnchorPos.align(AnchorAlign.center),
|
||||
builder:(cts){
|
||||
alignment: Alignment.center,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
//print("-- Destination is --- ${d.name} ------");
|
||||
if (indexController.currentDestinationFeature.isNotEmpty) {
|
||||
indexController.currentDestinationFeature.clear();
|
||||
}
|
||||
indexController.currentDestinationFeature.add(d);
|
||||
//indexController.getAction();
|
||||
|
||||
return InkWell(
|
||||
onTap: (){
|
||||
print("-- Destination is --- ${d.name} ------");
|
||||
if(indexController.currentDestinationFeature.isNotEmpty) {
|
||||
indexController.currentDestinationFeature.clear();
|
||||
}
|
||||
indexController.currentDestinationFeature.add(d);
|
||||
//indexController.getAction();
|
||||
|
||||
showModalBottomSheet(context: Get.context!, isScrollControlled: true,
|
||||
builder:((context) => BottomSheetNew())
|
||||
).whenComplete((){
|
||||
print("---- set skip gps to false -----");
|
||||
destinationController.skip_gps = false;
|
||||
});
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Container(
|
||||
width:20,
|
||||
height:20,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: Colors.white,
|
||||
width: d.checkin_radious != null ? d.checkin_radious! : 1,
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
(i + 1).toString(),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
Widget bottomSheet = BottomSheetNew(destination: d);
|
||||
/*
|
||||
if (d.cp == -1 || d.cp == 0) {
|
||||
bottomSheet = BottomSheetStart(destination: d);
|
||||
} else if (d.cp == -2 || d.cp == 0) {
|
||||
bottomSheet = BottomSheetGoal(destination: d);
|
||||
} else {
|
||||
bottomSheet = BottomSheetNormalPoint(destination: d);
|
||||
}
|
||||
*/
|
||||
|
||||
showModalBottomSheet(
|
||||
context: Get.context!,
|
||||
isScrollControlled: true,
|
||||
constraints:
|
||||
BoxConstraints.loose(Size(Get.width, Get.height * 0.85)),
|
||||
builder: ((context) => bottomSheet ),
|
||||
|
||||
).whenComplete(() {
|
||||
//print("---- set skip gps to false -----");
|
||||
destinationController.skipGps = false;
|
||||
});
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: Colors.white,
|
||||
width: d.checkin_radious != null ? d.checkin_radious! : 1,
|
||||
),
|
||||
),
|
||||
Container( color: Colors.yellow, child: Text(TextUtils.getDisplayText(d), style: const TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold, overflow: TextOverflow.visible),)),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
pts.add(m);
|
||||
}
|
||||
return pts;
|
||||
child: Center(
|
||||
child: Text(
|
||||
(i + 1).toString(),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
color: Colors.yellow,
|
||||
child: Text(
|
||||
TextUtils.getDisplayText(d),
|
||||
style: const TextStyle(
|
||||
fontSize: 15.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
overflow: TextOverflow.visible),
|
||||
)),
|
||||
],
|
||||
),
|
||||
));
|
||||
|
||||
pts.add(m);
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Obx((() =>
|
||||
Stack(
|
||||
children: [
|
||||
// indexController.is_rog_mapcontroller_loaded.value == false ?
|
||||
// Center(child: CircularProgressIndicator())
|
||||
// :
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.only(left:8.0),
|
||||
// child: BreadCrumbWidget(mapController:indexController.rogMapController),
|
||||
// ),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top:0.0),
|
||||
//child: TravelMap(),
|
||||
child:
|
||||
TravelMap(),
|
||||
),
|
||||
],
|
||||
)
|
||||
));
|
||||
return Obx((() => Stack(
|
||||
children: [
|
||||
// indexController.is_rog_mapcontroller_loaded.value == false ?
|
||||
// Center(child: CircularProgressIndicator())
|
||||
// :
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.only(left:8.0),
|
||||
// child: BreadCrumbWidget(mapController:indexController.rogMapController),
|
||||
// ),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 0.0),
|
||||
//child: TravelMap(),
|
||||
child: travelMap(),
|
||||
),
|
||||
],
|
||||
)));
|
||||
}
|
||||
|
||||
FlutterMap TravelMap() {
|
||||
// 要検討:MapOptionsのboundsプロパティにハードコードされた座標が使用されています。これを動的に設定できるようにすることを検討してください。
|
||||
//
|
||||
FlutterMap travelMap() {
|
||||
return FlutterMap(
|
||||
mapController: indexController.rogMapController,
|
||||
options: MapOptions(
|
||||
onMapReady: (){
|
||||
indexController.is_rog_mapcontroller_loaded.value = true;
|
||||
subscription = indexController.rogMapController.mapEventStream.listen((MapEvent mapEvent) {
|
||||
if (mapEvent is MapEventMoveStart) {
|
||||
}
|
||||
options: MapOptions(
|
||||
onMapReady: () {
|
||||
indexController.isRogMapcontrollerLoaded.value = true;
|
||||
subscription = indexController.rogMapController.mapEventStream
|
||||
.listen((MapEvent mapEvent) {
|
||||
if (mapEvent is MapEventMoveStart) {}
|
||||
if (mapEvent is MapEventMoveEnd) {
|
||||
//destinationController.is_gps_selected.value = true;
|
||||
//indexController.mapController!.move(c.center, c.zoom);
|
||||
LatLngBounds bounds = indexController.rogMapController.bounds!;
|
||||
indexController.currentBound.clear();
|
||||
indexController.currentBound.add(bounds);
|
||||
if(indexController.currentUser.isEmpty){
|
||||
indexController.loadLocationsBound();
|
||||
if (indexController.currentUser.isEmpty) {
|
||||
indexController.loadLocationsBound(indexController.currentUser[0]["user"]["event_code"]);
|
||||
}
|
||||
}
|
||||
});
|
||||
} ,
|
||||
bounds: indexController.currentBound.isNotEmpty ? indexController.currentBound[0]: LatLngBounds.fromPoints([LatLng(35.03999881162295, 136.40587119778962), LatLng(36.642756778706904, 137.95226720406063)]),
|
||||
zoom: 1,
|
||||
maxZoom: 42,
|
||||
interactiveFlags: InteractiveFlag.pinchZoom | InteractiveFlag.drag,
|
||||
),
|
||||
children: [
|
||||
const BaseLayer(),
|
||||
Obx(() =>
|
||||
indexController.routePointLenght > 0 ?
|
||||
PolylineLayer(
|
||||
polylines: [
|
||||
Polyline(
|
||||
points: getPoints()!,
|
||||
strokeWidth: 6.0,
|
||||
color: Colors.indigo
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
:
|
||||
Container(),
|
||||
},
|
||||
bounds: indexController.currentBound.isNotEmpty
|
||||
? indexController.currentBound[0]
|
||||
: LatLngBounds.fromPoints([
|
||||
const LatLng(35.03999881162295, 136.40587119778962),
|
||||
const LatLng(36.642756778706904, 137.95226720406063)
|
||||
]),
|
||||
initialZoom: 1,
|
||||
maxZoom: 42,
|
||||
interactiveFlags: InteractiveFlag.pinchZoom | InteractiveFlag.drag,
|
||||
),
|
||||
CurrentLocationLayer(),
|
||||
MarkerLayer(
|
||||
markers: getMarkers()!
|
||||
),
|
||||
],
|
||||
|
||||
);
|
||||
children: [
|
||||
const BaseLayer(),
|
||||
Obx(
|
||||
() => indexController.routePointLenght > 0
|
||||
? PolylineLayer(
|
||||
polylines: [
|
||||
Polyline(
|
||||
points: getPoints()!,
|
||||
strokeWidth: 6.0,
|
||||
color: Colors.indigo),
|
||||
],
|
||||
)
|
||||
: Container(),
|
||||
),
|
||||
CurrentLocationLayer(),
|
||||
MarkerLayer(markers: getMarkers()!),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// class DestinationMapPage extends StatefulWidget {
|
||||
// DestinationMapPage({ Key? key }) : super(key: key);
|
||||
|
||||
|
||||
// @override
|
||||
// State<DestinationMapPage> createState() => _DestinationMapPageState();
|
||||
// }
|
||||
|
||||
//class _DestinationMapPageState extends State<DestinationMapPage> {
|
||||
// final IndexController indexController = Get.find<IndexController>();
|
||||
|
||||
// final DestinationController destinationController = Get.find<DestinationController>();
|
||||
// StreamSubscription? subscription;
|
||||
// final PopupController _popupLayerController = PopupController();
|
||||
|
||||
// List<LatLng>? getPoints(List<PointLatLng> ptts){
|
||||
// //print("##### --- route point ${indexController.routePoints.length}");
|
||||
// List<LatLng> pts = [];
|
||||
// for(PointLatLng p in ptts){
|
||||
// LatLng l = LatLng(p.latitude, p.longitude);
|
||||
// pts.add(l);
|
||||
// }
|
||||
// return pts;
|
||||
// }
|
||||
|
||||
// String getDisplaytext(Destination dp){
|
||||
// String txt = "";
|
||||
// if(dp.cp! > 0){
|
||||
// txt = "${dp.cp}";
|
||||
// if(dp.checkin_point != null && dp.checkin_point! > 0){
|
||||
// txt = txt + "{${dp.checkin_point}}";
|
||||
// }
|
||||
// if(dp.buy_point != null && dp.buy_point! > 0){
|
||||
// txt = txt + "[${dp.buy_point}]";
|
||||
// }
|
||||
// }
|
||||
// return txt;
|
||||
// }
|
||||
|
||||
// List<Marker>? getMarkers() {
|
||||
// List<Marker> pts = [];
|
||||
// int index = -1;
|
||||
// for (int i = 0; i < destinationController.destinations.length; i++) {
|
||||
// Destination d = destinationController.destinations[i];
|
||||
// //for(Destination d in destinationController.destinations){
|
||||
// //print("-----lat ${lat}, ----- lon ${lan}");
|
||||
// Marker m = Marker(
|
||||
// point: LatLng(d.lat!, d.lon!),
|
||||
// anchorPos: AnchorPos.align(AnchorAlign.center),
|
||||
// builder:(cts){
|
||||
|
||||
// return InkWell(
|
||||
// onTap: (){
|
||||
// print("-- Destination is --- ${d.name} ------");
|
||||
// if(d != null){
|
||||
// if(indexController.currentDestinationFeature.length > 0) {
|
||||
// indexController.currentDestinationFeature.clear();
|
||||
// }
|
||||
// indexController.currentDestinationFeature.add(d);
|
||||
// //indexController.getAction();
|
||||
|
||||
// showModalBottomSheet(context: context, isScrollControlled: true,
|
||||
// //builder:((context) => BottomSheetWidget())
|
||||
// builder:((context) => BottomSheetNew())
|
||||
// );
|
||||
// }
|
||||
// },
|
||||
// child: Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// children: [
|
||||
// Container(
|
||||
// width:20,
|
||||
// height:20,
|
||||
// decoration: BoxDecoration(
|
||||
// color: Colors.red,
|
||||
// shape: BoxShape.circle,
|
||||
// border: new Border.all(
|
||||
// color: Colors.white,
|
||||
// width: d.checkin_radious != null ? d.checkin_radious! : 1,
|
||||
// ),
|
||||
// ),
|
||||
// child: new Center(
|
||||
// child: new Text(
|
||||
// (i + 1).toString(),
|
||||
// style: TextStyle(color: Colors.white),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// Container( color: Colors.yellow, child: Text(getDisplaytext(d), style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold, overflow: TextOverflow.visible),)),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
|
||||
// //return Icon(Icons.pin_drop);
|
||||
// // return IconButton(
|
||||
// // onPressed: ()async {
|
||||
// // Destination? fs = await destinationController.getDEstinationForLatLong(d.lat!, d.lon!);
|
||||
// // print("-- Destination is --- ${fs!.name} ------");
|
||||
// // if(fs != null){
|
||||
// // if(indexController.currentDestinationFeature.length > 0) {
|
||||
// // indexController.currentDestinationFeature.clear();
|
||||
// // }
|
||||
// // indexController.currentDestinationFeature.add(fs);
|
||||
// // //indexController.getAction();
|
||||
|
||||
// // showModalBottomSheet(context: context, isScrollControlled: true,
|
||||
// // //builder:((context) => BottomSheetWidget())
|
||||
// // builder:((context) => BottomSheetNew())
|
||||
// // );
|
||||
// // }
|
||||
// // },
|
||||
// // icon: Container(
|
||||
// // width: 60,
|
||||
// // height: 60,
|
||||
// // decoration: BoxDecoration(
|
||||
// // borderRadius: BorderRadius.circular(d.checkin_radious ?? 0),
|
||||
// // color: Colors.transparent,
|
||||
// // border: BoxBorder()
|
||||
// // ),
|
||||
// // child: Icon(Icons.pin_drop)
|
||||
// // )
|
||||
// // );
|
||||
|
||||
// });
|
||||
|
||||
// pts.add(m);
|
||||
// }
|
||||
// return pts;
|
||||
// }
|
||||
|
||||
// @override
|
||||
// void initState() {
|
||||
|
||||
// //indexController.routePoints.clear();
|
||||
// DestinationService.getDestinationLine(destinationController.destinations)?.then((value){
|
||||
// //print("---- loading destination points ------ ${value}");
|
||||
// setState(() {
|
||||
// indexController.routePoints = value;
|
||||
// });
|
||||
// });
|
||||
// super.initState();
|
||||
// }
|
||||
|
||||
// void reload(){
|
||||
// setState(() {
|
||||
|
||||
// });
|
||||
// }
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return Obx((() =>
|
||||
// Stack(
|
||||
// children: [
|
||||
// indexController.is_rog_mapcontroller_loaded.value == false ?
|
||||
// Center(child: CircularProgressIndicator())
|
||||
// :
|
||||
// BreadCrumbWidget(mapController:indexController.rogMapController),
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.only(top:50.0),
|
||||
// //child: TravelMap(),
|
||||
// child:
|
||||
// TravelMap(indexController.routePoints),
|
||||
// ),
|
||||
// // Positioned(
|
||||
// // bottom: 200,
|
||||
// // left: 10,
|
||||
// // child: Container(
|
||||
// // color: Colors.white,
|
||||
// // child: Row(
|
||||
// // children: [
|
||||
// // Text(destinationController.gps[0]),
|
||||
// // Text(destinationController.locationPermission[0])
|
||||
// // ],
|
||||
// // ),
|
||||
// // )
|
||||
// // ),
|
||||
// ],
|
||||
// )
|
||||
// ));
|
||||
// }
|
||||
|
||||
// FlutterMap TravelMap(List<PointLatLng> ptts) {
|
||||
// return FlutterMap(
|
||||
// options: MapOptions(
|
||||
// onMapCreated: (c){
|
||||
// indexController.rogMapController = c;
|
||||
// indexController.rogMapController!.onReady.then((_) {
|
||||
// indexController.is_rog_mapcontroller_loaded.value = true;
|
||||
// subscription = indexController.rogMapController!.mapEventStream.listen((MapEvent mapEvent) {
|
||||
// if (mapEvent is MapEventMoveStart) {
|
||||
// //print(DateTime.now().toString() + ' [MapEventMoveStart] START');
|
||||
// // do something
|
||||
// }
|
||||
// if (mapEvent is MapEventMoveEnd) {
|
||||
// destinationController.isSelected.value = false;
|
||||
// //print(DateTime.now().toString() + ' [MapEventMoveStart] END');
|
||||
// //indexController.loadLocationsBound();
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// } ,
|
||||
// bounds: indexController.currentBound.length > 0 ? indexController.currentBound[0]: LatLngBounds.fromPoints([LatLng(35.03999881162295, 136.40587119778962), LatLng(36.642756778706904, 137.95226720406063)]),
|
||||
// zoom: 1,
|
||||
// maxZoom: 42,
|
||||
// interactiveFlags: InteractiveFlag.pinchZoom | InteractiveFlag.drag,
|
||||
// //plugins: [LocationMarkerPlugin(),]
|
||||
// ),
|
||||
// children: [
|
||||
// TileLayerWidget(
|
||||
// options: TileLayerOptions(
|
||||
// urlTemplate: 'https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png',
|
||||
// subdomains: ['a', 'b', 'c'],
|
||||
// ),
|
||||
// ),
|
||||
// //Obx(() =>
|
||||
// indexController.routePoints.length > 0 ?
|
||||
// PolylineLayerWidget(
|
||||
// options: PolylineLayerOptions(
|
||||
// polylines: [
|
||||
// Polyline(
|
||||
// points: getPoints(ptts)!,
|
||||
// strokeWidth: 6.0,
|
||||
// color: Colors.indigo
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// )
|
||||
// :
|
||||
// Container(),
|
||||
// //),
|
||||
// // PopupMarkerLayerWidget(
|
||||
// // options: PopupMarkerLayerOptions(
|
||||
// // popupController: _popupLayerController,
|
||||
// // markers: _markers,
|
||||
// // markerRotateAlignment:
|
||||
// // PopupMarkerLayerOptions.rotationAlignmentFor(AnchorAlign.top),
|
||||
// // popupBuilder: (BuildContext context, Marker marker) =>
|
||||
|
||||
// // examplePopup(marker),
|
||||
// // ),
|
||||
// // ),
|
||||
// LocationMarkerLayerWidget(),
|
||||
// MarkerLayerWidget(
|
||||
// options: MarkerLayerOptions(
|
||||
// markers: getMarkers()!
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
|
||||
// );
|
||||
// }
|
||||
//}
|
||||
@ -1,128 +1,274 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/index/index_controller.dart';
|
||||
import 'package:rogapp/routes/app_pages.dart';
|
||||
import 'package:rogapp/services/auth_service.dart';
|
||||
import 'package:gifunavi/pages/destination/destination_controller.dart';
|
||||
import 'package:gifunavi/pages/index/index_controller.dart';
|
||||
import 'package:gifunavi/routes/app_pages.dart';
|
||||
import 'package:gifunavi/services/auth_service.dart';
|
||||
import 'package:gifunavi/utils/database_helper.dart';
|
||||
import 'package:gifunavi/widgets/debug_widget.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:gifunavi/pages/WebView/WebView_page.dart';
|
||||
|
||||
// SafeAreaウィジェットを使用して、画面の安全領域内にメニューを表示しています。
|
||||
// Columnウィジェットを使用して、メニューアイテムを縦に並べています。
|
||||
//
|
||||
class DrawerPage extends StatelessWidget {
|
||||
DrawerPage({ Key? key }) : super(key: key);
|
||||
DrawerPage({super.key});
|
||||
|
||||
final IndexController indexController = Get.find<IndexController>();
|
||||
|
||||
LogManager logManager = LogManager();
|
||||
|
||||
// 要検討:URLの起動に失敗した場合のエラーハンドリングが不十分です。適切なエラーメッセージを表示するなどの処理を追加してください。
|
||||
//
|
||||
/*
|
||||
void _launchURL(url) async {
|
||||
if (!await launch(url)) throw 'Could not launch $url';
|
||||
if (!await launchUrl(url)) throw 'Could not launch $url';
|
||||
}
|
||||
*/
|
||||
|
||||
void _launchURL(BuildContext context,String urlString) async {
|
||||
try {
|
||||
logManager.addOperationLog('User clicked $urlString on the drawer');
|
||||
Uri url = Uri.parse(urlString);
|
||||
if (await canLaunchUrl(url)) {
|
||||
await launchUrl(url);
|
||||
} else {
|
||||
// URLを開けない場合のフォールバック動作
|
||||
// 例えば、WebViewを使用してアプリ内でURLを開く
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => WebViewPage(url: urlString),
|
||||
),
|
||||
);
|
||||
}
|
||||
}catch(e){
|
||||
// エラーメッセージを表示する
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('URLを開けませんでした: $e')),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: Drawer(
|
||||
// Add a ListView to the drawer. This ensures the user can scroll
|
||||
// through the options in the drawer if there isn't enough vertical
|
||||
// space to fit everything.
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 100,
|
||||
color: Colors.amber,
|
||||
child: Obx(() =>
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child:
|
||||
indexController.currentUser.isEmpty ?
|
||||
Flexible(child: Text("drawer_title".tr, style: const TextStyle(color: Colors.black, fontSize: 20),))
|
||||
:
|
||||
Text(indexController.currentUser[0]['user']['email'], style: const TextStyle(color: Colors.black, fontSize: 20),),
|
||||
),
|
||||
)
|
||||
),
|
||||
child: Obx(() => Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: indexController.currentUser.isEmpty
|
||||
? Flexible(
|
||||
child: Text(
|
||||
"drawer_title".tr,
|
||||
style: const TextStyle(
|
||||
color: Colors.black, fontSize: 20),
|
||||
))
|
||||
: Text(
|
||||
indexController.currentUser[0]['user']['email'],
|
||||
style: const TextStyle(
|
||||
color: Colors.black, fontSize: 20),
|
||||
),
|
||||
),
|
||||
)),
|
||||
),
|
||||
Obx(() =>
|
||||
indexController.currentUser.isEmpty ?
|
||||
ListTile(
|
||||
leading: const Icon(Icons.login),
|
||||
title: Text("login".tr),
|
||||
onTap: (){
|
||||
Get.toNamed(AppPages.LOGIN);
|
||||
},
|
||||
) :
|
||||
ListTile(
|
||||
leading: const Icon(Icons.login),
|
||||
title: Text("logout".tr),
|
||||
onTap: (){
|
||||
indexController.logout();
|
||||
Get.toNamed(AppPages.TRAVEL);
|
||||
},
|
||||
)
|
||||
),
|
||||
indexController.currentUser.isNotEmpty ?
|
||||
|
||||
ListTile(
|
||||
leading: const Icon(Icons.password),
|
||||
title: Text("change_password".tr),
|
||||
onTap: (){
|
||||
Get.toNamed(AppPages.CHANGE_PASSWORD);
|
||||
leading: const Icon(Icons.group),
|
||||
title: const Text('チーム管理'),
|
||||
onTap: () async{
|
||||
//Get.back();
|
||||
// スナックバーを安全に閉じる
|
||||
await _safelyCloseSnackbar();
|
||||
Get.toNamed(AppPages.TEAM_LIST);
|
||||
},
|
||||
) :
|
||||
const SizedBox(width: 0, height: 0,),
|
||||
indexController.currentUser.isEmpty ?
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.event),
|
||||
title: const Text('エントリー管理'),
|
||||
onTap: () {
|
||||
Get.back();
|
||||
Get.toNamed(AppPages.ENTRY_LIST);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.event),
|
||||
title: const Text('イベント参加'),
|
||||
onTap: () {
|
||||
Get.back(); // ドロワーを閉じる
|
||||
Get.toNamed(AppPages.EVENT_ENTRY);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.person),
|
||||
title: Text("sign_up".tr),
|
||||
onTap: (){
|
||||
Get.toNamed(AppPages.REGISTER);
|
||||
title: const Text("個人情報の修正"),
|
||||
onTap: () {
|
||||
Get.back(); // Close the drawer
|
||||
Get.toNamed(AppPages.USER_DETAILS_EDIT);
|
||||
},
|
||||
) :
|
||||
const SizedBox(width: 0, height: 0,),
|
||||
indexController.currentUser.isNotEmpty ?
|
||||
ListTile(
|
||||
leading: const Icon(Icons.delete_forever),
|
||||
title: Text("delete_account".tr),
|
||||
onTap: (){
|
||||
String token = indexController.currentUser[0]['token'];
|
||||
AuthService.deleteUser(token).then((value){
|
||||
if(value.isNotEmpty){
|
||||
indexController.logout();
|
||||
Get.toNamed(AppPages.TRAVEL);
|
||||
Get.snackbar("accounted_deleted".tr, "account_deleted_message".tr);
|
||||
}
|
||||
});
|
||||
},
|
||||
) :
|
||||
const SizedBox(width: 0, height: 0,),
|
||||
// ListTile(
|
||||
// leading: const Icon(Icons.person),
|
||||
// title: Text("profile".tr),
|
||||
// onTap: (){},
|
||||
// ),
|
||||
// ListTile(
|
||||
// leading: const Icon(Icons.route),
|
||||
// title: Text("recommended_route".tr),
|
||||
// onTap: (){},
|
||||
// ),
|
||||
// ListTile(
|
||||
// leading: const Icon(Icons.favorite_rounded),
|
||||
// title: Text("point_rank".tr),
|
||||
// onTap: (){},
|
||||
// ),
|
||||
indexController.currentUser.isNotEmpty ?
|
||||
ListTile(
|
||||
leading: const Icon(Icons.featured_video),
|
||||
title: Text("rog_web".tr),
|
||||
onTap: (){
|
||||
_launchURL("https://www.gifuai.net/?page_id=17397");
|
||||
},
|
||||
) :
|
||||
const SizedBox(width: 0, height: 0,),
|
||||
),
|
||||
|
||||
Obx(() => indexController.currentUser.isEmpty
|
||||
? ListTile(
|
||||
leading: const Icon(Icons.login),
|
||||
title: Text("login".tr),
|
||||
onTap: () {
|
||||
Get.toNamed(AppPages.LOGIN);
|
||||
},
|
||||
)
|
||||
: ListTile(
|
||||
leading: const Icon(Icons.login),
|
||||
title: Text("logout".tr),
|
||||
onTap: () {
|
||||
indexController.logout();
|
||||
Get.toNamed(AppPages.LOGIN);
|
||||
},
|
||||
)),
|
||||
indexController.currentUser.isNotEmpty
|
||||
? ListTile(
|
||||
leading: const Icon(Icons.password),
|
||||
title: Text("change_password".tr),
|
||||
onTap: () {
|
||||
Get.toNamed(AppPages.CHANGE_PASSWORD);
|
||||
},
|
||||
)
|
||||
: const SizedBox(
|
||||
width: 0,
|
||||
height: 0,
|
||||
),
|
||||
indexController.currentUser.isEmpty
|
||||
? ListTile(
|
||||
leading: const Icon(Icons.person),
|
||||
title: Text("sign_up".tr),
|
||||
onTap: () {
|
||||
Get.toNamed(AppPages.REGISTER);
|
||||
},
|
||||
)
|
||||
: const SizedBox(
|
||||
width: 0,
|
||||
height: 0,
|
||||
),
|
||||
indexController.currentUser.isNotEmpty
|
||||
? ListTile(
|
||||
leading: const Icon(Icons.password),
|
||||
title: Text('reset_button'.tr),
|
||||
onTap: () {
|
||||
logManager.addOperationLog('User clicked RESET button on the drawer');
|
||||
// 要検討:リセット操作の確認メッセージをローカライズすることを検討してください。
|
||||
//
|
||||
Get.defaultDialog(
|
||||
title: "reset_title".tr,
|
||||
middleText: "reset_message".tr,
|
||||
textConfirm: "confirm".tr,
|
||||
textCancel: "cancel".tr,
|
||||
onCancel: () => Get.back(),
|
||||
onConfirm: () async {
|
||||
DestinationController destinationController =
|
||||
Get.find<DestinationController>();
|
||||
DatabaseHelper databaseHelper = DatabaseHelper.instance;
|
||||
|
||||
// ゲーム中のデータを削除
|
||||
await databaseHelper.deleteAllRogaining();
|
||||
await databaseHelper.deleteAllDestinations();
|
||||
destinationController.resetRogaining();
|
||||
|
||||
//destinationController.resetRogaining();
|
||||
//destinationController.deleteDBDestinations();
|
||||
Get.back();
|
||||
Get.snackbar(
|
||||
"reset_done".tr,
|
||||
"reset_explain".tr,
|
||||
backgroundColor: Colors.green,
|
||||
colorText: Colors.white,
|
||||
duration: const Duration(seconds: 3),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
: const SizedBox(
|
||||
width: 0,
|
||||
height: 0,
|
||||
),
|
||||
indexController.currentUser.isNotEmpty
|
||||
? ListTile(
|
||||
leading: const Icon(Icons.delete_forever),
|
||||
title: Text("delete_account".tr),
|
||||
onTap: () {
|
||||
Get.defaultDialog(
|
||||
title: "delete_account_title".tr,
|
||||
middleText: "delete_account_middle".tr,
|
||||
textConfirm: "confirm".tr,
|
||||
textCancel: "cancel".tr,
|
||||
onCancel: () => Get.back(),
|
||||
onConfirm: () {
|
||||
logManager.addOperationLog('User clicked Confirm button on the account delete dialog');
|
||||
String token = indexController.currentUser[0]['token'];
|
||||
AuthService.deleteUser(token).then((value) {
|
||||
if (value.isNotEmpty) {
|
||||
indexController.logout();
|
||||
Get.toNamed(AppPages.TRAVEL);
|
||||
Get.snackbar("accounted_deleted".tr,
|
||||
"account_deleted_message".tr,
|
||||
backgroundColor: Colors.green,
|
||||
colorText: Colors.white
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
: const SizedBox(
|
||||
width: 0,
|
||||
height: 0,
|
||||
),
|
||||
indexController.currentUser.isNotEmpty
|
||||
? ListTile(
|
||||
leading: const Icon(Icons.featured_video),
|
||||
title: Text("rog_web".tr),
|
||||
onTap: () {
|
||||
_launchURL(context, "https://www.gifuai.net/?page_id=60043");
|
||||
},
|
||||
)
|
||||
: const SizedBox(
|
||||
width: 0,
|
||||
height: 0,
|
||||
),
|
||||
|
||||
ListTile(
|
||||
leading: const Icon(Icons.privacy_tip),
|
||||
title: Text("privacy".tr),
|
||||
onTap: (){
|
||||
_launchURL("https://rogaining.sumasen.net/api/privacy/");
|
||||
onTap: () {
|
||||
_launchURL(context, "https://rogaining.sumasen.net/api/privacy/");
|
||||
},
|
||||
)
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.settings),
|
||||
title: Text('open_settings'.tr),
|
||||
onTap: () {
|
||||
Get.back(); // ドロワーを閉じる
|
||||
Get.toNamed(Routes.SETTINGS);
|
||||
},
|
||||
),
|
||||
|
||||
//ListTile(
|
||||
// leading: const Icon(Icons.developer_mode),
|
||||
// title: const Text('open_settings'),
|
||||
// onTap: () {
|
||||
// Get.back(); // ドロワーを閉じる
|
||||
// Get.toNamed('/debug'); // デバッグ画面に遷移
|
||||
// },
|
||||
//),
|
||||
|
||||
|
||||
// ListTile(
|
||||
// leading: const Icon(Icons.router),
|
||||
// title: Text("my_route".tr),
|
||||
@ -138,4 +284,14 @@ class DrawerPage extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _safelyCloseSnackbar() async {
|
||||
if (Get.isSnackbarOpen) {
|
||||
try {
|
||||
await Get.closeCurrentSnackbar();
|
||||
} catch (e) {
|
||||
print('Error closing snackbar: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
lib/pages/entry/entry_binding.dart
Normal file
11
lib/pages/entry/entry_binding.dart
Normal file
@ -0,0 +1,11 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gifunavi/pages/entry/entry_controller.dart';
|
||||
import 'package:gifunavi/services/api_service.dart';
|
||||
|
||||
class EntryBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<ApiService>(() => ApiService());
|
||||
Get.lazyPut<EntryController>(() => EntryController());
|
||||
}
|
||||
}
|
||||
310
lib/pages/entry/entry_controller.dart
Normal file
310
lib/pages/entry/entry_controller.dart
Normal file
@ -0,0 +1,310 @@
|
||||
// lib/entry/entry_controller.dart
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gifunavi/model/entry.dart';
|
||||
import 'package:gifunavi/model/event.dart';
|
||||
import 'package:gifunavi/model/team.dart';
|
||||
import 'package:gifunavi/model/category.dart';
|
||||
import 'package:gifunavi/services/api_service.dart';
|
||||
|
||||
import '../index/index_controller.dart';
|
||||
import 'package:timezone/timezone.dart' as tz;
|
||||
|
||||
class EntryController extends GetxController {
|
||||
late ApiService _apiService;
|
||||
|
||||
final entries = <Entry>[].obs;
|
||||
final events = <Event>[].obs;
|
||||
final teams = <Team>[].obs;
|
||||
final categories = <NewCategory>[].obs;
|
||||
|
||||
final selectedEvent = Rx<Event?>(null);
|
||||
final selectedTeam = Rx<Team?>(null);
|
||||
final selectedCategory = Rx<NewCategory?>(null);
|
||||
final selectedDate = Rx<DateTime?>(null);
|
||||
|
||||
final currentEntry = Rx<Entry?>(null);
|
||||
final isLoading = true.obs;
|
||||
|
||||
@override
|
||||
void onInit() async {
|
||||
super.onInit();
|
||||
await initializeApiService();
|
||||
await loadInitialData();
|
||||
}
|
||||
|
||||
Future<void> initializeApiService() async {
|
||||
try {
|
||||
_apiService = await Get.putAsync(() => ApiService().init());
|
||||
} catch (e) {
|
||||
print('Error initializing ApiService: $e');
|
||||
Get.snackbar('Error', 'Failed to initialize API service');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loadInitialData() async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
await Future.wait([
|
||||
fetchEntries(),
|
||||
fetchEvents(),
|
||||
fetchTeams(),
|
||||
fetchCategories(),
|
||||
]);
|
||||
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;
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
print('Error initializing data: $e');
|
||||
Get.snackbar('Error', 'Failed to load initial data');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void initializeEditMode(Entry entry) {
|
||||
currentEntry.value = entry;
|
||||
selectedEvent.value = entry.event;
|
||||
selectedTeam.value = entry.team;
|
||||
selectedCategory.value = entry.category;
|
||||
selectedDate.value = entry.date;
|
||||
}
|
||||
|
||||
void updateEvent(Event? value) {
|
||||
selectedEvent.value = value;
|
||||
if (value != null) {
|
||||
// イベント変更時に日付を調整
|
||||
if (selectedDate.value == null ||
|
||||
selectedDate.value!.isBefore(value.startDatetime) ||
|
||||
selectedDate.value!.isAfter(value.endDatetime)) {
|
||||
selectedDate.value = value.startDatetime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateTeam(Team? value) {
|
||||
selectedTeam.value = value;
|
||||
if (value != null) {
|
||||
selectedCategory.value = value.category;
|
||||
}
|
||||
}
|
||||
//void updateTeam(Team? value) => selectedTeam.value = value;
|
||||
void updateCategory(NewCategory? value) => selectedCategory.value = value;
|
||||
//void updateDate(DateTime value) => selectedDate.value = value;
|
||||
void updateDate(DateTime value) {
|
||||
selectedDate.value = tz.TZDateTime.from(value, tz.getLocation('Asia/Tokyo'));
|
||||
}
|
||||
/*
|
||||
void updateDate(DateTime value){
|
||||
selectedDate.value = DateFormat('yyyy-MM-dd').format(value!) as DateTime?;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
void _initializeEntryData() {
|
||||
if (currentEntry.value != null) {
|
||||
selectedEvent.value = currentEntry.value!.event;
|
||||
selectedTeam.value = currentEntry.value!.team;
|
||||
selectedCategory.value = currentEntry.value!.category;
|
||||
selectedDate.value = currentEntry.value!.date;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchEntries() async {
|
||||
try {
|
||||
final fetchedEntries = await _apiService.getEntries();
|
||||
entries.assignAll(fetchedEntries);
|
||||
} catch (e) {
|
||||
print('Error fetching entries: $e');
|
||||
Get.snackbar('Error', 'Failed to fetch entries');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchEvents() async {
|
||||
try {
|
||||
final fetchedEvents = await _apiService.getEvents();
|
||||
events.assignAll(fetchedEvents.map((event) {
|
||||
// end_dateの7日前を締め切りとして設定
|
||||
final deadlineDateTime = event.endDatetime.subtract(const Duration(days: 7));
|
||||
return Event(
|
||||
id: event.id,
|
||||
eventName: event.eventName,
|
||||
startDatetime: event.startDatetime,
|
||||
endDatetime: event.endDatetime,
|
||||
deadlineDateTime: deadlineDateTime,
|
||||
);
|
||||
}).toList());
|
||||
} catch (e) {
|
||||
print('Error fetching events: $e');
|
||||
Get.snackbar('Error', 'Failed to fetch events');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchEvents_old() async {
|
||||
try {
|
||||
final fetchedEvents = await _apiService.getEvents();
|
||||
events.assignAll(fetchedEvents);
|
||||
} catch (e) {
|
||||
print('Error fetching events: $e');
|
||||
Get.snackbar('Error', 'Failed to fetch events');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchTeams() async {
|
||||
try {
|
||||
final fetchedTeams = await _apiService.getTeams();
|
||||
teams.assignAll(fetchedTeams);
|
||||
} catch (e) {
|
||||
print('Error fetching teams: $e');
|
||||
Get.snackbar('Error', 'Failed to fetch team');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchCategories() async {
|
||||
try {
|
||||
final fetchedCategories = await _apiService.getCategories();
|
||||
categories.assignAll(fetchedCategories);
|
||||
} catch (e) {
|
||||
print('Error fetching categories: $e');
|
||||
Get.snackbar('Error', 'Failed to fetch categories');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> createEntry() async {
|
||||
if (selectedEvent.value == null || selectedTeam.value == null ||
|
||||
selectedCategory.value == null || selectedDate.value == null) {
|
||||
Get.snackbar('Error', 'Please fill all fields');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
isLoading.value = true;
|
||||
// Get zekken number
|
||||
final updatedCategory = await _apiService.getZekkenNumber(selectedCategory.value!.id);
|
||||
final zekkenNumber = updatedCategory.categoryNumber.toString();
|
||||
|
||||
final newEntry = await _apiService.createEntry(
|
||||
selectedTeam.value!.id,
|
||||
selectedEvent.value!.id,
|
||||
selectedCategory.value!.id,
|
||||
selectedDate.value!,
|
||||
zekkenNumber,
|
||||
);
|
||||
entries.add(newEntry);
|
||||
Get.back();
|
||||
} catch (e) {
|
||||
print('Error creating entry: $e');
|
||||
Get.snackbar('Error', 'Failed to create entry');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateEntryAndRefreshMap() async {
|
||||
await updateEntry();
|
||||
|
||||
// エントリーが正常に更新された後、マップをリフレッシュ
|
||||
final indexController = Get.find<IndexController>();
|
||||
final eventCode = currentEntry.value?.event.eventName ?? '';
|
||||
indexController.reloadMap(eventCode);
|
||||
}
|
||||
|
||||
bool isEntryEditable(Event event) {
|
||||
return DateTime.now().isBefore(event.deadlineDateTime);
|
||||
}
|
||||
|
||||
Future<void> updateEntry() async {
|
||||
if (currentEntry.value == null) {
|
||||
Get.snackbar('Error', 'No entry selected for update');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isEntryEditable(currentEntry.value!.event)) {
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
title: Text('エントリー変更不可'),
|
||||
content: Text('締め切りを過ぎているため、エントリーの変更はできません。変更が必要な場合は事務局にお問い合わせください。'),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('OK'),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
isLoading.value = true;
|
||||
final updatedEntry = await _apiService.updateEntry(
|
||||
currentEntry.value!.id,
|
||||
currentEntry.value!.team.id,
|
||||
selectedEvent.value!.id,
|
||||
selectedCategory.value!.id,
|
||||
selectedDate.value!,
|
||||
currentEntry.value!.zekkenNumber,
|
||||
);
|
||||
final index = entries.indexWhere((entry) => entry.id == updatedEntry.id);
|
||||
if (index != -1) {
|
||||
entries[index] = updatedEntry;
|
||||
}
|
||||
Get.back();
|
||||
} catch (e) {
|
||||
print('Error updating entry: $e');
|
||||
Get.snackbar('Error', 'Failed to update entry');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateEntryCategory(int entryId, int newCategoryId) async {
|
||||
try {
|
||||
//await _apiService.updateEntryCategory(entryId, newCategoryId);
|
||||
final updatedEntry = await _apiService.updateEntry(
|
||||
currentEntry.value!.id,
|
||||
currentEntry.value!.team.id,
|
||||
selectedEvent.value!.id,
|
||||
newCategoryId,
|
||||
currentEntry.value!.date!,
|
||||
currentEntry.value!.zekkenNumber,
|
||||
);
|
||||
await fetchEntries();
|
||||
} catch (e) {
|
||||
print('Error updating entry category: $e');
|
||||
Get.snackbar('エラー', 'エントリーのカテゴリ更新に失敗しました');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteEntry() async {
|
||||
if (currentEntry.value == null) {
|
||||
Get.snackbar('Error', 'No entry selected for deletion');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
isLoading.value = true;
|
||||
await _apiService.deleteEntry(currentEntry.value!.id);
|
||||
entries.removeWhere((entry) => entry.id == currentEntry.value!.id);
|
||||
Get.back();
|
||||
} catch (e) {
|
||||
print('Error deleting entry: $e');
|
||||
Get.snackbar('Error', 'Failed to delete entry');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool isOwner() {
|
||||
// Implement logic to check if the current user is the owner of the entry
|
||||
return true; // Placeholder
|
||||
}
|
||||
}
|
||||
193
lib/pages/entry/entry_detail_page.dart
Normal file
193
lib/pages/entry/entry_detail_page.dart
Normal file
@ -0,0 +1,193 @@
|
||||
// lib/pages/entry/entry_detail_page.dart
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gifunavi/pages/entry/entry_controller.dart';
|
||||
import 'package:gifunavi/model/event.dart';
|
||||
import 'package:gifunavi/model/category.dart';
|
||||
import 'package:gifunavi/model/team.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import 'package:timezone/timezone.dart' as tz;
|
||||
|
||||
class EntryDetailPage extends GetView<EntryController> {
|
||||
const EntryDetailPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Map<String, dynamic> arguments = Get.arguments ?? {};
|
||||
final mode = Get.arguments['mode'] as String? ?? 'new';
|
||||
final entry = Get.arguments['entry'];
|
||||
|
||||
if (mode == 'edit' && entry != null) {
|
||||
controller.initializeEditMode(entry);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(mode == 'new' ? 'エントリー登録' : 'エントリー詳細'),
|
||||
),
|
||||
body: Obx(() {
|
||||
if (controller.isLoading.value) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildDropdown<Event>(
|
||||
label: 'イベント',
|
||||
items: controller.events,
|
||||
selectedId: controller.selectedEvent.value?.id,
|
||||
onChanged: (eventId) => controller.updateEvent(
|
||||
controller.events.firstWhere((e) => e.id == eventId)
|
||||
),
|
||||
getDisplayName: (event) => event.eventName,
|
||||
getId: (event) => event.id,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildDropdown<Team>(
|
||||
label: 'チーム',
|
||||
items: controller.teams,
|
||||
selectedId: controller.selectedTeam.value?.id,
|
||||
onChanged: (teamId) => controller.updateTeam(
|
||||
controller.teams.firstWhere((t) => t.id == teamId)
|
||||
),
|
||||
getDisplayName: (team) => team.teamName,
|
||||
getId: (team) => team.id,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildCategoryDropdown(),
|
||||
/*
|
||||
_buildDropdown<NewCategory>()
|
||||
label: 'カテゴリ',
|
||||
items: controller.categories,
|
||||
selectedId: controller.selectedCategory.value?.id,
|
||||
onChanged: (categoryId) => controller.updateCategory(
|
||||
controller.categories.firstWhere((c) => c.id == categoryId)
|
||||
),
|
||||
getDisplayName: (category) => category.categoryName,
|
||||
getId: (category) => category.id,
|
||||
),
|
||||
|
||||
*/
|
||||
const SizedBox(height: 16),
|
||||
ListTile(
|
||||
title: const Text('日付'),
|
||||
subtitle: Text(
|
||||
controller.selectedDate.value != null
|
||||
? DateFormat('yyyy-MM-dd').format(tz.TZDateTime.from(controller.selectedDate.value!, tz.getLocation('Asia/Tokyo')))
|
||||
: '日付を選択してください',
|
||||
),
|
||||
onTap: () async {
|
||||
if (controller.selectedEvent.value == null) {
|
||||
Get.snackbar('Error', 'Please select an event first');
|
||||
return;
|
||||
}
|
||||
final tz.TZDateTime now = tz.TZDateTime.now(tz.getLocation('Asia/Tokyo'));
|
||||
final tz.TZDateTime eventStart = tz.TZDateTime.from(controller.selectedEvent.value!.startDatetime, tz.getLocation('Asia/Tokyo'));
|
||||
final tz.TZDateTime eventEnd = tz.TZDateTime.from(controller.selectedEvent.value!.endDatetime, tz.getLocation('Asia/Tokyo'));
|
||||
|
||||
final tz.TZDateTime initialDate = controller.selectedDate.value != null
|
||||
? tz.TZDateTime.from(controller.selectedDate.value!, tz.getLocation('Asia/Tokyo'))
|
||||
: (now.isAfter(eventStart) ? now : eventStart);
|
||||
|
||||
// 選択可能な最初の日付を設定(今日かイベント開始日のうち、より後の日付)
|
||||
final tz.TZDateTime firstDate = now.isAfter(eventStart) ? now : eventStart;
|
||||
|
||||
final DateTime? picked = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: initialDate.isAfter(firstDate) ? initialDate : firstDate,
|
||||
firstDate: firstDate,
|
||||
lastDate: eventEnd,
|
||||
);
|
||||
if (picked != null) {
|
||||
controller.updateDate(tz.TZDateTime.from(picked, tz.getLocation('Asia/Tokyo')));
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
if (mode == 'new')
|
||||
ElevatedButton(
|
||||
onPressed: () => controller.createEntry(),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue,
|
||||
foregroundColor: Colors.white,
|
||||
minimumSize: const Size(double.infinity, 50),
|
||||
),
|
||||
child: const Text('エントリーを作成'),
|
||||
)
|
||||
else
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () => controller.deleteEntry(),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
foregroundColor: Colors.white,
|
||||
minimumSize: const Size(0, 50),
|
||||
),
|
||||
child: const Text('エントリーを削除'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () => controller.updateEntry(),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.lightBlue,
|
||||
foregroundColor: Colors.white,
|
||||
minimumSize: const Size(0, 50),
|
||||
),
|
||||
child: const Text('エントリーを更新'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDropdown<T>({
|
||||
required String label,
|
||||
required List<T> items,
|
||||
required int? selectedId,
|
||||
required void Function(int?) onChanged,
|
||||
required String Function(T) getDisplayName,
|
||||
required int Function(T) getId,
|
||||
}) {
|
||||
return DropdownButtonFormField<int>(
|
||||
decoration: InputDecoration(labelText: label),
|
||||
value: selectedId,
|
||||
items: items.map((item) => DropdownMenuItem<int>(
|
||||
value: getId(item),
|
||||
child: Text(getDisplayName(item)),
|
||||
)).toList(),
|
||||
onChanged: onChanged,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCategoryDropdown() {
|
||||
final eligibleCategories = controller.categories.where((c) =>
|
||||
c.baseCategory == controller.selectedCategory.value?.baseCategory
|
||||
).toList();
|
||||
|
||||
return DropdownButtonFormField<NewCategory>(
|
||||
decoration: InputDecoration(labelText: 'カテゴリ'),
|
||||
value: controller.selectedCategory.value,
|
||||
items: eligibleCategories.map((category) => DropdownMenuItem<NewCategory>(
|
||||
value: category,
|
||||
child: Text(category.categoryName),
|
||||
)).toList(),
|
||||
onChanged: (value) => controller.updateCategory(value),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
116
lib/pages/entry/entry_list_page.dart
Normal file
116
lib/pages/entry/entry_list_page.dart
Normal file
@ -0,0 +1,116 @@
|
||||
// lib/pages/entry/entry_list_page.dart
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:gifunavi/pages/entry/entry_controller.dart';
|
||||
import 'package:gifunavi/routes/app_pages.dart';
|
||||
import 'package:timezone/timezone.dart' as tz;
|
||||
|
||||
class EntryListPage extends GetView<EntryController> {
|
||||
const EntryListPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('エントリー管理'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () => Get.toNamed(AppPages.ENTRY_DETAIL, arguments: {'mode': 'new'}),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Obx(() {
|
||||
if (controller.entries.isEmpty) {
|
||||
return const Center(
|
||||
child: Text('表示するエントリーがありません。'),
|
||||
);
|
||||
}
|
||||
return ListView.builder(
|
||||
itemCount: controller.entries.length,
|
||||
itemBuilder: (context, index) {
|
||||
final entry = controller.entries[index];
|
||||
return ListTile(
|
||||
title: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text('${_formatDate(entry.date)}: ${entry.event.eventName}'),
|
||||
),
|
||||
Text(entry.team.teamName, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
subtitle: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text('カテゴリー: ${entry.category.categoryName}'),
|
||||
),
|
||||
Text('ゼッケン: ${entry.zekkenNumber ?? "未設定"}'),
|
||||
],
|
||||
),
|
||||
onTap: () =>
|
||||
Get.toNamed(AppPages.ENTRY_DETAIL,
|
||||
arguments: {'mode': 'edit', 'entry': entry}),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatDate(DateTime? date) {
|
||||
if (date == null) {
|
||||
return '日時未設定';
|
||||
}
|
||||
final jstDate = tz.TZDateTime.from(date, tz.getLocation('Asia/Tokyo'));
|
||||
return DateFormat('yyyy-MM-dd').format(jstDate);
|
||||
}
|
||||
}
|
||||
|
||||
class EntryListPage_old extends GetView<EntryController> {
|
||||
const EntryListPage_old({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('エントリー管理'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () => Get.toNamed(AppPages.ENTRY_DETAIL, arguments: {'mode': 'new'}),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Obx((){
|
||||
if (controller.isLoading.value) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
// エントリーを日付昇順にソート
|
||||
final sortedEntries = controller.entries.toList()
|
||||
..sort((a, b) => (a.date ?? DateTime(0)).compareTo(b.date ?? DateTime(0)));
|
||||
|
||||
return ListView.builder(
|
||||
itemCount: sortedEntries.length,
|
||||
itemBuilder: (context, index) {
|
||||
final entry = sortedEntries[index];
|
||||
return ListTile(
|
||||
title: Text(entry.event.eventName ?? 'イベント未設定'),
|
||||
subtitle: Text(
|
||||
'${entry.team.teamName ?? 'チーム未設定'} - ${entry.category
|
||||
.categoryName ?? 'カテゴリ未設定'}'),
|
||||
trailing: Text(
|
||||
entry.date?.toString().substring(0, 10) ?? '日付未設定'),
|
||||
onTap: () =>
|
||||
Get.toNamed(AppPages.ENTRY_DETAIL,
|
||||
arguments: {'mode': 'edit', 'entry': entry}),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
9
lib/pages/entry/event_entries_binding.dart
Normal file
9
lib/pages/entry/event_entries_binding.dart
Normal file
@ -0,0 +1,9 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gifunavi/pages/entry/event_entries_controller.dart';
|
||||
|
||||
class EventEntriesBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<EventEntriesController>(() => EventEntriesController());
|
||||
}
|
||||
}
|
||||
129
lib/pages/entry/event_entries_controller.dart
Normal file
129
lib/pages/entry/event_entries_controller.dart
Normal file
@ -0,0 +1,129 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gifunavi/model/entry.dart';
|
||||
import 'package:gifunavi/pages/index/index_controller.dart';
|
||||
import 'package:gifunavi/pages/destination/destination_controller.dart';
|
||||
import 'package:gifunavi/services/api_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:timezone/timezone.dart' as tz;
|
||||
import 'package:timezone/data/latest.dart' as tz;
|
||||
|
||||
class EventEntriesController extends GetxController {
|
||||
final ApiService _apiService = Get.find<ApiService>();
|
||||
final IndexController _indexController = Get.find<IndexController>();
|
||||
late final DestinationController _destinationController;
|
||||
|
||||
final entries = <Entry>[].obs;
|
||||
final filteredEntries = <Entry>[].obs;
|
||||
final showTodayEntries = true.obs;
|
||||
|
||||
static bool _timezoneInitialized = false;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
_initializeTimezone();
|
||||
// DestinationControllerが登録されていない場合に備えて、lazyPutを使用
|
||||
Get.lazyPut<DestinationController>(() => DestinationController(), fenix: true);
|
||||
_destinationController = Get.find<DestinationController>();
|
||||
|
||||
fetchEntries();
|
||||
}
|
||||
|
||||
void _initializeTimezone() {
|
||||
if (!_timezoneInitialized) {
|
||||
tz.initializeTimeZones();
|
||||
_timezoneInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchEntries() async {
|
||||
try {
|
||||
final fetchedEntries = await _apiService.getEntries();
|
||||
entries.assignAll(fetchedEntries);
|
||||
filterEntries();
|
||||
} catch (e) {
|
||||
print('Error fetching entries: $e');
|
||||
// エラー処理を追加
|
||||
}
|
||||
}
|
||||
|
||||
void filterEntries() {
|
||||
if (showTodayEntries.value) {
|
||||
filterEntriesForToday();
|
||||
} else {
|
||||
filteredEntries.assignAll(entries);
|
||||
}
|
||||
}
|
||||
|
||||
void filterEntriesForToday() {
|
||||
final now = tz.TZDateTime.now(tz.getLocation('Asia/Tokyo'));
|
||||
filteredEntries.assignAll(entries.where((entry) {
|
||||
final entryDate = tz.TZDateTime.from(entry.date!, tz.getLocation('Asia/Tokyo'));
|
||||
return entryDate.year == now.year &&
|
||||
entryDate.month == now.month &&
|
||||
entryDate.day == now.day;
|
||||
}));
|
||||
}
|
||||
|
||||
void filterEntriesForToday_old() {
|
||||
final now = DateTime.now();
|
||||
filteredEntries.assignAll(entries.where((entry) =>
|
||||
entry.date?.year == now.year &&
|
||||
entry.date?.month == now.month &&
|
||||
entry.date?.day == now.day
|
||||
));
|
||||
}
|
||||
|
||||
void toggleShowTodayEntries() {
|
||||
showTodayEntries.toggle();
|
||||
filterEntries();
|
||||
}
|
||||
|
||||
void refreshMap() {
|
||||
final tk = _indexController.currentUser[0]["token"];
|
||||
if (tk != null) {
|
||||
|
||||
_destinationController.fixMapBound(tk);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> joinEvent(Entry entry) async {
|
||||
//final now = DateTime.now();
|
||||
final now = tz.TZDateTime.now(tz.getLocation('Asia/Tokyo'));
|
||||
final entryDate = tz.TZDateTime.from(entry.date!, tz.getLocation('Asia/Tokyo'));
|
||||
bool isToday = entryDate.year == now.year &&
|
||||
entryDate.month == now.month &&
|
||||
entryDate.day == now.day;
|
||||
|
||||
_indexController.setReferenceMode(!isToday);
|
||||
_indexController.setSelectedEventName(entry.event.eventName);
|
||||
|
||||
final userid = _indexController.currentUser[0]["user"]["id"];
|
||||
|
||||
await _apiService.updateUserInfo(userid,entry);
|
||||
|
||||
_indexController.currentUser[0]["user"]["event_date"] = entryDate; // 追加2024-8-9
|
||||
_indexController.currentUser[0]["user"]["event_code"] = entry.event.eventName;
|
||||
_indexController.currentUser[0]["user"]["team_name"] = entry.team.teamName;
|
||||
_indexController.currentUser[0]["user"]["group"] = entry.team.category.categoryName;
|
||||
_indexController.currentUser[0]["user"]["zekken_number"] = entry.zekkenNumber;
|
||||
|
||||
Get.back(); // エントリー一覧ページを閉じる
|
||||
//_indexController.isLoading.value = true;
|
||||
_indexController.reloadMap(entry.event.eventName);
|
||||
|
||||
refreshMap();
|
||||
|
||||
if (isToday) {
|
||||
Get.snackbar('成功', 'イベントに参加しました。',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.green,
|
||||
colorText: Colors.white);
|
||||
} else {
|
||||
Get.snackbar('参照モード', '過去または未来のイベントを参照しています。ロゲの開始やチェックインはできません。',
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.orange,
|
||||
colorText: Colors.white);
|
||||
}
|
||||
}
|
||||
}
|
||||
108
lib/pages/entry/event_entries_page.dart
Normal file
108
lib/pages/entry/event_entries_page.dart
Normal file
@ -0,0 +1,108 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:gifunavi/pages/entry/event_entries_controller.dart';
|
||||
import 'package:timezone/timezone.dart' as tz;
|
||||
|
||||
class EventEntriesPage_old extends GetView<EventEntriesController> {
|
||||
const EventEntriesPage_old({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('イベント参加')),
|
||||
body: Obx(() => ListView.builder(
|
||||
itemCount: controller.entries.length,
|
||||
itemBuilder: (context, index) {
|
||||
final entry = controller.entries[index];
|
||||
return ListTile(
|
||||
title: Text(entry.event.eventName),
|
||||
subtitle: Text('${entry.category.categoryName} - ${entry.date}'),
|
||||
onTap: () => controller.joinEvent(entry),
|
||||
);
|
||||
},
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EventEntriesPage extends GetView<EventEntriesController> {
|
||||
const EventEntriesPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Obx(() => Text(controller.showTodayEntries.value ? 'イベント参加' : 'イベント参照')),
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Obx(() => Text(
|
||||
controller.showTodayEntries.value ? '本日のエントリー' : 'すべてのエントリー',
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
)),
|
||||
Obx(() => Switch(
|
||||
value: !controller.showTodayEntries.value,
|
||||
onChanged: (value) {
|
||||
controller.toggleShowTodayEntries();
|
||||
},
|
||||
activeColor: Colors.blue,
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Obx(() {
|
||||
if (controller.filteredEntries.isEmpty) {
|
||||
return const Center(
|
||||
child: Text('表示するエントリーがありません。'),
|
||||
);
|
||||
}
|
||||
return ListView.builder(
|
||||
itemCount: controller.filteredEntries.length,
|
||||
itemBuilder: (context, index) {
|
||||
final entry = controller.filteredEntries[index];
|
||||
return ListTile(
|
||||
title: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text('${_formatDate(entry.date)}: ${entry.event.eventName}'),
|
||||
),
|
||||
Text(entry.team.teamName, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
subtitle: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text('カテゴリー: ${entry.category.categoryName}'),
|
||||
),
|
||||
Text('ゼッケン: ${entry.zekkenNumber ?? "未設定"}'),
|
||||
],
|
||||
),
|
||||
onTap: () async {
|
||||
await controller.joinEvent(entry);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatDate(DateTime? date) {
|
||||
if (date == null) {
|
||||
return '日時未設定';
|
||||
}
|
||||
final jstDate = tz.TZDateTime.from(date, tz.getLocation('Asia/Tokyo'));
|
||||
return DateFormat('yyyy-MM-dd').format(jstDate);
|
||||
}
|
||||
}
|
||||
|
||||
159
lib/pages/gps/gps_page.dart
Normal file
159
lib/pages/gps/gps_page.dart
Normal file
@ -0,0 +1,159 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:gifunavi/model/gps_data.dart';
|
||||
import 'package:gifunavi/pages/destination/destination_controller.dart';
|
||||
import 'package:gifunavi/pages/index/index_controller.dart';
|
||||
import 'package:gifunavi/utils/database_gps.dart';
|
||||
import 'package:gifunavi/widgets/base_layer_widget.dart';
|
||||
|
||||
class GpsPage extends StatefulWidget {
|
||||
const GpsPage({super.key});
|
||||
|
||||
@override
|
||||
State<GpsPage> createState() => _GpsPageState();
|
||||
}
|
||||
|
||||
class _GpsPageState extends State<GpsPage> {
|
||||
var gpsData = [].obs;
|
||||
MapController? mapController;
|
||||
StreamSubscription? subscription;
|
||||
final IndexController indexController = Get.find<IndexController>();
|
||||
final DestinationController destinationController =
|
||||
Get.find<DestinationController>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
loadGpsData();
|
||||
}
|
||||
|
||||
// 要検討:GPSデータの読み込みに失敗した場合のエラーハンドリングが不十分です。適切なエラーメッセージを表示するなどの処理を追加してください。
|
||||
//
|
||||
void loadGpsData() async {
|
||||
final teamName = indexController.currentUser[0]["user"]['team_name'];
|
||||
final eventCode = indexController.currentUser[0]["user"]["event_code"];
|
||||
GpsDatabaseHelper db = GpsDatabaseHelper.instance;
|
||||
var data = await db.getGPSData(teamName, eventCode);
|
||||
gpsData.value = data;
|
||||
|
||||
//print("--- gps data ${data} ----");
|
||||
}
|
||||
|
||||
// 要検討:マーカーの形状を決定する際に、マジックナンバーが使用されています。定数を使用するなどして、コードの可読性を向上させることを検討してください。
|
||||
//
|
||||
Widget getMarkerShape(GpsData i) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () {},
|
||||
child: Container(
|
||||
height: 22,
|
||||
width: 22,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.transparent,
|
||||
border: Border.all(
|
||||
color:
|
||||
i.is_checkin == 0 ? Colors.blueAccent : Colors.green,
|
||||
width: i.is_checkin == 0 ? 0.4 : 2,
|
||||
style: BorderStyle.solid)),
|
||||
child: const Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.circle,
|
||||
size: 6.0,
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
/*
|
||||
Container(
|
||||
color: Colors.transparent,
|
||||
child: i.is_checkin == 1
|
||||
? Text(
|
||||
DateTime.fromMicrosecondsSinceEpoch(i.created_at)
|
||||
.hour
|
||||
.toString() +
|
||||
":" +
|
||||
DateTime.fromMicrosecondsSinceEpoch(i.created_at)
|
||||
.minute
|
||||
.toString(),
|
||||
// ":" +
|
||||
// DateTime.fromMicrosecondsSinceEpoch(i.created_at)
|
||||
// .second
|
||||
// .toString(),
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
))
|
||||
: Container()),
|
||||
|
||||
*/
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("movement_history".tr),
|
||||
),
|
||||
body: Container(
|
||||
child: Obx(
|
||||
() => FlutterMap(
|
||||
mapController: mapController,
|
||||
options: MapOptions(
|
||||
maxZoom: 18.4,
|
||||
onMapReady: () {},
|
||||
//center: 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)
|
||||
]),
|
||||
zoom: 1,
|
||||
interactiveFlags: InteractiveFlag.pinchZoom | InteractiveFlag.drag,
|
||||
onPositionChanged: (MapPosition pos, bool hasGesture) {
|
||||
if (hasGesture) {
|
||||
indexController.currentBound = [pos.bounds!];
|
||||
}
|
||||
},
|
||||
onTap: (tapPos, cord) {}, // Hide popup when the map is tapped.
|
||||
),
|
||||
children: [
|
||||
const BaseLayer(),
|
||||
MarkerLayer(
|
||||
markers: gpsData.map((i) {
|
||||
return Marker(
|
||||
width: 30.0, // Fixed width
|
||||
height: 30.0, // Fixed height
|
||||
point: LatLng(i.lat, i.lon),
|
||||
child: getMarkerShape(i),
|
||||
alignment: Alignment.center);
|
||||
}).toList(),
|
||||
),
|
||||
// MarkerLayer(
|
||||
// markers: gpsData.map((i) {
|
||||
// return Marker(
|
||||
// alignment: Alignment.center,
|
||||
// height: 32.0,
|
||||
// width: 120.0,
|
||||
// point: LatLng(i.lat, i.lon),
|
||||
// child: getMarkerShape(i));
|
||||
// }).toList(),
|
||||
// )
|
||||
],
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,9 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rogapp/model/destination.dart';
|
||||
import 'package:rogapp/utils/database_helper.dart';
|
||||
import 'package:gifunavi/model/destination.dart';
|
||||
import 'package:gifunavi/utils/database_helper.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class HistoryPage extends StatefulWidget {
|
||||
const HistoryPage({super.key});
|
||||
@ -18,12 +19,14 @@ class _HistoryPageState extends State<HistoryPage> {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("History"),
|
||||
title: Text("pass_history".tr),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
FutureBuilder(
|
||||
// 要検討:スナップショットのエラーハンドリングが行われていますが、具体的なエラーメッセージを表示するようにすることをお勧めします。
|
||||
//
|
||||
future: db.getDestinations(),
|
||||
builder: (BuildContext context,
|
||||
AsyncSnapshot<List<Destination>> snapshot) {
|
||||
@ -32,28 +35,48 @@ class _HistoryPageState extends State<HistoryPage> {
|
||||
return Center(
|
||||
child: Text(
|
||||
'${snapshot.error} occurred',
|
||||
style: TextStyle(fontSize: 18),
|
||||
style: const TextStyle(fontSize: 18),
|
||||
),
|
||||
);
|
||||
} else if (snapshot.hasData) {
|
||||
final dests = snapshot.data;
|
||||
if (dests!.length > 0) {
|
||||
return Center(
|
||||
child: ListView.builder(itemBuilder:(ctx, index){
|
||||
return ListTile(
|
||||
title: Text(dests[index].name?? ""),
|
||||
subtitle: Text(dests[index].address ?? ""),
|
||||
leading: dests[0].photos != null ? Image.file(File(dests[0].photos!)) : Container(),
|
||||
);
|
||||
}),
|
||||
);
|
||||
if (dests!.isNotEmpty) {
|
||||
debugPrint("----- 通過履歴表示 -----");
|
||||
return SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
child: ListView.builder(
|
||||
itemCount: dests.length,
|
||||
itemBuilder: (ctx, index) {
|
||||
//print("--- photo ${dests[index].checkin_image!} ----");
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: CustomWidget(
|
||||
// 要検討:画像のサイズがハードコードされています。画像のサイズを動的に設定できるようにすることを検討してください。
|
||||
title: dests[index].name!,
|
||||
subtitle:
|
||||
"${dests[index].sub_loc_id} : ${dests[index].name}",
|
||||
image1: dests[index].checkin_image != null
|
||||
? Image.file(
|
||||
File(dests[index].checkin_image!))
|
||||
: null,
|
||||
image2:
|
||||
dests[index].buypoint_image != null
|
||||
? Image.file(File(
|
||||
dests[index].buypoint_image!))
|
||||
: null,
|
||||
),
|
||||
);
|
||||
}));
|
||||
} else {
|
||||
return Center(child: Text("No checkin yet"));
|
||||
return Center(child: Text("no_checkin_yet".tr));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(snapshot.connectionState == ConnectionState.waiting){
|
||||
return Center(child: CircularProgressIndicator(),);
|
||||
} else if (snapshot.connectionState ==
|
||||
ConnectionState.waiting) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
return Container();
|
||||
}),
|
||||
@ -63,3 +86,69 @@ class _HistoryPageState extends State<HistoryPage> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomWidget extends StatelessWidget {
|
||||
final Image? image1;
|
||||
final Image? image2;
|
||||
final String title;
|
||||
final String subtitle;
|
||||
|
||||
const CustomWidget({
|
||||
super.key,
|
||||
this.image1,
|
||||
this.image2,
|
||||
required this.title,
|
||||
required this.subtitle,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width:
|
||||
104, // 50 (width of each image) + 2 (space between images) + 2*1 (padding on both sides)
|
||||
child: Row(
|
||||
children: [
|
||||
if (image1 != null)
|
||||
SizedBox(
|
||||
width: 50,
|
||||
height: 100,
|
||||
child: image1,
|
||||
),
|
||||
if (image1 != null && image2 != null) const SizedBox(width: 2),
|
||||
if (image2 != null)
|
||||
SizedBox(
|
||||
width: 50,
|
||||
height: 100,
|
||||
child: image2,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style:
|
||||
const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
maxLines:
|
||||
null, // Allows the text to wrap onto an unlimited number of lines
|
||||
),
|
||||
Text(
|
||||
subtitle,
|
||||
style: const TextStyle(fontSize: 16),
|
||||
maxLines:
|
||||
null, // Allows the text to wrap onto an unlimited number of lines
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/home/home_controller.dart';
|
||||
import 'package:gifunavi/pages/home/home_controller.dart';
|
||||
|
||||
class HomeBinding extends Bindings{
|
||||
@override
|
||||
|
||||
@ -1,44 +1,82 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/search/search_page.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:gifunavi/routes/app_pages.dart';
|
||||
|
||||
class HomePage extends GetView{
|
||||
const HomePage({Key? key}) : super(key: key);
|
||||
class HomePage extends StatefulWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
_HomePageState createState() => _HomePageState();
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomePage> {
|
||||
bool _isLocationServiceEnabled = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
/*
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_checkLocationService(); // 非同期的に呼び出す
|
||||
});
|
||||
*/
|
||||
_checkLocationService();
|
||||
}
|
||||
|
||||
Future<void> _checkLocationService() async {
|
||||
final serviceEnabled = await Permission.location.serviceStatus.isEnabled;
|
||||
setState(() {
|
||||
_isLocationServiceEnabled = serviceEnabled;
|
||||
});
|
||||
}
|
||||
|
||||
void _showLocationDisabledDialog() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('location_disabled_title'.tr),
|
||||
content: Text('location_disabled_message'.tr),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('ok'.tr),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
TextButton(
|
||||
child: Text('open_settings'.tr),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
openAppSettings();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.white,
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text("app_title".tr,
|
||||
style: const TextStyle(
|
||||
color: Colors.blue
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
onTap: (){
|
||||
Navigator.push(context, MaterialPageRoute(builder: (context) => SearchPage()));
|
||||
},
|
||||
child: Container(
|
||||
height: 32,
|
||||
width: 75,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue,
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
|
||||
),
|
||||
child: const Center(child: Icon(Icons.search),),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
title: Text('home'.tr),
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('welcome'.tr),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: _isLocationServiceEnabled
|
||||
? () => Get.offNamed(AppPages.INDEX)
|
||||
: () => _showLocationDisabledDialog(),
|
||||
child: Text('start_app'.tr),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Container(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,17 +1,14 @@
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/index/index_controller.dart';
|
||||
import 'package:gifunavi/pages/destination/destination_controller.dart';
|
||||
import 'package:gifunavi/pages/index/index_controller.dart';
|
||||
import 'package:gifunavi/utils/location_controller.dart';
|
||||
|
||||
class IndexBinding extends Bindings {
|
||||
|
||||
IndexBinding(this.token);
|
||||
|
||||
String? token;
|
||||
|
||||
@override
|
||||
void dependencies() {
|
||||
final IndexController indexController = IndexController();
|
||||
indexController.userToken = token;
|
||||
Get.put<IndexController>(indexController);
|
||||
Get.lazyPut<IndexController>(() => IndexController());
|
||||
//Get.put<IndexController>(IndexController());
|
||||
Get.put<LocationController>(LocationController());
|
||||
Get.put<DestinationController>(DestinationController());
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,38 +1,147 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/destination/destination_controller.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/list_widget.dart';
|
||||
import 'package:rogapp/widgets/map_widget.dart';
|
||||
import 'package:gifunavi/pages/destination/destination_controller.dart';
|
||||
import 'package:gifunavi/pages/drawer/drawer_page.dart';
|
||||
import 'package:gifunavi/pages/index/index_controller.dart';
|
||||
import 'package:gifunavi/routes/app_pages.dart';
|
||||
import 'package:gifunavi/widgets/list_widget.dart';
|
||||
import 'package:gifunavi/widgets/map_widget.dart';
|
||||
import 'package:gifunavi/utils/location_controller.dart';
|
||||
|
||||
class IndexPage extends GetView<IndexController> {
|
||||
IndexPage({Key? key}) : super(key: key);
|
||||
// index_page.dartファイルの主な内容です。
|
||||
// このファイルは、アプリのメインページのUIを構築し、各機能へのナビゲーションを提供しています。
|
||||
// また、IndexControllerとDestinationControllerを使用して、状態管理と各種機能の実装を行っています。
|
||||
//
|
||||
// MapWidgetとListWidgetは、それぞれ別のファイルで定義されているウィジェットであり、マップモードとリストモードの表示を担当しています。
|
||||
//
|
||||
// 全体的に、index_page.dartはアプリのメインページの構造を定義し、他のコンポーネントやページへの橋渡しを行っているファイルです。
|
||||
//
|
||||
|
||||
// 要検討:GPSデータの表示アイコンをタップした際のエラーハンドリングを追加することをお勧めします。
|
||||
// MapWidgetとListWidgetの切り替えにObxを使用していますが、パフォーマンスを考慮して、必要な場合にのみウィジェットを再構築するようにしてください。
|
||||
// DestinationControllerのisSimulationModeを使用してGPS信号の強弱をシミュレーションしていますが、本番環境では適切に実際のGPS信号を使用するようにしてください。
|
||||
|
||||
// IndexPageクラスは、GetView<IndexController>を継承したStatelessWidgetです。このクラスは、アプリのメインページを表すウィジェットです。
|
||||
//
|
||||
|
||||
class IndexPage extends StatefulWidget {
|
||||
const IndexPage({super.key});
|
||||
|
||||
@override
|
||||
_IndexPageState createState() => _IndexPageState();
|
||||
}
|
||||
|
||||
class _IndexPageState extends State<IndexPage> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
//checkLoginAndShowDialog();
|
||||
});
|
||||
}
|
||||
|
||||
void checkLoginAndShowDialog() {
|
||||
if (indexController.currentUser.isEmpty) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('ログインが必要です'),
|
||||
content: const Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('1) ログインされていません。ロゲに参加するにはログインが必要です。'),
|
||||
SizedBox(height: 10),
|
||||
Text('2) ログイン後、個人情報入力、チーム登録、エントリー登録を行なってください。'),
|
||||
SizedBox(height: 10),
|
||||
Text('3) エントリー登録は場所と日にちごとに行なってください。'),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: const Text('キャンセル'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
ElevatedButton(
|
||||
child: const Text('ログイン'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
Get.toNamed(AppPages.LOGIN);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 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 WillPopScope(
|
||||
onWillPop: () async {
|
||||
indexController.switchPage(AppPages.INITIAL);
|
||||
return false;
|
||||
},
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
child: Scaffold(
|
||||
//
|
||||
// Scaffoldウィジェットを使用して、アプリのメインページのレイアウトを構築しています。
|
||||
//
|
||||
drawer: DrawerPage(),
|
||||
appBar: AppBar(
|
||||
// leading: IconButton(
|
||||
// icon: const Icon(Icons.arrow_back_ios),
|
||||
// onPressed: (){
|
||||
// indexController.switchPage(AppPages.TRAVEL);
|
||||
// },
|
||||
// ),
|
||||
//automaticallyImplyLeading: false,
|
||||
title: Text("add_location".tr),
|
||||
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)),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Get.toNamed(AppPages.HISTORY);
|
||||
},
|
||||
icon: const Icon(Icons.history)),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
final tk = indexController.currentUser[0]["token"];
|
||||
if (tk != null) {
|
||||
destinationController.fixMapBound(tk);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.refresh)),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Get.toNamed(AppPages.SEARCH);
|
||||
@ -41,6 +150,7 @@ class IndexPage extends GetView<IndexController> {
|
||||
height: 32,
|
||||
width: 75,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue,
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
),
|
||||
child: const Center(
|
||||
@ -48,125 +158,129 @@ class IndexPage extends GetView<IndexController> {
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(onPressed: () {
|
||||
Get.toNamed(AppPages.HISTORY);
|
||||
}, icon: const Icon(Icons.history))
|
||||
|
||||
//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.end,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(right: 10.0, top: 4.0, bottom: 4.0),
|
||||
child: InkWell(
|
||||
child:
|
||||
Obx(() => destinationController.is_gps_selected == 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);
|
||||
},
|
||||
),
|
||||
))),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 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);
|
||||
// },
|
||||
// ),
|
||||
// ))),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
|
||||
//
|
||||
// マップモードとリストモードを切り替えるためのボタンです。
|
||||
//
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () {
|
||||
indexController.toggleMode();
|
||||
if (indexController.currentCat.isNotEmpty) {
|
||||
print(indexController.currentCat[0].toString());
|
||||
}
|
||||
},
|
||||
tooltip: 'Increment',
|
||||
elevation: 4.0,
|
||||
elevation: 1.0,
|
||||
//
|
||||
// Obxウィジェットを使用して、indexController.mode.valueの値に基づいて、MapWidgetまたはListWidgetを表示しています。
|
||||
//
|
||||
child: Obx(
|
||||
() => indexController.mode == 0
|
||||
() => indexController.mode.value == 0
|
||||
? const Image(image: AssetImage('assets/images/list2.png'))
|
||||
: const Image(image: AssetImage('assets/images/map.png')),
|
||||
),
|
||||
),
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
|
||||
//
|
||||
// bodyには、SafeAreaウィジェットを使用して、画面の安全な領域内にUIを構築しています。
|
||||
//
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
// Container(
|
||||
// padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
// alignment: Alignment.centerLeft,
|
||||
// height: 50.0,
|
||||
// //child: SingleChildScrollView(
|
||||
// // scrollDirection: Axis.horizontal,
|
||||
// // child:Row(
|
||||
// // mainAxisAlignment: MainAxisAlignment.start,
|
||||
// // children: [
|
||||
// // TextButton(child:Text("Main Pef >", style: TextStyle(fontSize:16.0, fontWeight: FontWeight.bold),), onPressed: (){Get.toNamed(AppPages.MAINPERF);},),
|
||||
// // TextButton(child:Text("Sub Pef >", style: TextStyle(fontSize:16.0, fontWeight: FontWeight.bold),), onPressed: (){Get.toNamed(AppPages.SUBPERF);},),
|
||||
// // TextButton(child:Text("Cities >", style: TextStyle(fontSize:16.0, fontWeight: FontWeight.bold),), onPressed: (){Get.toNamed(AppPages.CITY);},),
|
||||
// // TextButton(child:Text("Categories", style: TextStyle(fontSize:16.0, fontWeight: FontWeight.bold),), onPressed: (){Get.toNamed(AppPages.CATEGORY);},),
|
||||
// // ],
|
||||
// // )
|
||||
// // ),
|
||||
// child: SingleChildScrollView(
|
||||
// scrollDirection: Axis.horizontal,
|
||||
// child: Obx(() =>
|
||||
// Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// children: [
|
||||
// indexController.is_mapController_loaded.value == false ?
|
||||
// Center(child: CircularProgressIndicator())
|
||||
// :
|
||||
// BreadCrumbWidget(mapController: indexController.mapController),
|
||||
// Container(width: 24.0,),
|
||||
// // Row(
|
||||
// // children: [
|
||||
// // indexController.currentCat.isNotEmpty ? Text(indexController.currentCat[0].toString()): Text(""),
|
||||
// // indexController.currentCat.isNotEmpty ?
|
||||
// // IconButton(
|
||||
// // onPressed: (){
|
||||
// // indexController.currentCat.clear();
|
||||
// // indexController.loadLocationsBound();
|
||||
// // },
|
||||
// // icon: Icon(Icons.cancel, color: Colors.red,)
|
||||
// // ) :
|
||||
// // Container(width: 0, height: 0,)
|
||||
// // ],
|
||||
// // )
|
||||
// ],
|
||||
// )
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
Expanded(
|
||||
child: Obx(
|
||||
() => indexController.mode == 0 ? MapWidget() : ListWidget(),
|
||||
() => indexController.mode.value == 0
|
||||
? const MapWidget()
|
||||
: const ListWidget(),
|
||||
))
|
||||
],
|
||||
),
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/routes/app_pages.dart';
|
||||
import 'package:gifunavi/routes/app_pages.dart';
|
||||
|
||||
// 要検討:ログインボタンとサインアップボタンの配色を見直すことを検討してください。現在の配色では、ボタンの役割がわかりにくい可能性があります。
|
||||
// ボタンのテキストをローカライズすることを検討してください。
|
||||
//
|
||||
class LandingPage extends StatefulWidget {
|
||||
const LandingPage({ Key? key }) : super(key: key);
|
||||
const LandingPage({ super.key });
|
||||
|
||||
@override
|
||||
State<LandingPage> createState() => _LandingPageState();
|
||||
|
||||
@ -1,16 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LoadingPage extends StatelessWidget {
|
||||
const LoadingPage({ Key? key }) : super(key: key);
|
||||
const LoadingPage({super.key});
|
||||
|
||||
// 要検討:ローディングインジケーターの値を固定値(0.8)にしていますが、実際のローディング進捗に合わせて動的に変更することを検討してください。
|
||||
//
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
alignment: Alignment.topCenter,
|
||||
margin: const EdgeInsets.only(top: 20),
|
||||
child: const CircularProgressIndicator(
|
||||
value: 0.8,
|
||||
)
|
||||
);
|
||||
alignment: Alignment.center,
|
||||
margin: const EdgeInsets.only(top: 20),
|
||||
child: const CircularProgressIndicator(
|
||||
value: 0.8,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,224 +1,398 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/index/index_controller.dart';
|
||||
import 'package:rogapp/routes/app_pages.dart';
|
||||
import 'package:gifunavi/pages/index/index_controller.dart';
|
||||
import 'package:gifunavi/routes/app_pages.dart';
|
||||
import 'package:gifunavi/widgets/helper_dialog.dart';
|
||||
import 'package:gifunavi/services/api_service.dart';
|
||||
|
||||
class LoginPage extends StatelessWidget {
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
// 要検討:ログインボタンとサインアップボタンの配色を見直すことを検討してください。現在の配色では、ボタンの役割がわかりにくい可能性があります。
|
||||
// エラーメッセージをローカライズすることを検討してください。
|
||||
// ログイン処理中にエラーが発生した場合のエラーハンドリングを追加することをお勧めします。
|
||||
//
|
||||
class LoginPage extends StatefulWidget {
|
||||
const LoginPage({super.key});
|
||||
|
||||
@override
|
||||
_LoginPageState createState() => _LoginPageState();
|
||||
}
|
||||
|
||||
class _LoginPageState extends State<LoginPage> {
|
||||
//class LoginPage extends StatelessWidget {
|
||||
final IndexController indexController = Get.find<IndexController>();
|
||||
final ApiService apiService = Get.find<ApiService>();
|
||||
|
||||
TextEditingController emailController = TextEditingController();
|
||||
TextEditingController passwordController = TextEditingController();
|
||||
bool _obscureText = true;
|
||||
String _version = ''; // バージョン情報を保持する変数
|
||||
|
||||
LoginPage({Key? key}) : super(key: key);
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showHelperDialog(
|
||||
'参加するにはユーザー登録が必要です。サインアップからユーザー登録してください。',
|
||||
'login_page'
|
||||
);
|
||||
});
|
||||
_getVersionInfo(); // バージョン情報を取得
|
||||
}
|
||||
|
||||
// バージョン情報を取得するメソッド
|
||||
Future<void> _getVersionInfo() async {
|
||||
final PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
setState(() {
|
||||
_version = packageInfo.version;
|
||||
});
|
||||
}
|
||||
|
||||
void _showResetPasswordDialog() {
|
||||
TextEditingController resetEmailController = TextEditingController();
|
||||
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
title: const Text('パスワードのリセット'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('パスワードをリセットするメールアドレスを入力してください。'),
|
||||
const SizedBox(height: 10),
|
||||
TextField(
|
||||
controller: resetEmailController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'メールアドレス',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: const Text('キャンセル'),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
ElevatedButton(
|
||||
child: const Text('リセット'),
|
||||
onPressed: () async {
|
||||
if (resetEmailController.text.isNotEmpty) {
|
||||
bool success = await apiService.resetPassword(resetEmailController.text);
|
||||
Get.back();
|
||||
if (success) {
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
title: const Text('パスワードリセット'),
|
||||
content: const Text('パスワードリセットメールを送信しました。メールのリンクからパスワードを設定してください。'),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: const Text('OK'),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
Get.snackbar('エラー', 'パスワードリセットに失敗しました。もう一度お試しください。',
|
||||
snackPosition: SnackPosition.BOTTOM);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
//LoginPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.white,
|
||||
leading:
|
||||
IconButton( onPressed: (){
|
||||
Navigator.pop(context);
|
||||
},icon:const Icon(Icons.arrow_back_ios,size: 20,color: Colors.black,)),
|
||||
),
|
||||
body:
|
||||
indexController.currentUser.isEmpty ?
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Container(
|
||||
height: MediaQuery.of(context).size.height/6,
|
||||
decoration: const BoxDecoration(
|
||||
image:DecorationImage(image: AssetImage('assets/images/login_image.jpg'))
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5,),
|
||||
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 40
|
||||
),
|
||||
child: Column(
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
body: GestureDetector(
|
||||
onTap: () => FocusScope.of(context).unfocus(),
|
||||
child: indexController.currentUser.isEmpty
|
||||
? SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
makeInput(label: "email".tr, controller: emailController),
|
||||
makeInput(label: "password".tr, controller: passwordController, obsureText: true),
|
||||
Container(
|
||||
height: MediaQuery.of(context).size.height / 6,
|
||||
decoration: const BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage(
|
||||
'assets/images/login_image.jpg'))),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
// バージョン情報を表示
|
||||
Text(
|
||||
'Version: $_version',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(top: 3,left: 3),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
),
|
||||
child: Obx((() =>
|
||||
indexController.is_loading == true ? MaterialButton(
|
||||
minWidth: double.infinity,
|
||||
height:60,
|
||||
onPressed: (){
|
||||
|
||||
},
|
||||
color: Colors.grey[400],
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(40)
|
||||
),
|
||||
child: const CircularProgressIndicator(),
|
||||
) :
|
||||
Column(
|
||||
children: [
|
||||
MaterialButton(
|
||||
minWidth: double.infinity,
|
||||
height:40,
|
||||
onPressed: (){
|
||||
if(emailController.text.isEmpty || passwordController.text.isEmpty){
|
||||
Get.snackbar(
|
||||
"no_values".tr,
|
||||
"email_and_password_required".tr,
|
||||
icon: const Icon(Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
|
||||
snackPosition: SnackPosition.TOP,
|
||||
duration: const Duration(milliseconds: 800),
|
||||
backgroundColor: Colors.yellow,
|
||||
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
||||
);
|
||||
return;
|
||||
}
|
||||
indexController.is_loading.value = true;
|
||||
indexController.login(emailController.text, passwordController.text, context);
|
||||
},
|
||||
color: Colors.indigoAccent[400],
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(40)
|
||||
),
|
||||
child: const Text("ログイン",style: TextStyle(
|
||||
fontWeight: FontWeight.w600,fontSize: 16,color: Colors.white70
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5.0,),
|
||||
MaterialButton(
|
||||
minWidth: double.infinity,
|
||||
height:40,
|
||||
onPressed: (){
|
||||
Get.toNamed(AppPages.REGISTER);
|
||||
},
|
||||
color: Colors.redAccent,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(40)
|
||||
),
|
||||
child: Text("sign_up".tr,style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,fontSize: 16,color: Colors.white70
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2.0,),
|
||||
MaterialButton(
|
||||
minWidth: double.infinity,
|
||||
height:40,
|
||||
onPressed: (){
|
||||
Get.back();
|
||||
},
|
||||
color: Colors.grey,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(40)
|
||||
),
|
||||
child: Text("cancel".tr,style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,fontSize: 16,color: Colors.white70
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
child: Column(
|
||||
children: [
|
||||
makeInput(
|
||||
label: "email".tr, controller: emailController),
|
||||
makePasswordInput(
|
||||
label: "password".tr,
|
||||
controller: passwordController,
|
||||
obscureText: _obscureText,
|
||||
onToggleVisibility: () {
|
||||
setState(() {
|
||||
_obscureText = !_obscureText;
|
||||
});
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
const SizedBox(height: 5,),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text("rogaining_user_need_tosign_up".tr, style: const TextStyle(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(top: 3, left: 3),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
),
|
||||
child: Obx(
|
||||
(() => indexController.isLoading.value == true
|
||||
? MaterialButton(
|
||||
minWidth: double.infinity,
|
||||
height: 60,
|
||||
onPressed: () {},
|
||||
color: Colors.grey[400],
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(40)),
|
||||
child: const CircularProgressIndicator(),
|
||||
)
|
||||
: Column(
|
||||
children: [
|
||||
MaterialButton(
|
||||
minWidth: double.infinity,
|
||||
height: 40,
|
||||
onPressed: () async {
|
||||
if (emailController.text.isEmpty ||
|
||||
passwordController
|
||||
.text.isEmpty) {
|
||||
Get.snackbar(
|
||||
"no_values".tr,
|
||||
"email_and_password_required"
|
||||
.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),
|
||||
);
|
||||
return;
|
||||
}
|
||||
indexController.isLoading.value =
|
||||
true;
|
||||
indexController.login(
|
||||
emailController.text,
|
||||
passwordController.text,
|
||||
context);
|
||||
},
|
||||
color: Colors.indigoAccent[400],
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(40)),
|
||||
child: Text(
|
||||
"login".tr,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
color: Colors.white70),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5.0,
|
||||
),
|
||||
MaterialButton(
|
||||
minWidth: double.infinity,
|
||||
height: 36,
|
||||
onPressed: () {
|
||||
Get.toNamed(AppPages.REGISTER);
|
||||
},
|
||||
color: Colors.redAccent,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(40)),
|
||||
child: Text(
|
||||
"sign_up".tr,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
color: Colors.white70),
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
)),
|
||||
const SizedBox(
|
||||
height: 3,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: _showResetPasswordDialog,
|
||||
child: Text(
|
||||
"forgot_password".tr,
|
||||
style: const TextStyle(color: Colors.blue),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text("app_developed_by_gifu_dx".tr, style: const TextStyle(
|
||||
overflow: TextOverflow.ellipsis, fontSize: 10.0
|
||||
),),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
"app_developed_by_gifu_dx".tr,
|
||||
style: const TextStyle(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
fontSize: 10.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
|
||||
),
|
||||
],
|
||||
),
|
||||
):
|
||||
Container(
|
||||
child: TextButton(
|
||||
onPressed: (){
|
||||
indexController.currentUser.clear();
|
||||
],
|
||||
),
|
||||
const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
"※第8回と第9回は、岐阜県の令和5年度「清流の国ぎふ」SDGs推進ネットワーク連携促進補助金を受けています",
|
||||
style: TextStyle(
|
||||
fontSize: 10.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: TextButton(
|
||||
onPressed: () {
|
||||
indexController.logout();
|
||||
Get.offAllNamed(AppPages.LOGIN);
|
||||
},
|
||||
child: const Text("Already Logged in, Click to logout"),
|
||||
),
|
||||
)
|
||||
,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget makeInput({label, required TextEditingController controller, obsureText = false}){
|
||||
Widget makePasswordInput({
|
||||
required String label,
|
||||
required TextEditingController controller,
|
||||
required bool obscureText,
|
||||
required VoidCallback onToggleVisibility,
|
||||
}) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label,style:const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Colors.black87
|
||||
),),
|
||||
const SizedBox(height: 5,),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 15, fontWeight: FontWeight.w400, color: Colors.black87),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
TextField(
|
||||
controller: controller,
|
||||
obscureText: obscureText,
|
||||
decoration: InputDecoration(
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(vertical: 0, horizontal: 10),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.grey[400]!),
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.grey[400]!),
|
||||
),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
obscureText ? Icons.visibility : Icons.visibility_off,
|
||||
color: Colors.grey,
|
||||
),
|
||||
onPressed: onToggleVisibility,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30.0)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeInput(
|
||||
{label, required TextEditingController controller, obsureText = false}) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 15, fontWeight: FontWeight.w400, color: Colors.black87),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
TextField(
|
||||
controller: controller,
|
||||
obscureText: obsureText,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 0,horizontal: 10),
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(vertical: 0, horizontal: 10),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: (Colors.grey[400])!,
|
||||
),
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: (Colors.grey[400])!
|
||||
borderSide: BorderSide(color: (Colors.grey[400])!),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30.0,)
|
||||
const SizedBox(
|
||||
height: 30.0,
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
381
lib/pages/login/login_page.dart_backup
Normal file
381
lib/pages/login/login_page.dart_backup
Normal file
@ -0,0 +1,381 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/index/index_controller.dart';
|
||||
import 'package:rogapp/routes/app_pages.dart';
|
||||
import 'package:rogapp/widgets/helper_dialog.dart';
|
||||
import 'package:rogapp/services/api_service.dart';
|
||||
|
||||
// 要検討:ログインボタンとサインアップボタンの配色を見直すことを検討してください。現在の配色では、ボタンの役割がわかりにくい可能性があります。
|
||||
// エラーメッセージをローカライズすることを検討してください。
|
||||
// ログイン処理中にエラーが発生した場合のエラーハンドリングを追加することをお勧めします。
|
||||
//
|
||||
class LoginPage extends StatefulWidget {
|
||||
@override
|
||||
_LoginPageState createState() => _LoginPageState();
|
||||
}
|
||||
|
||||
class _LoginPageState extends State<LoginPage> {
|
||||
//class LoginPage extends StatelessWidget {
|
||||
final IndexController indexController = Get.find<IndexController>();
|
||||
final ApiService apiService = Get.find<ApiService>();
|
||||
|
||||
TextEditingController emailController = TextEditingController();
|
||||
TextEditingController passwordController = TextEditingController();
|
||||
bool _obscureText = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showHelperDialog(
|
||||
'参加するにはユーザー登録が必要です。サインアップからユーザー登録してください。',
|
||||
'login_page'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void _showResetPasswordDialog() {
|
||||
TextEditingController resetEmailController = TextEditingController();
|
||||
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
title: Text('パスワードのリセット'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('パスワードをリセットするメールアドレスを入力してください。'),
|
||||
SizedBox(height: 10),
|
||||
TextField(
|
||||
controller: resetEmailController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'メールアドレス',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('キャンセル'),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
ElevatedButton(
|
||||
child: Text('リセット'),
|
||||
onPressed: () async {
|
||||
if (resetEmailController.text.isNotEmpty) {
|
||||
bool success = await apiService.resetPassword(resetEmailController.text);
|
||||
Get.back();
|
||||
if (success) {
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
title: Text('パスワードリセット'),
|
||||
content: Text('パスワードリセットメールを送信しました。メールのリンクからパスワードを設定してください。'),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('OK'),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
Get.snackbar('エラー', 'パスワードリセットに失敗しました。もう一度お試しください。',
|
||||
snackPosition: SnackPosition.BOTTOM);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
//LoginPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.white,
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
body: indexController.currentUser.isEmpty
|
||||
? SizedBox(
|
||||
width: double.infinity,
|
||||
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Container(
|
||||
height: MediaQuery.of(context).size.height / 6,
|
||||
decoration: const BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage(
|
||||
'assets/images/login_image.jpg'))),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
child: Column(
|
||||
children: [
|
||||
makeInput(
|
||||
label: "email".tr, controller: emailController),
|
||||
makePasswordInput(
|
||||
label: "password".tr,
|
||||
controller: passwordController,
|
||||
obscureText: _obscureText,
|
||||
onToggleVisibility: () {
|
||||
setState(() {
|
||||
_obscureText = !_obscureText;
|
||||
});
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(top: 3, left: 3),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
),
|
||||
child: Obx(
|
||||
(() => indexController.isLoading.value == true
|
||||
? MaterialButton(
|
||||
minWidth: double.infinity,
|
||||
height: 60,
|
||||
onPressed: () {},
|
||||
color: Colors.grey[400],
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(40)),
|
||||
child: const CircularProgressIndicator(),
|
||||
)
|
||||
: Column(
|
||||
children: [
|
||||
MaterialButton(
|
||||
minWidth: double.infinity,
|
||||
height: 40,
|
||||
onPressed: () async {
|
||||
if (emailController.text.isEmpty ||
|
||||
passwordController
|
||||
.text.isEmpty) {
|
||||
Get.snackbar(
|
||||
"no_values".tr,
|
||||
"email_and_password_required"
|
||||
.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),
|
||||
// backgroundColor: Colors.yellow,
|
||||
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
||||
);
|
||||
return;
|
||||
}
|
||||
indexController.isLoading.value =
|
||||
true;
|
||||
indexController.login(
|
||||
emailController.text,
|
||||
passwordController.text,
|
||||
context);
|
||||
},
|
||||
color: Colors.indigoAccent[400],
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(40)),
|
||||
child: Text(
|
||||
"login".tr,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
color: Colors.white70),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5.0,
|
||||
),
|
||||
MaterialButton(
|
||||
minWidth: double.infinity,
|
||||
height: 36,
|
||||
onPressed: () {
|
||||
Get.toNamed(AppPages.REGISTER);
|
||||
},
|
||||
color: Colors.redAccent,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(40)),
|
||||
child: Text(
|
||||
"sign_up".tr,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
color: Colors.white70),
|
||||
),
|
||||
),
|
||||
|
||||
],
|
||||
)),
|
||||
),
|
||||
)),
|
||||
const SizedBox(
|
||||
height: 3,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: _showResetPasswordDialog,
|
||||
child: Text(
|
||||
"forgot_password".tr,
|
||||
style: TextStyle(color: Colors.blue),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
"app_developed_by_gifu_dx".tr,
|
||||
style: const TextStyle(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
fontSize: 10.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
"※第8回と第9回は、岐阜県の令和5年度「清流の国ぎふ」SDGs推進ネットワーク連携促進補助金を受けています",
|
||||
style: TextStyle(
|
||||
//overflow: TextOverflow.ellipsis,
|
||||
fontSize:
|
||||
10.0, // Consider adjusting the font size if the text is too small.
|
||||
// Removed overflow: TextOverflow.ellipsis to allow text wrapping.
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
)
|
||||
: TextButton(
|
||||
onPressed: () {
|
||||
indexController.logout();
|
||||
Get.offAllNamed(AppPages.LOGIN);
|
||||
},
|
||||
child: const Text("Already Logged in, Click to logout"),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget makePasswordInput({
|
||||
required String label,
|
||||
required TextEditingController controller,
|
||||
required bool obscureText,
|
||||
required VoidCallback onToggleVisibility,
|
||||
}) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 15, fontWeight: FontWeight.w400, color: Colors.black87),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
TextField(
|
||||
controller: controller,
|
||||
obscureText: obscureText,
|
||||
decoration: InputDecoration(
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(vertical: 0, horizontal: 10),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.grey[400]!),
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.grey[400]!),
|
||||
),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
obscureText ? Icons.visibility : Icons.visibility_off,
|
||||
color: Colors.grey,
|
||||
),
|
||||
onPressed: onToggleVisibility,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30.0)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeInput(
|
||||
{label, required TextEditingController controller, obsureText = false}) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 15, fontWeight: FontWeight.w400, color: Colors.black87),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
TextField(
|
||||
controller: controller,
|
||||
obscureText: obsureText,
|
||||
decoration: InputDecoration(
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(vertical: 0, horizontal: 10),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: (Colors.grey[400])!,
|
||||
),
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: (Colors.grey[400])!),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 30.0,
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -1,10 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/index/index_controller.dart';
|
||||
import 'package:rogapp/routes/app_pages.dart';
|
||||
import 'package:gifunavi/pages/index/index_controller.dart';
|
||||
import 'package:gifunavi/routes/app_pages.dart';
|
||||
|
||||
// 要検討:ログインボタンとサインアップボタンの配色を見直すことを検討してください。現在の配色では、ボタンの役割がわかりにくい可能性があります。
|
||||
// エラーメッセージをローカライズすることを検討してください。
|
||||
// ポップアップを閉じるボタンを追加することを検討してください。
|
||||
//
|
||||
class LoginPopupPage extends StatelessWidget {
|
||||
LoginPopupPage({Key? key}) : super(key: key);
|
||||
LoginPopupPage({super.key});
|
||||
|
||||
final IndexController indexController = Get.find<IndexController>();
|
||||
|
||||
@ -15,195 +19,235 @@ class LoginPopupPage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.white,
|
||||
leading:
|
||||
IconButton( onPressed: (){
|
||||
Navigator.pop(context);
|
||||
},icon:const Icon(Icons.arrow_back_ios,size: 20,color: Colors.black,)),
|
||||
),
|
||||
body:
|
||||
indexController.currentUser.isEmpty ?
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Container(
|
||||
height: MediaQuery.of(context).size.height/5,
|
||||
decoration: const BoxDecoration(
|
||||
image:DecorationImage(image: AssetImage('assets/images/login_image.jpg'))
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5,),
|
||||
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 40
|
||||
),
|
||||
child: Column(
|
||||
leading: IconButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.arrow_back_ios,
|
||||
size: 20,
|
||||
color: Colors.black,
|
||||
)),
|
||||
),
|
||||
body: indexController.currentUser.isEmpty
|
||||
? SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
makeInput(label: "email".tr, controller: emailController),
|
||||
makeInput(label: "password".tr, controller: passwordController, obsureText: true),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(top: 3,left: 3),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
),
|
||||
child: Obx((() =>
|
||||
indexController.is_loading == true ? MaterialButton(
|
||||
minWidth: double.infinity,
|
||||
height:60,
|
||||
onPressed: (){
|
||||
|
||||
},
|
||||
color: Colors.grey[400],
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(40)
|
||||
),
|
||||
child: const CircularProgressIndicator(),
|
||||
) :
|
||||
Column(
|
||||
children: [
|
||||
MaterialButton(
|
||||
minWidth: double.infinity,
|
||||
height:60,
|
||||
onPressed: (){
|
||||
if(emailController.text.isEmpty || passwordController.text.isEmpty){
|
||||
Get.snackbar(
|
||||
"no_values".tr,
|
||||
"email_and_password_required".tr,
|
||||
icon: const Icon(Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
|
||||
snackPosition: SnackPosition.TOP,
|
||||
duration: const Duration(milliseconds: 800),
|
||||
backgroundColor: Colors.yellow,
|
||||
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
||||
);
|
||||
return;
|
||||
}
|
||||
indexController.is_loading.value = true;
|
||||
indexController.login(emailController.text, passwordController.text, context);
|
||||
},
|
||||
color: Colors.indigoAccent[400],
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(40)
|
||||
),
|
||||
child: const Text("ログイン",style: TextStyle(
|
||||
fontWeight: FontWeight.w600,fontSize: 16,color: Colors.white70
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 19.0,),
|
||||
MaterialButton(
|
||||
minWidth: double.infinity,
|
||||
height:50,
|
||||
onPressed: (){
|
||||
Get.toNamed(AppPages.REGISTER);
|
||||
},
|
||||
color: Colors.redAccent,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(40)
|
||||
),
|
||||
child: Text("sign_up".tr,style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,fontSize: 16,color: Colors.white70
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 19.0,),
|
||||
MaterialButton(
|
||||
minWidth: double.infinity,
|
||||
height:50,
|
||||
onPressed: (){
|
||||
Get.toNamed(AppPages.TRAVEL);
|
||||
},
|
||||
color: Colors.grey,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(40)
|
||||
),
|
||||
child: Text("cancel".tr,style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,fontSize: 16,color: Colors.white70
|
||||
),
|
||||
),
|
||||
)
|
||||
Container(
|
||||
height: MediaQuery.of(context).size.height / 5,
|
||||
decoration: const BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage(
|
||||
'assets/images/login_image.jpg'))),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
const SizedBox(height: 20,),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text("rogaining_user_need_tosign_up".tr, style: const TextStyle(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
child: Column(
|
||||
children: [
|
||||
makeInput(
|
||||
label: "email".tr, controller: emailController),
|
||||
makeInput(
|
||||
label: "password".tr,
|
||||
controller: passwordController,
|
||||
obsureText: true),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(top: 3, left: 3),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
),
|
||||
child: Obx(
|
||||
(() => indexController.isLoading.value == true
|
||||
? MaterialButton(
|
||||
minWidth: double.infinity,
|
||||
height: 60,
|
||||
onPressed: () {},
|
||||
color: Colors.grey[400],
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(40)),
|
||||
child: const CircularProgressIndicator(),
|
||||
)
|
||||
: Column(
|
||||
children: [
|
||||
MaterialButton(
|
||||
minWidth: double.infinity,
|
||||
height: 60,
|
||||
onPressed: () {
|
||||
if (emailController.text.isEmpty ||
|
||||
passwordController
|
||||
.text.isEmpty) {
|
||||
Get.snackbar(
|
||||
"no_values".tr,
|
||||
"email_and_password_required"
|
||||
.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(
|
||||
milliseconds: 800),
|
||||
//backgroundColor: Colors.yellow,
|
||||
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
||||
);
|
||||
return;
|
||||
}
|
||||
indexController.isLoading.value =
|
||||
true;
|
||||
indexController.login(
|
||||
emailController.text,
|
||||
passwordController.text,
|
||||
context);
|
||||
},
|
||||
color: Colors.indigoAccent[400],
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(40)),
|
||||
child: const Text(
|
||||
"ログイン",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
color: Colors.white70),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 19.0,
|
||||
),
|
||||
MaterialButton(
|
||||
minWidth: double.infinity,
|
||||
height: 50,
|
||||
onPressed: () {
|
||||
Get.toNamed(AppPages.REGISTER);
|
||||
},
|
||||
color: Colors.redAccent,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(40)),
|
||||
child: Text(
|
||||
"sign_up".tr,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
color: Colors.white70),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 19.0,
|
||||
),
|
||||
MaterialButton(
|
||||
minWidth: double.infinity,
|
||||
height: 50,
|
||||
onPressed: () {
|
||||
Get.toNamed(AppPages.TRAVEL);
|
||||
},
|
||||
color: Colors.grey,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(40)),
|
||||
child: Text(
|
||||
"cancel".tr,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
color: Colors.white70),
|
||||
),
|
||||
)
|
||||
],
|
||||
)),
|
||||
),
|
||||
)),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
"rogaining_user_need_tosign_up".tr,
|
||||
style: const TextStyle(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: TextButton(
|
||||
onPressed: () {
|
||||
indexController.logout();
|
||||
Get.offAllNamed(AppPages.LOGIN);
|
||||
},
|
||||
child: const Text("Already Logged in, Click to logout"),
|
||||
),
|
||||
],
|
||||
),
|
||||
):
|
||||
Container(
|
||||
child: TextButton(
|
||||
onPressed: (){
|
||||
indexController.currentUser.clear();
|
||||
},
|
||||
child: const Text("Already Logged in, Click to logout"),
|
||||
),
|
||||
)
|
||||
,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget makeInput({label, required TextEditingController controller, obsureText = false}){
|
||||
Widget makeInput(
|
||||
{label, required TextEditingController controller, obsureText = false}) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label,style:const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Colors.black87
|
||||
),),
|
||||
const SizedBox(height: 5,),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 15, fontWeight: FontWeight.w400, color: Colors.black87),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
TextField(
|
||||
controller: controller,
|
||||
obscureText: obsureText,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 0,horizontal: 10),
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(vertical: 0, horizontal: 10),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: (Colors.grey[400])!,
|
||||
),
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: (Colors.grey[400])!
|
||||
borderSide: BorderSide(color: (Colors.grey[400])!),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30.0,)
|
||||
const SizedBox(
|
||||
height: 30.0,
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,31 +1,31 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/index/index_controller.dart';
|
||||
// import 'package:flutter/material.dart';
|
||||
// import 'package:get/get.dart';
|
||||
// import 'package:rogapp/pages/index/index_controller.dart';
|
||||
|
||||
class MainPerfPage extends StatelessWidget {
|
||||
MainPerfPage({Key? key}) : super(key: key);
|
||||
// class MainPerfPage extends StatelessWidget {
|
||||
// MainPerfPage({Key? key}) : super(key: key);
|
||||
|
||||
IndexController indexController = Get.find<IndexController>();
|
||||
// IndexController indexController = Get.find<IndexController>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Select Main Perfecture"),
|
||||
),
|
||||
body: ListView.builder(
|
||||
itemCount: indexController.perfectures.length,
|
||||
itemBuilder: (context, index){
|
||||
return ListTile(
|
||||
onTap: (){
|
||||
indexController.dropdownValue = indexController.perfectures[index][0]["id"].toString();
|
||||
indexController.populateForPerf(indexController.dropdownValue, indexController.mapController);
|
||||
Get.back();
|
||||
},
|
||||
title: Text(indexController.perfectures[index][0]["adm1_ja"].toString()),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return Scaffold(
|
||||
// appBar: AppBar(
|
||||
// title: const Text("Select Main Perfecture"),
|
||||
// ),
|
||||
// body: ListView.builder(
|
||||
// itemCount: indexController.perfectures.length,
|
||||
// itemBuilder: (context, index){
|
||||
// return ListTile(
|
||||
// onTap: (){
|
||||
// indexController.dropdownValue = indexController.perfectures[index][0]["id"].toString();
|
||||
// indexController.populateForPerf(indexController.dropdownValue, indexController.mapController);
|
||||
// Get.back();
|
||||
// },
|
||||
// title: Text(indexController.perfectures[index][0]["adm1_ja"].toString()),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
@ -1,154 +1,311 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:rogapp/routes/app_pages.dart';
|
||||
import 'dart:async';
|
||||
|
||||
class PermissionHandlerScreen extends StatefulWidget {
|
||||
const PermissionHandlerScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<PermissionHandlerScreen> createState() => _PermissionHandlerScreenState();
|
||||
}
|
||||
class PermissionController {
|
||||
|
||||
class _PermissionHandlerScreenState extends State<PermissionHandlerScreen> {
|
||||
|
||||
static bool _isRequestingPermission = false;
|
||||
static Completer<bool>? _permissionCompleter;
|
||||
|
||||
Future<void> _showMyDialog() async {
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false, // user must tap button!
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('ロケーション許可'),
|
||||
content: const SingleChildScrollView(
|
||||
child: ListBody(
|
||||
children: <Widget>[
|
||||
Text( 'このアプリでは、位置情報の収集を行います。'),
|
||||
Text( 'このアプリでは、開始時点で位置情報を収集します。'),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: const Text('わかった'),
|
||||
onPressed: () {
|
||||
//Navigator.of(context).pop();
|
||||
Get.toNamed(AppPages.TRAVEL);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
static Future<bool> checkLocationPermissions() async {
|
||||
final locationPermission = await Permission.location.status;
|
||||
final whenInUsePermission = await Permission.locationWhenInUse.status;
|
||||
final alwaysPermission = await Permission.locationAlways.status;
|
||||
|
||||
return locationPermission == PermissionStatus.granted &&
|
||||
(whenInUsePermission == PermissionStatus.granted || alwaysPermission == PermissionStatus.granted);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
// TODO: implement initState
|
||||
super.initState();
|
||||
|
||||
//permissionServiceCall();
|
||||
}
|
||||
static Future<bool> checkAndRequestPermissions() async {
|
||||
if (_isRequestingPermission) {
|
||||
return _permissionCompleter!.future;
|
||||
}
|
||||
|
||||
Future<PermissionStatus> checkLocationPermission() async {
|
||||
return await Permission.location.status;
|
||||
}
|
||||
_isRequestingPermission = true;
|
||||
_permissionCompleter = Completer<bool>();
|
||||
|
||||
permissionServiceCall() async {
|
||||
await permissionServices().then(
|
||||
(value) {
|
||||
if (value[Permission.location]!.isGranted ) {
|
||||
/* ========= New Screen Added ============= */
|
||||
|
||||
Get.toNamed(AppPages.TRAVEL);
|
||||
|
||||
// Navigator.pushReplacement(
|
||||
// context,
|
||||
// MaterialPageRoute(builder: (context) => SplashScreen()),
|
||||
// );
|
||||
bool hasPermissions = await checkLocationPermissions();
|
||||
if (!hasPermissions) {
|
||||
bool userAgreed = await showLocationDisclosure();
|
||||
if (userAgreed) {
|
||||
try {
|
||||
await requestAllLocationPermissions();
|
||||
hasPermissions = await checkLocationPermissions();
|
||||
} catch (e) {
|
||||
print('Error requesting location permissions: $e');
|
||||
hasPermissions = false;
|
||||
}
|
||||
else{
|
||||
_showMyDialog();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/*Permission services*/
|
||||
Future<Map<Permission, PermissionStatus>> permissionServices() async {
|
||||
// You can request multiple permissions at once.
|
||||
Map<Permission, PermissionStatus> statuses = await [
|
||||
Permission.location,
|
||||
|
||||
//add more permission to request here.
|
||||
].request();
|
||||
|
||||
if (statuses[Permission.location]!.isPermanentlyDenied) {
|
||||
await openAppSettings().then(
|
||||
(value) async {
|
||||
if (value) {
|
||||
if (await Permission.location.status.isPermanentlyDenied == true &&
|
||||
await Permission.location.status.isGranted == false) {
|
||||
// openAppSettings();
|
||||
permissionServiceCall(); /* opens app settings until permission is granted */
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
if (statuses[Permission.location]!.isDenied) {
|
||||
permissionServiceCall();
|
||||
} else {
|
||||
print('User did not agree to location usage');
|
||||
hasPermissions = false;
|
||||
// アプリを終了
|
||||
SystemNavigator.pop();
|
||||
}
|
||||
}
|
||||
|
||||
/*{Permission.camera: PermissionStatus.granted, Permission.storage: PermissionStatus.granted}*/
|
||||
return statuses;
|
||||
|
||||
_isRequestingPermission = false;
|
||||
_permissionCompleter!.complete(hasPermissions);
|
||||
return _permissionCompleter!.future;
|
||||
}
|
||||
|
||||
static Future<void> requestAllLocationPermissions() async {
|
||||
await Permission.location.request();
|
||||
await Permission.locationWhenInUse.request();
|
||||
await Permission.locationAlways.request();
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var status = Permission.location.status.then((value){
|
||||
if(value.isGranted == false){
|
||||
Future.delayed(Duration.zero, () => showAlert(context));
|
||||
if (await Permission.locationAlways.isGranted) {
|
||||
const platform = MethodChannel('location');
|
||||
try {
|
||||
await platform.invokeMethod('startLocationService');
|
||||
} on PlatformException catch (e) {
|
||||
debugPrint("Failed to start location service: '${e.message}'.");
|
||||
}
|
||||
else {
|
||||
Get.toNamed(AppPages.TRAVEL);
|
||||
}
|
||||
});
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
child: const Text(""),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void showAlert(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) => AlertDialog(
|
||||
title: const Text('ロケーション許可'),
|
||||
content: const SingleChildScrollView(
|
||||
child: ListBody(
|
||||
children: <Widget>[
|
||||
Text( 'このアプリでは、位置情報の収集を行います。'),
|
||||
Text('岐阜ナビアプリではチェックポイントの自動チェックインの機能を可能にするために、現在地のデータが収集されます。アプリを閉じている時や、使用していないときにも収集されます。位置情報は、個人を特定できない統計的な情報として、ユーザーの個人情報とは一切結びつかない形で送信されます。お知らせの配信、位置情報の利用を許可しない場合は、この後表示されるダイアログで「許可しない」を選択してください。'),
|
||||
],
|
||||
),
|
||||
static Future<bool> showLocationDisclosure() async {
|
||||
return await Get.dialog<bool>(
|
||||
AlertDialog(
|
||||
title: const Text('位置情報の使用について'),
|
||||
content: const SingleChildScrollView(
|
||||
child: ListBody(
|
||||
children: <Widget>[
|
||||
Text('このアプリでは、以下の目的で位置情報を使用します:'),
|
||||
Text('• チェックポイントの自動チェックイン(アプリが閉じているときも含む)'),
|
||||
Text('• 移動履歴の記録(バックグラウンドでも継続)'),
|
||||
Text('• 現在地周辺の情報表示'),
|
||||
Text('\nバックグラウンドでも位置情報を継続的に取得します。'),
|
||||
Text('これにより、バッテリーの消費が増加する可能性があります。'),
|
||||
Text('同意しない場合には、アプリは終了します。'),
|
||||
],
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: const Text('わかった'),
|
||||
onPressed: () {
|
||||
permissionServiceCall();
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: const Text('同意しない'),
|
||||
onPressed: () => Get.back(result: false),
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('同意する'),
|
||||
onPressed: () => Get.back(result: true),
|
||||
),
|
||||
],
|
||||
),
|
||||
barrierDismissible: false,
|
||||
) ?? false;
|
||||
}
|
||||
|
||||
static void showPermissionDeniedDialog(String title,String message) {
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
//title: Text('location_permission_needed_title'.tr),
|
||||
title: Text(title.tr),
|
||||
// 位置情報への許可が必要です
|
||||
//content: Text('location_permission_needed_main'.tr),
|
||||
content: Text(message.tr),
|
||||
// 岐阜ロゲでは、位置情報を使用してスタート・チェックイン・ゴール等の通過照明及び移動手段の記録のために、位置情報のトラッキングを行なっています。このためバックグラウンドでもトラッキングができるように位置情報の権限が必要です。
|
||||
// 設定画面で、「岐阜ナビ」に対して、常に位置情報を許可するように設定してください。
|
||||
actions: [
|
||||
TextButton(
|
||||
child: const Text('キャンセル'),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('設定'),
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
openAppSettings();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
static Future<bool> requestLocationPermissions(BuildContext context) async {
|
||||
if (_isRequestingPermission) {
|
||||
// If a request is already in progress, wait for it to complete
|
||||
return _permissionCompleter!.future;
|
||||
}
|
||||
|
||||
_isRequestingPermission = true;
|
||||
_permissionCompleter = Completer<bool>();
|
||||
|
||||
bool userAgreed = await showLocationDisclosure(context);
|
||||
if (userAgreed) {
|
||||
try {
|
||||
final locationStatus = await Permission.location.request();
|
||||
final whenInUseStatus = await Permission.locationWhenInUse.request();
|
||||
final alwaysStatus = await Permission.locationAlways.request();
|
||||
|
||||
if (locationStatus == PermissionStatus.granted &&
|
||||
(whenInUseStatus == PermissionStatus.granted || alwaysStatus == PermissionStatus.granted)) {
|
||||
_permissionCompleter!.complete(true);
|
||||
} else {
|
||||
showPermissionDeniedDialog('location_permission_needed_title', 'location_permission_needed_main');
|
||||
_permissionCompleter!.complete(false);
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error requesting location permission: $e');
|
||||
_permissionCompleter!.complete(false);
|
||||
}
|
||||
} else {
|
||||
print('User did not agree to location usage');
|
||||
_permissionCompleter!.complete(false);
|
||||
// Exit the app
|
||||
SystemNavigator.pop();
|
||||
}
|
||||
|
||||
_isRequestingPermission = false;
|
||||
return _permissionCompleter!.future;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
static Future<bool> checkStoragePermission() async {
|
||||
//debugPrint("(gifunavi)== checkStoragePermission ==");
|
||||
final storagePermission = await Permission.storage.status;
|
||||
return storagePermission == PermissionStatus.granted;
|
||||
}
|
||||
|
||||
static Future<void> requestStoragePermission() async {
|
||||
//debugPrint("(gifunavi)== requestStoragePermission ==");
|
||||
final storagePermission = await Permission.storage.request();
|
||||
|
||||
if (storagePermission == PermissionStatus.granted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (storagePermission == PermissionStatus.permanentlyDenied) {
|
||||
// リクエストが完了するまで待機
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
showPermissionDeniedDialog('storage_permission_needed_title','storage_permission_needed_main');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
static Future<bool> checkLocationBasicPermission() async {
|
||||
//debugPrint("(gifunavi)== checkLocationBasicPermission ==");
|
||||
final locationPermission = await Permission.location.status;
|
||||
return locationPermission == PermissionStatus.granted;
|
||||
}
|
||||
|
||||
static Future<bool> checkLocationWhenInUsePermission() async {
|
||||
//debugPrint("(gifunavi)== checkLocationWhenInUsePermission ==");
|
||||
final whenInUsePermission = await Permission.locationWhenInUse.status;
|
||||
return whenInUsePermission == PermissionStatus.granted;
|
||||
}
|
||||
|
||||
static Future<bool> checkLocationAlwaysPermission() async {
|
||||
//debugPrint("(gifunavi)== checkLocationAlwaysPermission ==");
|
||||
final alwaysPermission = await Permission.locationAlways.status;
|
||||
return alwaysPermission == PermissionStatus.granted;
|
||||
}
|
||||
|
||||
static bool isBasicPermission=false;
|
||||
static Future<void> requestLocationBasicPermissions() async {
|
||||
//debugPrint("(gifunavi)== requestLocationBasicPermissions ==");
|
||||
try{
|
||||
if(!isBasicPermission){
|
||||
isBasicPermission=true;
|
||||
final locationStatus = await Permission.location.request();
|
||||
|
||||
if (locationStatus != PermissionStatus.granted) {
|
||||
showPermissionDeniedDialog('location_permission_needed_title','location_permission_needed_main');
|
||||
}
|
||||
}
|
||||
}catch (e, stackTrace){
|
||||
print('Exception: $e');
|
||||
print('Stack trace: $stackTrace');
|
||||
debugPrintStack(label: 'Exception occurred', stackTrace: stackTrace);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static bool isLocationServiceRunning = false;
|
||||
static bool isRequestedWhenInUsePermission = false;
|
||||
|
||||
static Future<void> requestLocationWhenInUsePermissions() async {
|
||||
//debugPrint("(gifunavi)== requestLocationWhenInUsePermissions ==");
|
||||
|
||||
try{
|
||||
if(!isRequestedWhenInUsePermission){
|
||||
isRequestedWhenInUsePermission=true;
|
||||
final whenInUseStatus = await Permission.locationWhenInUse.request();
|
||||
|
||||
if (whenInUseStatus != PermissionStatus.granted) {
|
||||
showPermissionDeniedDialog('location_permission_needed_title','location_permission_needed_main');
|
||||
}else{
|
||||
if( !isLocationServiceRunning ){
|
||||
isLocationServiceRunning=true;
|
||||
const platform = MethodChannel('location');
|
||||
try {
|
||||
await platform.invokeMethod('startLocationService'); // Location Service を開始する。
|
||||
} on PlatformException catch (e) {
|
||||
debugPrint("Failed to start location service: '${e.message}'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}catch (e, stackTrace){
|
||||
debugPrint('Exception: $e');
|
||||
debugPrint('Stack trace: $stackTrace');
|
||||
debugPrintStack(label: 'Exception occurred', stackTrace: stackTrace);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static bool isRequestedAlwaysPermission = false;
|
||||
|
||||
static Future<void> requestLocationAlwaysPermissions() async {
|
||||
//debugPrint("(gifunavi)== requestLocationAlwaysPermissions ==");
|
||||
|
||||
try {
|
||||
if( !isRequestedAlwaysPermission ){
|
||||
isRequestedAlwaysPermission=true;
|
||||
final alwaysStatus = await Permission.locationAlways.request();
|
||||
|
||||
if (alwaysStatus != PermissionStatus.granted) {
|
||||
showPermissionDeniedDialog('location_permission_needed_title','location_permission_needed_main');
|
||||
}
|
||||
}
|
||||
}catch (e, stackTrace){
|
||||
print('Exception: $e');
|
||||
print('Stack trace: $stackTrace');
|
||||
debugPrintStack(label: 'Exception occurred', stackTrace: stackTrace);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static Future<void> checkAndRequestPermissions() async {
|
||||
final hasPermissions = await checkLocationBasicPermission();
|
||||
if (!hasPermissions) {
|
||||
await requestLocationBasicPermissions();
|
||||
}
|
||||
|
||||
final hasWIUPermissions = await checkLocationWhenInUsePermission();
|
||||
if (!hasWIUPermissions) {
|
||||
await requestLocationWhenInUsePermissions();
|
||||
}
|
||||
|
||||
final hasAlwaysPermissions = await checkLocationAlwaysPermission();
|
||||
if (!hasAlwaysPermissions) {
|
||||
await requestLocationAlwaysPermissions();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ProgressPage extends StatelessWidget {
|
||||
const ProgressPage({Key? key}) : super(key: key);
|
||||
const ProgressPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@ -1,143 +1,114 @@
|
||||
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/index/index_controller.dart';
|
||||
import 'package:rogapp/routes/app_pages.dart';
|
||||
import 'package:gifunavi/pages/index/index_controller.dart';
|
||||
import 'package:gifunavi/routes/app_pages.dart';
|
||||
import 'package:gifunavi/widgets/helper_dialog.dart';
|
||||
|
||||
class RegisterPage extends StatelessWidget {
|
||||
class RegisterPage extends StatefulWidget {
|
||||
const RegisterPage({super.key});
|
||||
|
||||
@override
|
||||
_RegisterPageState createState() => _RegisterPageState();
|
||||
}
|
||||
|
||||
class _RegisterPageState extends State<RegisterPage> {
|
||||
final IndexController indexController = Get.find<IndexController>();
|
||||
|
||||
TextEditingController emailController = TextEditingController();
|
||||
TextEditingController passwordController = TextEditingController();
|
||||
TextEditingController confirmPasswordController = TextEditingController();
|
||||
final TextEditingController emailController = TextEditingController();
|
||||
final TextEditingController passwordController = TextEditingController();
|
||||
final TextEditingController confirmPasswordController = TextEditingController();
|
||||
|
||||
RegisterPage({Key? key}) : super(key: key);
|
||||
bool _obscurePassword = true;
|
||||
bool _obscureConfirmPassword = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showHelperDialog(
|
||||
'登録メールにアクティベーションメールが送信されます。メールにあるリンクをタップすると正式登録になります。',
|
||||
'register_page'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
resizeToAvoidBottomInset: true,
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.white,
|
||||
leading:
|
||||
IconButton( onPressed: (){
|
||||
Navigator.pop(context);
|
||||
},icon:const Icon(Icons.arrow_back_ios,size: 20,color: Colors.black,)),
|
||||
leading: IconButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: const Icon(Icons.arrow_back_ios, size: 20, color: Colors.black),
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
child: SizedBox(
|
||||
child: Container(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
const Text ("サインアップ", style: TextStyle(
|
||||
fontSize: 30,
|
||||
fontWeight: FontWeight.bold,
|
||||
),),
|
||||
const SizedBox(height: 20,),
|
||||
Text("アカウントを作成し、無料です",style: TextStyle(
|
||||
fontSize: 15,
|
||||
color: Colors.grey[700],
|
||||
),),
|
||||
const SizedBox(height: 30,)
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 40
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
makeInput(label: "Eメール", controller: emailController),
|
||||
makeInput(label: "パスワード", controller: passwordController,obsureText: true),
|
||||
makeInput(label: "パスワードを認証する", controller: confirmPasswordController,obsureText: true)
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(top: 3,left: 3),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
border: const Border(
|
||||
bottom: BorderSide(color: Colors.black),
|
||||
top: BorderSide(color: Colors.black),
|
||||
right: BorderSide(color: Colors.black),
|
||||
left: BorderSide(color: Colors.black)
|
||||
)
|
||||
),
|
||||
child: MaterialButton(
|
||||
minWidth: double.infinity,
|
||||
height:60,
|
||||
onPressed: (){
|
||||
if(passwordController.text != confirmPasswordController.text){
|
||||
Get.snackbar(
|
||||
"No match",
|
||||
"Passwords does not match",
|
||||
icon: const Icon(Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
|
||||
snackPosition: SnackPosition.TOP,
|
||||
duration: const Duration(milliseconds: 800),
|
||||
backgroundColor: Colors.yellow,
|
||||
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
||||
);
|
||||
}
|
||||
if(emailController.text.isEmpty || passwordController.text.isEmpty){
|
||||
Get.snackbar(
|
||||
"no_values".tr,
|
||||
"email_and_password_required".tr,
|
||||
icon: const Icon(Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
|
||||
snackPosition: SnackPosition.TOP,
|
||||
duration: const Duration(milliseconds: 800),
|
||||
backgroundColor: Colors.yellow,
|
||||
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
||||
);
|
||||
return;
|
||||
}
|
||||
indexController.is_loading.value = true;
|
||||
indexController.register(emailController.text, passwordController.text, context);
|
||||
},
|
||||
color: Colors.redAccent,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(40)
|
||||
),
|
||||
child: Text("sign_up".tr,style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,fontSize: 16,
|
||||
|
||||
),),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20,),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Flexible(child: Text("すでにアカウントをお持ちですか?")),
|
||||
TextButton(
|
||||
onPressed: (){
|
||||
Get.toNamed(AppPages.LOGIN);
|
||||
},
|
||||
child: const Text("ログイン",style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 18
|
||||
),),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
|
||||
Text(
|
||||
"sign_up".tr,
|
||||
style: const TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
"create_account".tr,
|
||||
style: TextStyle(fontSize: 15, color: Colors.grey[700]),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
makeInput(label: "email".tr, controller: emailController),
|
||||
//makeInput(label: "password".tr, controller: passwordController, obsureText: true),
|
||||
//makeInput(label: "confirm_password".tr, controller: confirmPasswordController, obsureText: true),
|
||||
makePasswordInput(
|
||||
label: "password".tr,
|
||||
controller: passwordController,
|
||||
obscureText: _obscurePassword,
|
||||
onToggleVisibility: () {
|
||||
setState(() {
|
||||
_obscurePassword = !_obscurePassword;
|
||||
});
|
||||
},
|
||||
),
|
||||
makePasswordInput(
|
||||
label: "confirm_password".tr,
|
||||
controller: confirmPasswordController,
|
||||
obscureText: _obscureConfirmPassword,
|
||||
onToggleVisibility: () {
|
||||
setState(() {
|
||||
_obscureConfirmPassword = !_obscureConfirmPassword;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: _handleRegister,
|
||||
style: ElevatedButton.styleFrom(
|
||||
foregroundColor: Colors.white,
|
||||
backgroundColor: Colors.redAccent,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(40)),
|
||||
minimumSize: const Size(double.infinity, 60),
|
||||
),
|
||||
child: Text("sign_up".tr, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 16)),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(child: Text("already_have_account".tr)),
|
||||
TextButton(
|
||||
onPressed: () => Get.toNamed(AppPages.LOGIN),
|
||||
child: Text("login".tr, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 18)),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -145,36 +116,96 @@ class RegisterPage extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget makePasswordInput({
|
||||
required String label,
|
||||
required TextEditingController controller,
|
||||
required bool obscureText,
|
||||
required VoidCallback onToggleVisibility,
|
||||
}) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w400, color: Colors.black87)),
|
||||
const SizedBox(height: 5),
|
||||
TextField(
|
||||
controller: controller,
|
||||
obscureText: obscureText,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 10),
|
||||
enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey[400]!)),
|
||||
border: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey[400]!)),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
obscureText ? Icons.visibility : Icons.visibility_off,
|
||||
color: Colors.grey,
|
||||
),
|
||||
onPressed: onToggleVisibility,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _handleRegister() {
|
||||
if (passwordController.text != confirmPasswordController.text) {
|
||||
_showErrorSnackbar("no_match".tr, "password_does_not_match".tr);
|
||||
return;
|
||||
}
|
||||
if (emailController.text.isEmpty || passwordController.text.isEmpty) {
|
||||
_showErrorSnackbar("no_values".tr, "email_and_password_required".tr);
|
||||
return;
|
||||
}
|
||||
|
||||
indexController.isLoading.value = true;
|
||||
try {
|
||||
indexController.register(
|
||||
emailController.text,
|
||||
passwordController.text,
|
||||
confirmPasswordController.text,
|
||||
context
|
||||
);
|
||||
// 登録が成功したと仮定し、ログインページに遷移
|
||||
//Get.offNamed(AppPages.LOGIN);
|
||||
} catch (error) {
|
||||
_showErrorSnackbar("registration_error".tr, error.toString());
|
||||
} finally {
|
||||
indexController.isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
void _showErrorSnackbar(String title, String message) {
|
||||
Get.snackbar(
|
||||
title,
|
||||
message,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
icon: const Icon(Icons.error_outline, size: 40.0, color: Colors.white),
|
||||
snackPosition: SnackPosition.TOP,
|
||||
duration: const Duration(seconds: 3),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget makeInput({label, required TextEditingController controller, obsureText = false}){
|
||||
Widget makeInput({required String label, required TextEditingController controller, bool obsureText = false}) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label,style:const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Colors.black87
|
||||
),),
|
||||
const SizedBox(height: 5,),
|
||||
Text(label, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w400, color: Colors.black87)),
|
||||
const SizedBox(height: 5),
|
||||
TextField(
|
||||
controller: controller,
|
||||
obscureText: obsureText,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 0,horizontal: 10),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: (Colors.grey[400])!,
|
||||
),
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: (Colors.grey[400])!
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 10),
|
||||
enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey[400]!)),
|
||||
border: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey[400]!)),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30,)
|
||||
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
263
lib/pages/register/user_detail_page.dart
Normal file
263
lib/pages/register/user_detail_page.dart
Normal file
@ -0,0 +1,263 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gifunavi/model/user.dart';
|
||||
import 'package:gifunavi/routes/app_pages.dart';
|
||||
import 'package:gifunavi/services/api_service.dart';
|
||||
import 'package:gifunavi/pages/index/index_controller.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import 'package:gifunavi/widgets/custom_date_picker.dart'; // 追加: 日付フォーマット用
|
||||
|
||||
class UserDetailsEditPage extends StatefulWidget {
|
||||
const UserDetailsEditPage({super.key});
|
||||
|
||||
@override
|
||||
_UserDetailsEditPageState createState() => _UserDetailsEditPageState();
|
||||
}
|
||||
|
||||
class _UserDetailsEditPageState extends State<UserDetailsEditPage> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final IndexController indexController = Get.find<IndexController>();
|
||||
late User _user;
|
||||
final TextEditingController _firstnameController = TextEditingController();
|
||||
final TextEditingController _lastnameController = TextEditingController();
|
||||
final TextEditingController _dateOfBirthController = TextEditingController();
|
||||
late bool _female;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_user = User.fromJson(indexController.currentUser[0]['user']);
|
||||
_firstnameController.text = _user.firstname;
|
||||
_lastnameController.text = _user.lastname;
|
||||
_dateOfBirthController.text = _user.dateOfBirth != null
|
||||
? '${_user.dateOfBirth!.year}/${_user.dateOfBirth!.month.toString().padLeft(2, '0')}/${_user.dateOfBirth!.day.toString().padLeft(2, '0')}'
|
||||
: '';
|
||||
_female = _user.female;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('個人情報の修正'),
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
body: Form(
|
||||
key: _formKey,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: _lastnameController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: '姓',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return '姓を入力してください';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _firstnameController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: '名',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return '名を入力してください';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _dateOfBirthController,
|
||||
decoration: InputDecoration(
|
||||
labelText: '生年月日 (YYYY/MM/DD)',
|
||||
border: const OutlineInputBorder(),
|
||||
hintText: 'YYYY/MM/DD',
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(Icons.calendar_today),
|
||||
onPressed: () => _selectDate(context),
|
||||
),
|
||||
),
|
||||
keyboardType: TextInputType.number, // <=datetime,
|
||||
onChanged: (value) {
|
||||
// スラッシュを除去
|
||||
value = value.replaceAll('/', '');
|
||||
|
||||
if (value.length <= 8) {
|
||||
String formattedValue = '';
|
||||
|
||||
// 年の処理(4桁)
|
||||
if (value.length >= 4) {
|
||||
formattedValue += '${value.substring(0, 4)}/';
|
||||
value = value.substring(4);
|
||||
} else {
|
||||
formattedValue += value;
|
||||
value = '';
|
||||
}
|
||||
|
||||
// 月の処理(2桁)
|
||||
if (value.length >= 2) {
|
||||
formattedValue += '${value.substring(0, 2)}/';
|
||||
value = value.substring(2);
|
||||
} else if (value.isNotEmpty) {
|
||||
formattedValue += value;
|
||||
value = '';
|
||||
}
|
||||
|
||||
// 残りの日付
|
||||
formattedValue += value;
|
||||
|
||||
// 末尾のスラッシュを削除
|
||||
if (formattedValue.endsWith('/')) {
|
||||
formattedValue = formattedValue.substring(0, formattedValue.length - 1);
|
||||
}
|
||||
|
||||
_dateOfBirthController.value = TextEditingValue(
|
||||
text: formattedValue,
|
||||
selection: TextSelection.collapsed(offset: formattedValue.length),
|
||||
);
|
||||
}
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return '生年月日を入力してください';
|
||||
}
|
||||
if (!RegExp(r'^\d{4}/\d{2}/\d{2}$').hasMatch(value)) {
|
||||
return '正しい形式で入力してください (YYYY/MM/DD)';
|
||||
}
|
||||
final date = DateTime.tryParse(value.replaceAll('/', '-'));
|
||||
if (date == null) {
|
||||
return '有効な日付を入力してください';
|
||||
}
|
||||
if (date.isAfter(DateTime.now())) {
|
||||
return '未来の日付は入力できません';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SwitchListTile(
|
||||
title: const Text('性別'),
|
||||
subtitle: Text(_female ? '女性' : '男性'),
|
||||
value: _female,
|
||||
onChanged: (bool value) {
|
||||
setState(() {
|
||||
_female = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
initialValue: _user.email,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'メールアドレス',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
enabled: false,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SwitchListTile(
|
||||
title: const Text('アクティブ状態'),
|
||||
value: _user.isActive,
|
||||
onChanged: null,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
ElevatedButton(
|
||||
onPressed: _updateUserDetails,
|
||||
child: const Text('更新'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 日付選択用の関数を追加
|
||||
Future<void> _selectDate(BuildContext context) async {
|
||||
final DateTime? picked = await showDialog<DateTime>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return CustomDatePicker(
|
||||
initialDate: _user.dateOfBirth ?? DateTime.now(),
|
||||
firstDate: DateTime(1900),
|
||||
lastDate: DateTime.now(),
|
||||
currentDateText: _dateOfBirthController.text,
|
||||
);
|
||||
},
|
||||
);
|
||||
if (picked != null) {
|
||||
setState(() {
|
||||
_dateOfBirthController.text = DateFormat('yyyy/MM/dd').format(picked);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _updateUserDetails() async {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
final dateOfBirth = DateTime.tryParse(_dateOfBirthController.text.replaceAll('/', '-'));
|
||||
if (dateOfBirth == null || dateOfBirth.isAfter(DateTime.now())) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('生年月日が無効です', style: TextStyle(color: Colors.red))),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 13歳以上かどうかをチェック
|
||||
final now = DateTime.now();
|
||||
final age = now.year - dateOfBirth.year -
|
||||
(now.month > dateOfBirth.month ||
|
||||
(now.month == dateOfBirth.month && now.day >= dateOfBirth.day) ? 0 : 1);
|
||||
|
||||
if (age < 13) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('13歳未満の方は登録できません', style: TextStyle(color: Colors.red))),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
User updatedUser = User(
|
||||
id: _user.id,
|
||||
email: _user.email,
|
||||
firstname: _firstnameController.text,
|
||||
lastname: _lastnameController.text,
|
||||
dateOfBirth: dateOfBirth,
|
||||
female: _female,
|
||||
isActive: _user.isActive,
|
||||
);
|
||||
|
||||
try {
|
||||
bool success = await ApiService.updateUserDetail(updatedUser, indexController.currentUser[0]['token']);
|
||||
if (success) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('個人情報が更新されました')),
|
||||
);
|
||||
indexController.updateCurrentUser(updatedUser);
|
||||
Get.offAllNamed(AppPages.INDEX);
|
||||
//Get.back();
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('更新に失敗しました', style: TextStyle(color: Colors.red))),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('エラーが発生しました: $e', style: const TextStyle(color: Colors.red))),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/search/search_controller.dart';
|
||||
import 'package:gifunavi/pages/search/search_controller.dart';
|
||||
|
||||
class SearchBinding extends Bindings {
|
||||
@override
|
||||
|
||||
@ -1,23 +1,21 @@
|
||||
import 'package:geojson/geojson.dart';
|
||||
import 'package:geojson_vi/geojson_vi.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/index/index_controller.dart';
|
||||
import 'package:gifunavi/pages/index/index_controller.dart';
|
||||
|
||||
class SearchBarController extends GetxController {
|
||||
List<GeoJSONFeature> searchResults = <GeoJSONFeature>[].obs;
|
||||
|
||||
List<GeoJsonFeature> searchResults = <GeoJsonFeature>[].obs;
|
||||
|
||||
|
||||
@override
|
||||
@override
|
||||
void onInit() {
|
||||
IndexController indexController = Get.find<IndexController>();
|
||||
if(indexController.locations.isNotEmpty){
|
||||
for(int i=0; i<= indexController.locations[0].collection.length - 1; i++){
|
||||
GeoJsonFeature p = indexController.locations[0].collection[i];
|
||||
if (indexController.locations.isNotEmpty) {
|
||||
for (int i = 0;
|
||||
i <= indexController.locations[0].features.length - 1;
|
||||
i++) {
|
||||
GeoJSONFeature p = indexController.locations[0].features[i]!;
|
||||
searchResults.add(p);
|
||||
}
|
||||
}
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,28 +1,35 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||
import 'package:geojson/geojson.dart';
|
||||
import 'package:geojson_vi/geojson_vi.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/index/index_controller.dart';
|
||||
import 'package:rogapp/pages/search/search_controller.dart';
|
||||
import 'package:rogapp/widgets/bottom_sheet_new.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/pages/search/search_controller.dart';
|
||||
//import 'package:gifunavi/widgets/bottom_sheets/bottom_sheet_start.dart';
|
||||
//import 'package:gifunavi/widgets/bottom_sheets/bottom_sheet_goal.dart';
|
||||
//import 'package:gifunavi/widgets/bottom_sheets/bottom_sheet_normal_point.dart';
|
||||
import 'package:gifunavi/widgets/bottom_sheet_new.dart';
|
||||
|
||||
class SearchPage extends StatelessWidget {
|
||||
SearchPage({Key? key}) : super(key: key);
|
||||
SearchPage({super.key});
|
||||
|
||||
SearchBarController searchController = Get.find<SearchBarController>();
|
||||
IndexController indexController = Get.find<IndexController>();
|
||||
|
||||
Image getImage(int index){
|
||||
if(searchController.searchResults[index].properties!["photos"] == null || searchController.searchResults[index].properties!["photos"] == ""){
|
||||
Image getImage(int index) {
|
||||
if (searchController.searchResults[index].properties!["photos"] == null ||
|
||||
searchController.searchResults[index].properties!["photos"] == "") {
|
||||
return const Image(image: AssetImage('assets/images/empty_image.png'));
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
return Image(
|
||||
image: NetworkImage(searchController.searchResults[index].properties!["photos"]),
|
||||
errorBuilder: (BuildContext context, Object exception, StackTrace? stackTrace) {
|
||||
image: NetworkImage(
|
||||
searchController.searchResults[index].properties!["photos"]),
|
||||
errorBuilder:
|
||||
(BuildContext context, Object exception, StackTrace? stackTrace) {
|
||||
return Image.asset("assets/images/empty_image.png");
|
||||
},
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,64 +40,84 @@ class SearchPage extends StatelessWidget {
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.white,
|
||||
leading: IconButton(
|
||||
onPressed:(){
|
||||
Navigator.pop(context);
|
||||
},
|
||||
icon: const Icon(Icons.arrow_back_ios_new, color: Colors.black,)),
|
||||
title: TypeAheadField(
|
||||
textFieldConfiguration: const TextFieldConfiguration(
|
||||
autofocus: true,
|
||||
),
|
||||
suggestionsCallback: (pattern) async{
|
||||
return searchController.searchResults.where((GeoJsonFeature element) => element.properties!["location_name"].toString().contains(pattern));
|
||||
//return await
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.arrow_back_ios_new,
|
||||
color: Colors.black,
|
||||
)),
|
||||
centerTitle: true,
|
||||
//title: const CupertinoSearchTextField(),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: TypeAheadField<GeoJSONFeature>(
|
||||
// textFieldConfiguration: TextFieldConfiguration(
|
||||
// autofocus: true,
|
||||
// style: DefaultTextStyle.of(context).style.copyWith(
|
||||
// fontStyle: FontStyle.normal,
|
||||
// fontSize: 15.0,
|
||||
// ),
|
||||
// decoration: InputDecoration(
|
||||
// border: const OutlineInputBorder(),
|
||||
// hintText: "検索",
|
||||
// prefixIcon: const Icon(Icons.search),
|
||||
// suffixIcon: IconButton(
|
||||
// icon: const Icon(Icons.clear),
|
||||
// onPressed: () {
|
||||
// // clear the text field
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
onSelected: (GeoJSONFeature suggestion) {
|
||||
indexController.currentFeature.clear();
|
||||
indexController.currentFeature.add(suggestion);
|
||||
DestinationController destinationController =
|
||||
Get.find<DestinationController>();
|
||||
Destination des =
|
||||
destinationController.festuretoDestination(suggestion);
|
||||
Get.back();
|
||||
|
||||
Widget bottomSheet = BottomSheetNew(destination: des);
|
||||
/*
|
||||
if (des.cp == -1 || des.cp == 0) {
|
||||
bottomSheet = BottomSheetStart(destination: des);
|
||||
} else if (des.cp == -2 || des.cp == 0) {
|
||||
bottomSheet = BottomSheetGoal(destination: des);
|
||||
} else {
|
||||
bottomSheet = BottomSheetNormalPoint(destination: des);
|
||||
}
|
||||
*/
|
||||
showModalBottomSheet(
|
||||
constraints:
|
||||
BoxConstraints.loose(Size(Get.width, Get.height * 0.75)),
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: ((context) => bottomSheet)
|
||||
);
|
||||
},
|
||||
itemBuilder: (context, GeoJsonFeature suggestion){
|
||||
|
||||
suggestionsCallback: (pattern) async {
|
||||
return searchController.searchResults
|
||||
.where((GeoJSONFeature element) => element
|
||||
.properties!["location_name"]
|
||||
.toString()
|
||||
.contains(pattern))
|
||||
.toList();
|
||||
//return await
|
||||
},
|
||||
itemBuilder: (context, GeoJSONFeature suggestion) {
|
||||
return ListTile(
|
||||
title: Text(suggestion.properties!["location_name"]),
|
||||
subtitle: suggestion.properties!["category"] != null ? Text(suggestion.properties!["category"]) : const Text(""),
|
||||
subtitle: suggestion.properties!["category"] != null
|
||||
? Text(suggestion.properties!["category"])
|
||||
: const Text(""),
|
||||
//leading: getImage(index),
|
||||
);
|
||||
},
|
||||
onSuggestionSelected: (GeoJsonFeature suggestion){
|
||||
indexController.currentFeature.clear();
|
||||
indexController.currentFeature.add(suggestion);
|
||||
Get.back();
|
||||
showModalBottomSheet(
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
//builder: (context) => BottomSheetWidget(),
|
||||
builder:((context) => BottomSheetNew())
|
||||
);
|
||||
},
|
||||
),
|
||||
//title: const CupertinoSearchTextField(),
|
||||
|
||||
),
|
||||
//body:
|
||||
// Obx(() =>
|
||||
// ListView.builder(
|
||||
// itemCount: searchController.searchResults.length,
|
||||
// itemBuilder: (context, index){
|
||||
// return ListTile(
|
||||
// title: searchController.searchResults[index].properties!["location_name"] != null ? Text(searchController.searchResults[index].properties!["location_name"]) : Text(""),
|
||||
// subtitle: searchController.searchResults[index].properties!["category"] != null ? Text(searchController.searchResults[index].properties!["category"]) : Text(""),
|
||||
// leading: getImage(index),
|
||||
// onTap: (){
|
||||
// indexController.currentFeature.clear();
|
||||
// indexController.currentFeature.add(searchController.searchResults[index]);
|
||||
// Get.back();
|
||||
// showModalBottomSheet(
|
||||
// isScrollControlled: true,
|
||||
// context: context,
|
||||
// //builder: (context) => BottomSheetWidget(),
|
||||
// builder:((context) => BottomSheetNew())
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// )
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
lib/pages/settings/settings_binding.dart
Normal file
12
lib/pages/settings/settings_binding.dart
Normal file
@ -0,0 +1,12 @@
|
||||
// lib/pages/settings/settings_binding.dart
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gifunavi/pages/settings/settings_controller.dart';
|
||||
|
||||
class SettingsBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.put<SettingsController>(SettingsController()); // これを修正
|
||||
//Get.lazyPut<SettingsController>(() => SettingsController());
|
||||
}
|
||||
}
|
||||
26
lib/pages/settings/settings_controller.dart
Normal file
26
lib/pages/settings/settings_controller.dart
Normal file
@ -0,0 +1,26 @@
|
||||
// lib/pages/settings/settings_controller.dart
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gifunavi/widgets/map_widget.dart';
|
||||
|
||||
|
||||
class SettingsController extends GetxController {
|
||||
var timerDuration = const Duration(seconds: 10).obs;
|
||||
var autoReturnDisabled = false.obs;
|
||||
final MapResetController mapResetController = Get.put(MapResetController());
|
||||
|
||||
void updateTimerDuration(int seconds) {
|
||||
timerDuration.value = Duration(seconds: seconds);
|
||||
}
|
||||
|
||||
void setAutoReturnDisabled(bool value) {
|
||||
autoReturnDisabled.value = value;
|
||||
if (!value) {
|
||||
resetIdleTimer();
|
||||
}
|
||||
}
|
||||
|
||||
void resetIdleTimer() {
|
||||
mapResetController.resetIdleTimer!();
|
||||
}
|
||||
}
|
||||
63
lib/pages/settings/settings_page.dart
Normal file
63
lib/pages/settings/settings_page.dart
Normal file
@ -0,0 +1,63 @@
|
||||
// lib/pages/settings/settings_page.dart
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gifunavi/pages/settings/settings_controller.dart';
|
||||
|
||||
class SettingsPage extends GetView<SettingsController> {
|
||||
const SettingsPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('設定'),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'timer_duration'.tr,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Obx(
|
||||
() => controller.autoReturnDisabled.value
|
||||
? Container()
|
||||
: Slider(
|
||||
value: controller.timerDuration.value.inSeconds.toDouble(),
|
||||
min: 5,
|
||||
max: 30,
|
||||
divisions: 5,
|
||||
label: '${controller.timerDuration.value.inSeconds}秒',
|
||||
onChanged: (value) {
|
||||
controller.updateTimerDuration(value.toInt());
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'マップ操作がなければ自動的に現在地に復帰します。そのタイマー秒数を入れて下さい。チェックボックスをチェックすると、自動復帰は行われなくなります。',
|
||||
style: TextStyle(fontSize: 14),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Obx(
|
||||
() => CheckboxListTile(
|
||||
title: const Text('自動復帰なし'),
|
||||
value: controller.autoReturnDisabled.value,
|
||||
onChanged: (value) {
|
||||
controller.setAutoReturnDisabled(value!);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,14 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/index/index_controller.dart';
|
||||
import 'package:gifunavi/pages/index/index_controller.dart';
|
||||
|
||||
class SubPerfPage extends StatelessWidget {
|
||||
SubPerfPage({Key? key}) : super(key: key);
|
||||
SubPerfPage({super.key});
|
||||
|
||||
IndexController indexController = Get.find<IndexController>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint("SubPerfPage ---->");
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Select Sub Perfecture"),
|
||||
|
||||
11
lib/pages/team/member_binding.dart
Normal file
11
lib/pages/team/member_binding.dart
Normal file
@ -0,0 +1,11 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gifunavi/pages/team/member_controller.dart';
|
||||
import 'package:gifunavi/services/api_service.dart';
|
||||
|
||||
class MemberBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<ApiService>(() => ApiService());
|
||||
Get.lazyPut<MemberController>(() => MemberController());
|
||||
}
|
||||
}
|
||||
270
lib/pages/team/member_controller.dart
Normal file
270
lib/pages/team/member_controller.dart
Normal file
@ -0,0 +1,270 @@
|
||||
// lib/controllers/member_controller.dart
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gifunavi/model/user.dart';
|
||||
import 'package:gifunavi/services/api_service.dart';
|
||||
import 'package:gifunavi/pages/team/team_controller.dart';
|
||||
|
||||
class MemberController extends GetxController {
|
||||
late final ApiService _apiService;
|
||||
late final TeamController _teamController;
|
||||
|
||||
final selectedMember = Rx<User?>(null);
|
||||
int teamId = 0;
|
||||
final member = Rx<User?>(null);
|
||||
final email = ''.obs;
|
||||
final firstname = ''.obs;
|
||||
final lastname = ''.obs;
|
||||
final female = false.obs;
|
||||
final dateOfBirth = Rx<DateTime?>(null);
|
||||
final isLoading = false.obs; // isLoadingプロパティを追加
|
||||
final isActive = false.obs;
|
||||
|
||||
//MemberController(this._apiService);
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
_apiService = Get.find<ApiService>();
|
||||
_teamController = Get.find<TeamController>();
|
||||
ever(member, (_) => _initializeMemberData());
|
||||
loadInitialData();
|
||||
}
|
||||
|
||||
bool get isDummyEmail => email.value.startsWith('dummy_');
|
||||
bool get isApproved => !email.value.startsWith('dummy_') && member.value?.isActive == true;
|
||||
|
||||
Future<void> loadInitialData() async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
if (Get.arguments != null) {
|
||||
if (Get.arguments['member'] != null) {
|
||||
member.value = Get.arguments['member'];
|
||||
}
|
||||
if (Get.arguments['teamId'] != null) {
|
||||
teamId = Get.arguments['teamId'];
|
||||
}
|
||||
}
|
||||
// 他の必要な初期データの取得をここで行う
|
||||
} catch (e) {
|
||||
print('Error loading initial data: $e');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void _initializeMemberData() {
|
||||
if (member.value != null) {
|
||||
email.value = member.value!.email ?? '';
|
||||
firstname.value = member.value!.firstname ?? '';
|
||||
lastname.value = member.value!.lastname ?? '';
|
||||
dateOfBirth.value = member.value!.dateOfBirth;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void setSelectedMember(User member) {
|
||||
this.member.value = member;
|
||||
email.value = member.email ?? '';
|
||||
firstname.value = member.firstname ?? '';
|
||||
lastname.value = member.lastname ?? '';
|
||||
dateOfBirth.value = member.dateOfBirth;
|
||||
female.value = member.female ?? false;
|
||||
isActive.value = member.isActive ?? false;
|
||||
}
|
||||
|
||||
bool validateInputs() {
|
||||
if (email.value.isNotEmpty && !isDummyEmail) {
|
||||
return true; // Emailのみの場合は有効
|
||||
}
|
||||
if (firstname.value.isEmpty || lastname.value.isEmpty || dateOfBirth.value == null) {
|
||||
Get.snackbar('エラー', 'Emailが空の場合、姓名と生年月日及び性別は必須です', snackPosition: SnackPosition.BOTTOM);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void updateFirstName(String value) {
|
||||
firstname.value = value;
|
||||
}
|
||||
|
||||
void updateLastName(String value) {
|
||||
lastname.value = value;
|
||||
}
|
||||
|
||||
|
||||
Future<bool> saveMember() async {
|
||||
if (!validateInputs()) return false;
|
||||
|
||||
try {
|
||||
isLoading.value = true;
|
||||
User updatedMember;
|
||||
if (member.value == null || member.value!.id == null) {
|
||||
// 新規メンバー作成
|
||||
updatedMember = await _apiService.createTeamMember(
|
||||
teamId,
|
||||
isDummyEmail ? null : email.value, // dummy_メールの場合はnullを送信
|
||||
firstname.value,
|
||||
lastname.value,
|
||||
dateOfBirth.value,
|
||||
female.value,
|
||||
);
|
||||
} else {
|
||||
// 既存メンバー更新
|
||||
updatedMember = await _apiService.updateTeamMember(
|
||||
teamId,
|
||||
member.value!.id!,
|
||||
firstname.value,
|
||||
lastname.value,
|
||||
dateOfBirth.value,
|
||||
female.value,
|
||||
);
|
||||
}
|
||||
member.value = updatedMember;
|
||||
await _teamController.updateTeamComposition();
|
||||
|
||||
Get.snackbar('成功', 'メンバーが保存されました', snackPosition: SnackPosition.BOTTOM);
|
||||
return true;
|
||||
} catch (e) {
|
||||
print('Error saving member: $e');
|
||||
Get.snackbar('エラー', 'メンバーの保存に失敗しました: ${e.toString()}', snackPosition: SnackPosition.BOTTOM);
|
||||
return false;
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String getDisplayName() {
|
||||
if (!isActive.value && !isDummyEmail) {
|
||||
final displayName = email.value.split('@')[0];
|
||||
return '$displayName(未承認)';
|
||||
}
|
||||
return '${lastname.value} ${firstname.value}'.trim();
|
||||
}
|
||||
|
||||
Future<bool> updateMember() async {
|
||||
if (member.value == null) return false;
|
||||
int? memberId = member.value?.id;
|
||||
try {
|
||||
final updatedMember = await _apiService.updateTeamMember(
|
||||
teamId,
|
||||
memberId,
|
||||
firstname.value,
|
||||
lastname.value,
|
||||
dateOfBirth.value,
|
||||
female.value,
|
||||
);
|
||||
member.value = updatedMember;
|
||||
return true;
|
||||
} catch (e) {
|
||||
print('Error updating member: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> deleteMember() async {
|
||||
if (member.value == null || member.value!.id == null) {
|
||||
Get.snackbar('エラー', 'メンバー情報が不正です', snackPosition: SnackPosition.BOTTOM);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
isLoading.value = true;
|
||||
await _apiService.deleteTeamMember(teamId, member.value!.id!);
|
||||
|
||||
await _teamController.updateTeamComposition();
|
||||
|
||||
Get.snackbar('成功', 'メンバーが削除されました', snackPosition: SnackPosition.BOTTOM);
|
||||
member.value = null;
|
||||
|
||||
return true;
|
||||
|
||||
} catch (e) {
|
||||
print('Error deleting member: $e');
|
||||
Get.snackbar('エラー', 'メンバーの削除に失敗しました: ${e.toString()}', snackPosition: SnackPosition.BOTTOM);
|
||||
isLoading.value = false;
|
||||
return false;
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> resendInvitation() async {
|
||||
if (isDummyEmail) {
|
||||
Get.snackbar('エラー', 'ダミーメールアドレスには招待メールを送信できません', snackPosition: SnackPosition.BOTTOM);
|
||||
return;
|
||||
}
|
||||
|
||||
if (member.value == null || member.value!.email == null) return;
|
||||
int? memberId = member.value?.id;
|
||||
try {
|
||||
await _apiService.resendMemberInvitation(memberId!);
|
||||
Get.snackbar('Success', 'Invitation resent successfully');
|
||||
} catch (e) {
|
||||
print('Error resending invitation: $e');
|
||||
Get.snackbar('Error', 'Failed to resend invitation');
|
||||
}
|
||||
}
|
||||
|
||||
void updateEmail(String value) => email.value = value;
|
||||
void updateFirstname(String value) => firstname.value = value;
|
||||
void updateLastname(String value) => lastname.value = value;
|
||||
void updateDateOfBirth(DateTime value) => dateOfBirth.value = value;
|
||||
|
||||
String getMemberStatus() {
|
||||
if (member.value == null) return '';
|
||||
if (member.value!.email == null) return '未登録';
|
||||
if (member.value!.isActive) return '承認済';
|
||||
return '招待中';
|
||||
}
|
||||
|
||||
int calculateAge() {
|
||||
if (dateOfBirth.value == null) return 0;
|
||||
final today = DateTime.now();
|
||||
int age = today.year - dateOfBirth.value!.year;
|
||||
if (today.month < dateOfBirth.value!.month ||
|
||||
(today.month == dateOfBirth.value!.month && today.day < dateOfBirth.value!.day)) {
|
||||
age--;
|
||||
}
|
||||
return age;
|
||||
}
|
||||
|
||||
String calculateGrade() {
|
||||
if (dateOfBirth.value == null) return '不明';
|
||||
|
||||
final today = DateTime.now();
|
||||
final birthDate = dateOfBirth.value!;
|
||||
|
||||
// 今年の4月1日
|
||||
final thisYearSchoolStart = DateTime(today.year, 4, 1);
|
||||
|
||||
// 生まれた年の翌年の4月1日(学齢期の始まり)
|
||||
final schoolStartDate = DateTime(birthDate.year + 1, 4, 1);
|
||||
|
||||
// 学齢期の開始からの年数
|
||||
int yearsFromSchoolStart = today.year - schoolStartDate.year;
|
||||
|
||||
// 今年の4月1日より前なら1年引く
|
||||
if (today.isBefore(thisYearSchoolStart)) {
|
||||
yearsFromSchoolStart--;
|
||||
}
|
||||
|
||||
if (yearsFromSchoolStart < 7) return '未就学';
|
||||
if (yearsFromSchoolStart < 13) return '小${yearsFromSchoolStart - 6}';
|
||||
if (yearsFromSchoolStart < 16) return '中${yearsFromSchoolStart - 12}';
|
||||
if (yearsFromSchoolStart < 19) return '高${yearsFromSchoolStart - 15}';
|
||||
return '成人';
|
||||
}
|
||||
|
||||
String getAgeAndGrade() {
|
||||
final age = calculateAge();
|
||||
final grade = calculateGrade();
|
||||
return '$age歳/$grade';
|
||||
}
|
||||
|
||||
bool isOver18() {
|
||||
return calculateAge() >= 18;
|
||||
}
|
||||
}
|
||||
325
lib/pages/team/member_detail_page.dart
Normal file
325
lib/pages/team/member_detail_page.dart
Normal file
@ -0,0 +1,325 @@
|
||||
// lib/pages/team/member_detail_page.dart
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gifunavi/pages/team/member_controller.dart';
|
||||
import 'package:gifunavi/pages/team/team_controller.dart';
|
||||
import 'package:intl/intl.dart'; // この行を追加
|
||||
import 'package:gifunavi/widgets/custom_date_picker.dart';
|
||||
// 追加
|
||||
|
||||
import 'package:gifunavi/routes/app_pages.dart';
|
||||
import 'package:gifunavi/model/user.dart';
|
||||
|
||||
class MemberDetailPage extends StatefulWidget {
|
||||
const MemberDetailPage({super.key});
|
||||
|
||||
@override
|
||||
_MemberDetailPageState createState() => _MemberDetailPageState();
|
||||
}
|
||||
|
||||
class _MemberDetailPageState extends State<MemberDetailPage> {
|
||||
final MemberController controller = Get.find<MemberController>();
|
||||
final TeamController teamController = Get.find<TeamController>();
|
||||
late TextEditingController _firstNameController;
|
||||
late TextEditingController _lastNameController;
|
||||
late TextEditingController _emailController;
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initializeControllers();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final mode = Get.arguments['mode'];
|
||||
final member = Get.arguments['member'];
|
||||
if (mode == 'edit' && member != null) {
|
||||
controller.setSelectedMember(member);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _initializeControllers() {
|
||||
_firstNameController =
|
||||
TextEditingController(text: controller.firstname.value);
|
||||
_lastNameController =
|
||||
TextEditingController(text: controller.lastname.value);
|
||||
_emailController = TextEditingController(text: controller.email.value);
|
||||
|
||||
controller.firstname.listen((value) {
|
||||
if (_firstNameController.text != value) {
|
||||
_firstNameController.value = TextEditingValue(
|
||||
text: value,
|
||||
selection: TextSelection.fromPosition(
|
||||
TextPosition(offset: value.length)),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
controller.lastname.listen((value) {
|
||||
if (_lastNameController.text != value) {
|
||||
_lastNameController.value = TextEditingValue(
|
||||
text: value,
|
||||
selection: TextSelection.fromPosition(
|
||||
TextPosition(offset: value.length)),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
controller.email.listen((value) {
|
||||
if (_emailController.text != value) {
|
||||
_emailController.value = TextEditingValue(
|
||||
text: value,
|
||||
selection: TextSelection.fromPosition(
|
||||
TextPosition(offset: value.length)),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _handleSaveAndNavigateBack_old() async {
|
||||
bool success = await controller.saveMember();
|
||||
if (success) {
|
||||
Get.until((route) => Get.currentRoute == AppPages.TEAM_DETAIL);
|
||||
// スナックバーが表示されるのを待つ
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
|
||||
// 現在のスナックバーを安全に閉じる
|
||||
if (Get.isSnackbarOpen) {
|
||||
await Get.closeCurrentSnackbar();
|
||||
}
|
||||
|
||||
// リストページに戻る
|
||||
//Get.until((route) => Get.currentRoute == '/team');
|
||||
// または、リストページの具体的なルート名がある場合は以下を使用
|
||||
// Get.offNamed('/team');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _handleSaveAndNavigateBack() async {
|
||||
|
||||
if (!controller.validateInputs()) return;
|
||||
|
||||
try {
|
||||
await controller.saveMember();
|
||||
await teamController.updateTeamComposition();
|
||||
|
||||
Get.until((route) => Get.currentRoute == AppPages.TEAM_DETAIL);
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
if (Get.isSnackbarOpen) {
|
||||
await Get.closeCurrentSnackbar();
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error saving member: $e');
|
||||
Get.snackbar('エラー', 'メンバーの保存に失敗しました: ${e.toString()}', snackPosition: SnackPosition.BOTTOM);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> _handleDeleteAndNavigateBack() async {
|
||||
final confirmed = await Get.dialog<bool>(
|
||||
AlertDialog(
|
||||
title: const Text('確認'),
|
||||
content: const Text('このメンバーを削除してもよろしいですか?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: const Text('キャンセル'),
|
||||
onPressed: () => Get.back(result: false),
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('削除'),
|
||||
onPressed: () => Get.back(result: true),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (confirmed == true) {
|
||||
try {
|
||||
if (controller.member.value != null && controller.member.value!.id != null) {
|
||||
await teamController.removeMember(controller.member.value!.id!);
|
||||
|
||||
// スナックバーの処理を安全に行う
|
||||
await _safelyCloseSnackbar();
|
||||
|
||||
// 画面遷移
|
||||
Get.until((route) => Get.currentRoute == AppPages.TEAM_DETAIL);
|
||||
|
||||
} else {
|
||||
Get.snackbar('エラー', 'メンバー情報が不正です', snackPosition: SnackPosition.BOTTOM);
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error deleting member: $e');
|
||||
Get.snackbar('エラー', 'メンバーの削除に失敗しました: ${e.toString()}', snackPosition: SnackPosition.BOTTOM);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _safelyCloseSnackbar() async {
|
||||
if (Get.isSnackbarOpen) {
|
||||
try {
|
||||
await Get.closeCurrentSnackbar();
|
||||
} catch (e) {
|
||||
print('Error closing snackbar: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final mode = Get.arguments['mode'] as String;
|
||||
//final member = Get.arguments['member'];
|
||||
final teamId = Get.arguments['teamId'] as int;
|
||||
|
||||
/*
|
||||
return MaterialApp( // MaterialApp をここに追加
|
||||
localizationsDelegates: [
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: [
|
||||
const Locale('ja', 'JP'),
|
||||
],
|
||||
home:Scaffold(
|
||||
*/
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(mode == 'new' ? 'メンバー追加' : 'メンバー詳細'),
|
||||
),
|
||||
body: Obx(() {
|
||||
if (controller.isLoading.value) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
// TextEditingControllerをObxの中で作成
|
||||
final emailController = TextEditingController(text: controller.email.value);
|
||||
// カーソル位置を保持
|
||||
emailController.selection = TextSelection.fromPosition(
|
||||
TextPosition(offset: controller.email.value.length),
|
||||
);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (mode == 'new')
|
||||
TextFormField(
|
||||
controller: _emailController,
|
||||
onChanged: (value) => controller.updateEmail(value),
|
||||
decoration: const InputDecoration(labelText: 'メールアドレス'),
|
||||
keyboardType: TextInputType.emailAddress, // メールアドレス用のキーボードを表示
|
||||
autocorrect: false, // 自動修正を無効化
|
||||
enableSuggestions: false,
|
||||
)
|
||||
else if (controller.isDummyEmail)
|
||||
const Text('メールアドレス: (メアド無し)')
|
||||
else
|
||||
Text('メールアドレス: ${controller.email.value}'),
|
||||
|
||||
if (controller.email.value.isEmpty || controller.isDummyEmail || mode == 'edit') ...[
|
||||
TextFormField(
|
||||
decoration: const InputDecoration(labelText: '姓'),
|
||||
controller: _lastNameController,
|
||||
onChanged: (value) => controller.updateLastName(value),
|
||||
|
||||
//controller: TextEditingController(text: controller.lastname.value),
|
||||
),
|
||||
TextFormField(
|
||||
decoration: const InputDecoration(labelText: '名'),
|
||||
controller: _firstNameController,
|
||||
//onChanged: (value) => controller.firstname.value = value,
|
||||
onChanged: (value) => controller.updateFirstName(value),
|
||||
//controller: TextEditingController(text: controller.firstname.value),
|
||||
),
|
||||
// 生年月日
|
||||
if (controller.isDummyEmail || !controller.isOver18())
|
||||
ListTile(
|
||||
title: const Text('生年月日'),
|
||||
subtitle: Text(controller.dateOfBirth.value != null
|
||||
? '${DateFormat('yyyy年MM月dd日').format(controller.dateOfBirth.value!)} (${controller.getAgeAndGrade()})'
|
||||
: '未設定'),
|
||||
onTap: () async {
|
||||
final date = await showDialog<DateTime>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return CustomDatePicker(
|
||||
initialDate: controller.dateOfBirth.value ?? DateTime.now(),
|
||||
firstDate: DateTime(1920),
|
||||
lastDate: DateTime.now(),
|
||||
currentDateText: controller.dateOfBirth.value != null
|
||||
? DateFormat('yyyy/MM/dd').format(controller.dateOfBirth.value!)
|
||||
: '',
|
||||
);
|
||||
},
|
||||
);
|
||||
if (date != null) {
|
||||
controller.dateOfBirth.value = date;
|
||||
}
|
||||
},
|
||||
)
|
||||
else
|
||||
const Text('18歳以上'),
|
||||
|
||||
SwitchListTile(
|
||||
title: const Text('性別'),
|
||||
subtitle: Text(controller.female.value ? '女性' : '男性'),
|
||||
value: controller.female.value,
|
||||
onChanged: (value) => controller.female.value = value,
|
||||
),
|
||||
],
|
||||
]),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: _handleDeleteAndNavigateBack,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: const Text('削除'),
|
||||
),
|
||||
if (!controller.isDummyEmail && !controller.isApproved && mode == 'edit')
|
||||
ElevatedButton(
|
||||
onPressed: () => controller.resendInvitation(),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.black,
|
||||
),
|
||||
child: const Text('招待再送信'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: _handleSaveAndNavigateBack,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.green,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: Text(controller.isDummyEmail ? '保存' :
|
||||
(mode == 'new' && !controller.isDummyEmail) ? '保存・招待' :
|
||||
'保存'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_firstNameController.dispose();
|
||||
_lastNameController.dispose();
|
||||
_emailController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
11
lib/pages/team/team_binding.dart
Normal file
11
lib/pages/team/team_binding.dart
Normal file
@ -0,0 +1,11 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gifunavi/pages/team/team_controller.dart';
|
||||
import 'package:gifunavi/services/api_service.dart';
|
||||
|
||||
class TeamBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<ApiService>(() => ApiService());
|
||||
Get.lazyPut<TeamController>(() => TeamController());
|
||||
}
|
||||
}
|
||||
692
lib/pages/team/team_controller.dart
Normal file
692
lib/pages/team/team_controller.dart
Normal file
@ -0,0 +1,692 @@
|
||||
// lib/controllers/team_controller.dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gifunavi/model/team.dart';
|
||||
import 'package:gifunavi/model/category.dart';
|
||||
import 'package:gifunavi/model/user.dart';
|
||||
import 'package:gifunavi/model/entry.dart';
|
||||
|
||||
import 'package:gifunavi/services/api_service.dart';
|
||||
import 'package:gifunavi/widgets/category_change_dialog.dart';
|
||||
import 'package:gifunavi/routes/app_pages.dart';
|
||||
import 'package:gifunavi/pages/entry/entry_controller.dart';
|
||||
|
||||
import 'package:gifunavi/model/event.dart';
|
||||
|
||||
|
||||
class TeamController extends GetxController {
|
||||
late final ApiService _apiService;
|
||||
late final EntryController _entryController;
|
||||
|
||||
final teams = <Team>[].obs;
|
||||
final categories = <NewCategory>[].obs;
|
||||
final RxList<User> teamMembers = <User>[].obs;
|
||||
final teamEntries = <Entry>[].obs;
|
||||
|
||||
final selectedCategory = Rx<NewCategory?>(null);
|
||||
final selectedTeam = Rx<Team?>(null);
|
||||
final currentUser = Rx<User?>(null);
|
||||
|
||||
final teamName = ''.obs;
|
||||
final isLoading = false.obs;
|
||||
final error = RxString('');
|
||||
|
||||
@override
|
||||
void onInit() async {
|
||||
super.onInit();
|
||||
try {
|
||||
_apiService = Get.find<ApiService>();
|
||||
|
||||
if (!Get.isRegistered<EntryController>()) {
|
||||
Get.put(EntryController());
|
||||
}
|
||||
_entryController = Get.find<EntryController>();
|
||||
|
||||
await loadInitialData();
|
||||
} catch (e) {
|
||||
error.value = e.toString();
|
||||
print('Error in TeamController onInit: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loadInitialData() async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
await Future.wait([
|
||||
fetchCategories(),
|
||||
fetchTeams(),
|
||||
getCurrentUser(),
|
||||
]);
|
||||
if (categories.isNotEmpty && selectedCategory.value == null) {
|
||||
selectedCategory.value = categories.first;
|
||||
}
|
||||
} catch (e) {
|
||||
error.value = e.toString();
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void setSelectedTeam(Team team) {
|
||||
selectedTeam.value = team;
|
||||
teamName.value = team.teamName;
|
||||
if (categories.isNotEmpty) {
|
||||
selectedCategory.value = categories.firstWhere(
|
||||
(category) => category.id == team.category.id,
|
||||
orElse: () => categories.first,
|
||||
);
|
||||
} else {
|
||||
// カテゴリリストが空の場合、teamのカテゴリをそのまま使用
|
||||
selectedCategory.value = team.category;
|
||||
}
|
||||
fetchTeamMembers(team.id);
|
||||
}
|
||||
|
||||
void resetForm() {
|
||||
selectedTeam.value = null;
|
||||
teamName.value = '';
|
||||
if (categories.isNotEmpty) {
|
||||
selectedCategory.value = categories.first;
|
||||
} else {
|
||||
selectedCategory.value = null;
|
||||
// カテゴリが空の場合、エラーメッセージをセット
|
||||
error.value = 'カテゴリデータが取得できませんでした。';
|
||||
} teamMembers.clear();
|
||||
}
|
||||
|
||||
void cleanupForNavigation() {
|
||||
selectedTeam.value = null;
|
||||
teamName.value = '';
|
||||
selectedCategory.value = categories.isNotEmpty ? categories.first : null;
|
||||
teamMembers.clear();
|
||||
//teamMembersはクリアしない
|
||||
// 必要に応じて他のクリーンアップ処理を追加
|
||||
}
|
||||
|
||||
Future<void> fetchTeams() async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
final fetchedTeams = await _apiService.getTeams();
|
||||
teams.assignAll(fetchedTeams);
|
||||
} catch (e) {
|
||||
error.value = 'チームの取得に失敗しました: $e';
|
||||
print('Error fetching teams: $e');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool checkIfUserHasEntryData(){
|
||||
if (teams.isEmpty) {
|
||||
return false;
|
||||
}else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchCategories() async {
|
||||
try {
|
||||
final fetchedCategories = await _apiService.getCategories();
|
||||
categories.assignAll(fetchedCategories);
|
||||
print("Fetched categories: ${categories.length}"); // デバッグ用
|
||||
|
||||
} catch (e) {
|
||||
print('Error fetching categories: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> getCurrentUser() async {
|
||||
try {
|
||||
final user = await _apiService.getCurrentUser();
|
||||
currentUser.value = user;
|
||||
} catch (e) {
|
||||
print('Error getting current user: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<Team> createTeam(String teamName, int categoryId) async {
|
||||
final newTeam = await _apiService.createTeam(teamName, categoryId);
|
||||
|
||||
// 自分自身をメンバーとして自動登録
|
||||
await _apiService.createTeamMember(
|
||||
newTeam.id,
|
||||
currentUser.value?.email,
|
||||
currentUser.value!.firstname,
|
||||
currentUser.value!.lastname,
|
||||
currentUser.value?.dateOfBirth,
|
||||
currentUser.value?.female,
|
||||
);
|
||||
|
||||
return newTeam;
|
||||
}
|
||||
|
||||
Future<Team> updateTeam(int teamId, String teamName, int categoryId) async {
|
||||
// APIサービスを使用してチームを更新
|
||||
final updatedTeam = await _apiService.updateTeam(teamId, teamName, categoryId);
|
||||
return updatedTeam;
|
||||
}
|
||||
|
||||
Future<void> deleteTeam(int teamId) async {
|
||||
bool confirmDelete = await Get.dialog(
|
||||
AlertDialog(
|
||||
title: const Text('チーム削除の確認'),
|
||||
content: const Text('このチームとそのすべてのメンバーを削除しますか?この操作は取り消せません。'),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: const Text('キャンセル'),
|
||||
onPressed: () => Get.back(result: false),
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('削除'),
|
||||
onPressed: () => Get.back(result: true),
|
||||
),
|
||||
],
|
||||
),
|
||||
) ?? false;
|
||||
|
||||
if (confirmDelete) {
|
||||
try {
|
||||
// まず、チームのメンバーを全て削除
|
||||
await _apiService.deleteAllTeamMembers(teamId);
|
||||
|
||||
// その後、チームを削除
|
||||
await _apiService.deleteTeam(teamId);
|
||||
|
||||
// ローカルのチームリストを更新
|
||||
teams.removeWhere((team) => team.id == teamId);
|
||||
|
||||
/*
|
||||
Get.snackbar(
|
||||
'成功',
|
||||
'チームとそのメンバーが削除されました',
|
||||
backgroundColor: Colors.green,
|
||||
colorText: Colors.white,
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
*/
|
||||
|
||||
// チームリスト画面に戻る
|
||||
Get.back();
|
||||
|
||||
} catch (e) {
|
||||
print('Error deleting team and members: $e');
|
||||
Get.snackbar('エラー', 'チームとメンバーの削除に失敗しました');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchTeamMembers(int teamId) async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
final members = await _apiService.getTeamMembers(teamId);
|
||||
teamMembers.assignAll(members);
|
||||
teamMembers.refresh(); // 明示的に更新を通知
|
||||
} catch (e) {
|
||||
error.value = 'メンバーの取得に失敗しました: $e';
|
||||
print('Error fetching team members: $e');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateMember(teamId, User member) async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
await _apiService.updateTeamMember(teamId,member.id, member.firstname, member.lastname, member.dateOfBirth, member.female);
|
||||
await fetchTeamMembers(selectedTeam.value!.id);
|
||||
} catch (e) {
|
||||
error.value = 'メンバーの更新に失敗しました: $e';
|
||||
print('Error updating member: $e');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteMember(int memberId, int teamId) async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
await _apiService.deleteTeamMember(teamId,memberId);
|
||||
await fetchTeamMembers(teamId);
|
||||
} catch (e) {
|
||||
error.value = 'メンバーの削除に失敗しました: $e';
|
||||
print('Error deleting member: $e');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Future<void> addMember(User member, int teamId) async {
|
||||
// try {
|
||||
// isLoading.value = true;
|
||||
// await _apiService.createTeamMember(teamId, member.email, member.firstname, member.lastname, member.dateOfBirth);
|
||||
// await fetchTeamMembers(teamId);
|
||||
// } catch (e) {
|
||||
// error.value = 'メンバーの追加に失敗しました: $e';
|
||||
// print('Error adding member: $e');
|
||||
// } finally {
|
||||
// isLoading.value = false;
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
void updateTeamName(String value) {
|
||||
teamName.value = value;
|
||||
}
|
||||
|
||||
void updateCategory(NewCategory? value) {
|
||||
if (value != null) {
|
||||
selectedCategory.value = value;
|
||||
}
|
||||
}
|
||||
//void updateCategory(NewCategory? value) {
|
||||
// if (value != null) {
|
||||
// selectedCategory.value = categories.firstWhere(
|
||||
// (category) => category.id == value.id,
|
||||
// orElse: () => value,
|
||||
// );
|
||||
// }
|
||||
//}
|
||||
|
||||
Future<void> saveTeam() async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
if (selectedCategory.value == null) {
|
||||
throw Exception('カテゴリを選択してください');
|
||||
}
|
||||
|
||||
if (selectedTeam.value == null) {
|
||||
await createTeam(teamName.value, selectedCategory.value!.id);
|
||||
} else {
|
||||
await updateTeam(selectedTeam.value!.id, teamName.value, selectedCategory.value!.id);
|
||||
}
|
||||
|
||||
// サーバーから最新のデータを再取得
|
||||
await fetchTeams();
|
||||
update(); // UIを強制的に更新
|
||||
} catch (e) {
|
||||
error.value = 'チームの保存に失敗しました: $e';
|
||||
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> deleteSelectedTeam() async {
|
||||
if (selectedTeam.value != null) {
|
||||
await deleteTeam(selectedTeam.value!.id);
|
||||
selectedTeam.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
List<NewCategory> getFilteredCategories_old() {
|
||||
//List<User> teamMembers = getCurrentTeamMembers();
|
||||
return categories.where((category) {
|
||||
return isCategoryValid(category, teamMembers);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
List<NewCategory> getFilteredCategories() {
|
||||
if (teamMembers.isEmpty && currentUser.value != null) {
|
||||
// ソロの場合
|
||||
String baseCategory = currentUser.value!.female ? 'ソロ女子' : 'ソロ男子';
|
||||
return categories.where((c) => c.categoryName.startsWith(baseCategory)).toList();
|
||||
} else {
|
||||
bool hasElementaryOrYounger = teamMembers.any(isElementarySchoolOrYounger);
|
||||
String baseCategory = hasElementaryOrYounger ? 'ファミリー' : '一般';
|
||||
return categories.where((c) => c.categoryName.startsWith(baseCategory)).toList();
|
||||
}
|
||||
}
|
||||
|
||||
bool isElementarySchoolOrYounger(User user) {
|
||||
final now = DateTime.now();
|
||||
final age = now.year - user.dateOfBirth!.year;
|
||||
return age <= 12;
|
||||
}
|
||||
|
||||
bool isCategoryValid(NewCategory category, List<User> teamMembers) {
|
||||
int maleCount = teamMembers.where((member) => !member.female).length;
|
||||
int femaleCount = teamMembers.where((member) => member.female).length;
|
||||
int totalCount = teamMembers.length;
|
||||
|
||||
bool isValidGender = category.female ? (femaleCount == totalCount) : true;
|
||||
bool isValidMemberCount = totalCount == category.numOfMember;
|
||||
bool isValidFamily = category.family ? areAllMembersFamily(teamMembers) : true;
|
||||
|
||||
return isValidGender && isValidMemberCount && isValidFamily;
|
||||
}
|
||||
|
||||
bool areAllMembersFamily(List<User> teamMembers) {
|
||||
// 家族かどうかを判断するロジック(例: 同じ姓を持つメンバーが2人以上いる場合は家族とみなす)
|
||||
Set<String> familyNames = teamMembers.map((member) => member.lastname).toSet();
|
||||
return familyNames.length < teamMembers.length;
|
||||
}
|
||||
|
||||
|
||||
Future<void> updateTeamComposition() async {
|
||||
NewCategory? oldCategory = selectedCategory.value;
|
||||
String? oldTime = oldCategory?.time;
|
||||
|
||||
// メンバーリストの最新状態を取得
|
||||
await fetchTeamMembers(selectedTeam.value!.id);
|
||||
|
||||
List<NewCategory> eligibleCategories = [];
|
||||
if (teamMembers.isEmpty || teamMembers.length == 1) {
|
||||
if (currentUser.value != null) {
|
||||
String baseCategory = currentUser.value!.female ? 'ソロ女子' : 'ソロ男子';
|
||||
eligibleCategories = categories.where((c) => c.baseCategory == baseCategory).toList();
|
||||
}
|
||||
} else {
|
||||
bool hasElementaryOrYounger = teamMembers.any(isElementarySchoolOrYounger);
|
||||
String baseCategory = hasElementaryOrYounger ? 'ファミリー' : '一般';
|
||||
eligibleCategories = categories.where((c) => c.baseCategory == baseCategory).toList();
|
||||
}
|
||||
|
||||
// 同じ時間のカテゴリを優先的に選択
|
||||
NewCategory? newCategory = eligibleCategories.firstWhereOrNull((c) => c.time == oldTime);
|
||||
|
||||
if (newCategory == null && eligibleCategories.isNotEmpty) {
|
||||
// 同じ時間のカテゴリがない場合、最初のカテゴリを選択
|
||||
newCategory = eligibleCategories.first;
|
||||
|
||||
// 警告メッセージを表示
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
title: Text('カテゴリ変更の通知'),
|
||||
content: Text('旧カテゴリの時間($oldTime)と一致するカテゴリがないため、${newCategory.time ?? "時間指定なし"}を選択しました。'),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('OK'),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (newCategory != null && oldCategory != newCategory) {
|
||||
selectedCategory.value = newCategory;
|
||||
|
||||
// チームのカテゴリを更新
|
||||
if (selectedTeam.value != null) {
|
||||
try {
|
||||
final updatedTeam = await updateTeam(selectedTeam.value!.id, selectedTeam.value!.teamName, newCategory.id);
|
||||
selectedTeam.value = updatedTeam;
|
||||
|
||||
// チームリストも更新
|
||||
final index = teams.indexWhere((team) => team.id == updatedTeam.id);
|
||||
if (index != -1) {
|
||||
teams[index] = updatedTeam;
|
||||
}
|
||||
|
||||
// エントリーの締め切りチェック
|
||||
bool hasClosedEntries = await checkForClosedEntries(selectedTeam.value!.id);
|
||||
if (hasClosedEntries) {
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
title: Text('警告'),
|
||||
content: Text('締め切りを過ぎたエントリーがあります。カテゴリ変更が必要な場合は事務局にお問い合わせください。'),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('OK'),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await checkAndHandleCategoryChange(oldCategory!, newCategory);
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error updating team category: $e');
|
||||
Get.snackbar('エラー', 'チームのカテゴリ更新に失敗しました');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> checkForClosedEntries(int teamId) async {
|
||||
try {
|
||||
final entries = await _apiService.getEntries();
|
||||
return entries.any((entry) => !isEntryEditable(entry.event));
|
||||
} catch (e) {
|
||||
print('Error checking for closed entries: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool isEntryEditable(Event event) {
|
||||
return DateTime.now().isBefore(event.deadlineDateTime);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Future<void> updateTeamComposition_old() async {
|
||||
NewCategory? oldCategory = selectedCategory.value;
|
||||
String? oldTime = oldCategory?.time;
|
||||
|
||||
// メンバーリストの最新状態を取得
|
||||
await fetchTeamMembers(selectedTeam.value!.id);
|
||||
|
||||
List<NewCategory> eligibleCategories = [];
|
||||
if (teamMembers.isEmpty) {
|
||||
if (currentUser.value != null) {
|
||||
String baseCategory = currentUser.value!.female ? 'ソロ女子' : 'ソロ男子';
|
||||
eligibleCategories = categories.where((c) => c.baseCategory == baseCategory).toList();
|
||||
}
|
||||
} else {
|
||||
bool hasElementaryOrYounger = teamMembers.any(isElementarySchoolOrYounger);
|
||||
String baseCategory = hasElementaryOrYounger ? 'ファミリー' : '一般';
|
||||
eligibleCategories = categories.where((c) => c.baseCategory == baseCategory).toList();
|
||||
}
|
||||
|
||||
// 同じ時間のカテゴリを優先的に選択
|
||||
NewCategory? newCategory = eligibleCategories.firstWhereOrNull((c) => c.time == oldTime);
|
||||
|
||||
if (newCategory == null && eligibleCategories.isNotEmpty) {
|
||||
// 同じ時間のカテゴリがない場合、最初のカテゴリを選択
|
||||
newCategory = eligibleCategories.first;
|
||||
|
||||
// 警告メッセージを表示
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
title: Text('カテゴリ変更の通知'),
|
||||
content: Text('旧カテゴリの時間($oldTime)と一致するカテゴリがないため、${newCategory.time ?? "時間指定なし"}を選択しました。'),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('OK'),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (newCategory != null && oldCategory != newCategory) {
|
||||
selectedCategory.value = newCategory;
|
||||
|
||||
// チームのカテゴリを更新
|
||||
if (selectedTeam.value != null) {
|
||||
try {
|
||||
final updatedTeam = await updateTeam(selectedTeam.value!.id, selectedTeam.value!.teamName, newCategory.id);
|
||||
selectedTeam.value = updatedTeam;
|
||||
|
||||
// チームリストも更新
|
||||
final index = teams.indexWhere((team) => team.id == updatedTeam.id);
|
||||
if (index != -1) {
|
||||
teams[index] = updatedTeam;
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error updating team category: $e');
|
||||
Get.snackbar('エラー', 'チームのカテゴリ更新に失敗しました');
|
||||
}
|
||||
}
|
||||
|
||||
await checkAndHandleCategoryChange(oldCategory!, newCategory);
|
||||
}
|
||||
}
|
||||
|
||||
// メンバーの追加後に呼び出すメソッド
|
||||
Future<void> addMember(User newMember) async {
|
||||
try {
|
||||
await _apiService.createTeamMember(selectedTeam.value!.id, newMember.email, newMember.firstname, newMember.lastname, newMember.dateOfBirth, newMember.female);
|
||||
await updateTeamComposition();
|
||||
} catch (e) {
|
||||
print('Error adding member: $e');
|
||||
Get.snackbar('エラー', 'メンバーの追加に失敗しました');
|
||||
}
|
||||
}
|
||||
|
||||
// メンバーの削除後に呼び出すメソッド
|
||||
Future<void> removeMember(int memberId) async {
|
||||
try {
|
||||
await _apiService.deleteTeamMember(selectedTeam.value!.id, memberId);
|
||||
|
||||
// selectedTeamからメンバーを削除
|
||||
if (selectedTeam.value != null) {
|
||||
selectedTeam.value!.members.removeWhere((member) => member.id == memberId);
|
||||
selectedTeam.refresh();
|
||||
}
|
||||
|
||||
// メンバー削除後にチーム構成を更新
|
||||
await updateTeamComposition();
|
||||
|
||||
// teamMembersを更新
|
||||
await fetchTeamMembers(selectedTeam.value!.id);
|
||||
} catch (e) {
|
||||
print('Error removing member: $e');
|
||||
Get.snackbar('エラー', 'メンバーの削除に失敗しました');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> checkAndHandleCategoryChange(NewCategory oldCategory, NewCategory newCategory) async {
|
||||
try {
|
||||
if (selectedTeam.value == null) {
|
||||
print('No team selected');
|
||||
return;
|
||||
}
|
||||
|
||||
// エントリーの存在を確認
|
||||
bool hasEntries = await checkIfTeamHasEntries(selectedTeam.value!.id);
|
||||
|
||||
if (hasEntries) {
|
||||
bool shouldCreateNewTeam = await Get.dialog<bool>(
|
||||
CategoryChangeDialog(
|
||||
oldCategory: oldCategory.categoryName,
|
||||
newCategory: newCategory.categoryName,
|
||||
),
|
||||
) ?? false;
|
||||
|
||||
if (shouldCreateNewTeam) {
|
||||
await createNewTeamWithCurrentMembers();
|
||||
} else {
|
||||
await updateEntriesWithNewCategory();
|
||||
}
|
||||
}else{
|
||||
// エントリーが存在しない場合は、カテゴリの更新のみを行う
|
||||
await updateTeamCategory(newCategory);
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error in checkAndHandleCategoryChange: $e');
|
||||
Get.snackbar('エラー', 'カテゴリ変更の処理中にエラーが発生しました');
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> checkIfTeamHasEntries(int teamId) async {
|
||||
try {
|
||||
final entries = await _apiService.getTeamEntries(teamId);
|
||||
return entries.isNotEmpty;
|
||||
} catch (e) {
|
||||
print('Error checking if team has entries: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateTeamCategory(NewCategory newCategory) async {
|
||||
try {
|
||||
if (selectedTeam.value != null) {
|
||||
final updatedTeam = await updateTeam(selectedTeam.value!.id, selectedTeam.value!.teamName, newCategory.id);
|
||||
selectedTeam.value = updatedTeam;
|
||||
|
||||
// チームリストも更新
|
||||
final index = teams.indexWhere((team) => team.id == updatedTeam.id);
|
||||
if (index != -1) {
|
||||
teams[index] = updatedTeam;
|
||||
}
|
||||
|
||||
Get.snackbar('成功', 'チームのカテゴリを更新しました');
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error updating team category: $e');
|
||||
Get.snackbar('エラー', 'チームのカテゴリ更新に失敗しました');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchTeamEntries(int teamId) async {
|
||||
try {
|
||||
final fetchedEntries = await _apiService.getEntries();
|
||||
teamEntries.assignAll(fetchedEntries);
|
||||
} catch (e) {
|
||||
print('Error fetching team entries: $e');
|
||||
teamEntries.clear(); // エラーが発生した場合、エントリーリストをクリア
|
||||
//Get.snackbar('エラー', 'チームのエントリー取得に失敗しました');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> createNewTeamWithCurrentMembers() async {
|
||||
String newTeamName = '${selectedTeam.value!.teamName}_${DateTime.now().millisecondsSinceEpoch}';
|
||||
|
||||
try {
|
||||
Team newTeam = await _apiService.createTeam(newTeamName, selectedCategory.value!.id);
|
||||
|
||||
for (var member in teamMembers) {
|
||||
await _apiService.createTeamMember(
|
||||
newTeam.id,
|
||||
member.email,
|
||||
member.firstname,
|
||||
member.lastname,
|
||||
member.dateOfBirth,
|
||||
member.female,
|
||||
);
|
||||
}
|
||||
|
||||
Get.offNamed(AppPages.TEAM_DETAIL, arguments: {'mode': 'edit', 'team': newTeam});
|
||||
} catch (e) {
|
||||
print('Error creating new team: $e');
|
||||
Get.snackbar('エラー', '新しいチームの作成に失敗しました');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateEntriesWithNewCategory() async {
|
||||
try {
|
||||
for (var entry in teamEntries) {
|
||||
//await _apiService.updateEntry(entry.id, selectedCategory.value!.id);
|
||||
final updatedEntry = await _apiService.updateEntry(
|
||||
entry.id,
|
||||
entry.team.id,
|
||||
entry.event.id,
|
||||
selectedCategory.value!.id,
|
||||
entry.date!,
|
||||
entry.zekkenNumber,
|
||||
);
|
||||
}
|
||||
Get.snackbar('成功', 'エントリーのカテゴリを更新しました');
|
||||
} catch (e) {
|
||||
print('Error updating entries: $e');
|
||||
Get.snackbar('エラー', 'エントリーの更新に失敗しました');
|
||||
}
|
||||
}
|
||||
|
||||
void updateCurrentUserGender(bool isFemale) {
|
||||
if (currentUser.value != null) {
|
||||
currentUser.value!.female = isFemale;
|
||||
updateTeamComposition();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
263
lib/pages/team/team_detail_page.dart
Normal file
263
lib/pages/team/team_detail_page.dart
Normal file
@ -0,0 +1,263 @@
|
||||
// lib/pages/team/team_detail_page.dart
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gifunavi/pages/team/team_controller.dart';
|
||||
import 'package:gifunavi/routes/app_pages.dart';
|
||||
import 'package:gifunavi/model/team.dart';
|
||||
import 'package:gifunavi/model/category.dart';
|
||||
|
||||
class TeamDetailPage extends StatefulWidget {
|
||||
const TeamDetailPage({super.key});
|
||||
|
||||
@override
|
||||
_TeamDetailPageState createState() => _TeamDetailPageState();
|
||||
}
|
||||
|
||||
class _TeamDetailPageState extends State<TeamDetailPage> {
|
||||
late TeamController controller;
|
||||
late TextEditingController _teamNameController = TextEditingController();
|
||||
final RxString mode = ''.obs;
|
||||
final Rx<Team?> team = Rx<Team?>(null);
|
||||
Worker? _teamNameWorker;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller = Get.find<TeamController>();
|
||||
_teamNameController = TextEditingController();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_initializeData();
|
||||
});
|
||||
}
|
||||
|
||||
void _initializeData() {
|
||||
final args = Get.arguments;
|
||||
if (args != null && args is Map<String, dynamic>) {
|
||||
mode.value = args['mode'] as String? ?? '';
|
||||
team.value = args['team'] as Team?;
|
||||
|
||||
if (mode.value == 'edit' && team.value != null) {
|
||||
controller.setSelectedTeam(team.value!);
|
||||
controller.fetchTeamMembers(team.value!.id); // メンバーを取得
|
||||
} else {
|
||||
controller.resetForm();
|
||||
}
|
||||
} else {
|
||||
// 引数がない場合は新規作成モードとして扱う
|
||||
mode.value = 'new';
|
||||
controller.resetForm();
|
||||
}
|
||||
|
||||
_teamNameController.text = controller.teamName.value;
|
||||
|
||||
// Use ever instead of direct listener
|
||||
_teamNameWorker = ever(controller.teamName, (String value) {
|
||||
if (_teamNameController.text != value) {
|
||||
_teamNameController.text = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_teamNameWorker?.dispose();
|
||||
_teamNameController.dispose();
|
||||
//controller.resetForm(); // Reset the form when leaving the page
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
Get.arguments['mode'] == 'new' ? '新規チーム作成' : 'チーム詳細'),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
controller.cleanupForNavigation();
|
||||
Get.back();
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.save),
|
||||
onPressed: () async {
|
||||
try {
|
||||
await controller.saveTeam();
|
||||
Get.back();
|
||||
} catch (e) {
|
||||
Get.snackbar('エラー', e.toString(),
|
||||
snackPosition: SnackPosition.BOTTOM);
|
||||
}
|
||||
},
|
||||
),
|
||||
Obx(() {
|
||||
if (mode.value == 'edit') {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () async {
|
||||
try {
|
||||
await controller.deleteSelectedTeam();
|
||||
Get.back();
|
||||
} catch (e) {
|
||||
Get.snackbar('エラー', e.toString(),
|
||||
snackPosition: SnackPosition.BOTTOM);
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}),
|
||||
],
|
||||
),
|
||||
body: Obx(() {
|
||||
if (controller.isLoading.value) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
final filteredCategories = controller.getFilteredCategories();
|
||||
final categoriesToDisplay = filteredCategories.isEmpty
|
||||
? controller.categories
|
||||
: filteredCategories;
|
||||
|
||||
// 選択されているカテゴリが表示リストに含まれていない場合、最初の項目を選択する
|
||||
if (controller.selectedCategory.value == null ||
|
||||
!categoriesToDisplay.contains(controller.selectedCategory.value)) {
|
||||
controller.updateCategory(categoriesToDisplay.isNotEmpty
|
||||
? categoriesToDisplay.first
|
||||
: null);
|
||||
}
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Obx(() =>
|
||||
TextFormField(
|
||||
decoration: const InputDecoration(labelText: 'チーム名'),
|
||||
controller: TextEditingController(text: controller
|
||||
.selectedTeam.value?.teamName ?? ''),
|
||||
onChanged: (value) => controller.updateTeamName(value),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (categoriesToDisplay.isEmpty)
|
||||
const Text('カテゴリデータを読み込めませんでした。',
|
||||
style: TextStyle(color: Colors.red))
|
||||
else
|
||||
Obx(() =>
|
||||
DropdownButtonFormField<NewCategory>(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'カテゴリ'),
|
||||
value: controller.selectedCategory.value,
|
||||
items: categoriesToDisplay.map((category) =>
|
||||
DropdownMenuItem(
|
||||
value: category,
|
||||
child: Text(category.categoryName),
|
||||
)).toList(),
|
||||
onChanged: (value) => controller.updateCategory(value),
|
||||
),
|
||||
),
|
||||
|
||||
if (filteredCategories.isEmpty &&
|
||||
controller.categories.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
'注意: チーム構成に適さないカテゴリーも表示されています。',
|
||||
style: TextStyle(color: Colors.orange[700], fontSize: 12),
|
||||
),
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Text('所有者: ${controller.currentUser.value?.email ??
|
||||
"未設定"}'),
|
||||
),
|
||||
|
||||
if (mode.value == 'edit') ...[
|
||||
const SizedBox(height: 24),
|
||||
const Text('メンバーリスト', style: TextStyle(
|
||||
fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 8),
|
||||
Obx(() {
|
||||
final teamMembers = controller.teamMembers;
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: teamMembers.length,
|
||||
itemBuilder: (context, index) {
|
||||
final member = teamMembers[index];
|
||||
|
||||
final isDummyEmail = member.email?.startsWith(
|
||||
'dummy_') ?? false;
|
||||
final isCurrentUser = member.email ==
|
||||
controller.currentUser.value?.email;
|
||||
final displayName = isDummyEmail
|
||||
? '${member.lastname} ${member.firstname}'
|
||||
: member.isActive
|
||||
? '${member.lastname} ${member.firstname}'
|
||||
: member.email?.split('@')[0] ?? ''; //(未承認)';
|
||||
|
||||
return ListTile(
|
||||
title: Text(
|
||||
'$displayName${isCurrentUser ? ' (自分)' : ''}'),
|
||||
subtitle: isDummyEmail
|
||||
? const Text('Email未設定')
|
||||
: null,
|
||||
|
||||
onTap: isCurrentUser ? null : () async {
|
||||
final result = await Get.toNamed(
|
||||
AppPages.MEMBER_DETAIL,
|
||||
arguments: {
|
||||
'mode': 'edit',
|
||||
'member': member,
|
||||
'teamId': controller.selectedTeam.value?.id
|
||||
},
|
||||
);
|
||||
|
||||
await controller.fetchTeamMembers(
|
||||
controller.selectedTeam.value!.id);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
child: const Text('新しいメンバーを追加'),
|
||||
onPressed: () async {
|
||||
final result = await Get.toNamed(
|
||||
AppPages.MEMBER_DETAIL,
|
||||
arguments: {
|
||||
'mode': 'new',
|
||||
'teamId': controller.selectedTeam.value?.id
|
||||
},
|
||||
);
|
||||
if (result == true && controller.selectedTeam.value != null) {
|
||||
await controller.fetchTeamMembers(controller.selectedTeam.value!.id);
|
||||
controller.teamMembers.refresh(); // 明示的に更新を通知
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
57
lib/pages/team/team_list_page.dart
Normal file
57
lib/pages/team/team_list_page.dart
Normal file
@ -0,0 +1,57 @@
|
||||
// lib/pages/team/team_list_page.dart
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gifunavi/pages/team/team_controller.dart';
|
||||
import 'package:gifunavi/routes/app_pages.dart';
|
||||
|
||||
class TeamListPage extends GetWidget<TeamController> {
|
||||
const TeamListPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('チーム管理'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () => Get.toNamed(AppPages.TEAM_DETAIL, arguments: {'mode': 'new'}),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Obx(() {
|
||||
if (controller.isLoading.value) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (controller.error.value.isNotEmpty) {
|
||||
return Center(child: Text(controller.error.value));
|
||||
} else if (controller.teams.isEmpty) {
|
||||
return const Center(child: Text('チームがありません'));
|
||||
} else {
|
||||
return RefreshIndicator(
|
||||
onRefresh: controller.fetchTeams,
|
||||
child: ListView.builder(
|
||||
itemCount: controller.teams.length,
|
||||
itemBuilder: (context, index) {
|
||||
final team = controller.teams[index];
|
||||
return ListTile(
|
||||
title: Text(team.teamName),
|
||||
subtitle: Text('${team.category.categoryName} '),
|
||||
onTap: () async {
|
||||
await Get.toNamed(
|
||||
AppPages.TEAM_DETAIL,
|
||||
arguments: {'mode': 'edit', 'team': team},
|
||||
);
|
||||
controller.fetchTeams();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
31
lib/provider/auth_provider.dart
Normal file
31
lib/provider/auth_provider.dart
Normal file
@ -0,0 +1,31 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:gifunavi/model/auth_user.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
final authUserStateProvider =
|
||||
StateNotifierProvider<AuthUserState, AuthUser>((ref) {
|
||||
return AuthUserState();
|
||||
});
|
||||
|
||||
class AuthUserState extends StateNotifier<AuthUser> {
|
||||
AuthUserState() : super(AuthUser());
|
||||
|
||||
Future<void> saveToDevice(String val) async {
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString("user_token", val);
|
||||
}
|
||||
|
||||
Future<String?> tokenFromDevice() async {
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getString("user_token");
|
||||
}
|
||||
|
||||
Future<void> addLogin(AuthUser user) async {
|
||||
state = user;
|
||||
await saveToDevice(user.auth_token!);
|
||||
}
|
||||
|
||||
void removeLogin() {
|
||||
state = AuthUser();
|
||||
}
|
||||
}
|
||||
24
lib/provider/cached_tile_provider.dart
Normal file
24
lib/provider/cached_tile_provider.dart
Normal file
@ -0,0 +1,24 @@
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'dart:io';
|
||||
|
||||
class CacheProvider {
|
||||
static Future<void> initialize() async {
|
||||
final cacheDirectory = await getTemporaryDirectory();
|
||||
final mapCacheDir = Directory('${cacheDirectory.path}/map_cache');
|
||||
|
||||
if (!await mapCacheDir.exists()) {
|
||||
await mapCacheDir.create(recursive: true);
|
||||
}
|
||||
|
||||
// ここに追加の初期化ロジックを記述できます
|
||||
// 例:キャッシュサイズの制限設定、有効期限の設定など
|
||||
}
|
||||
|
||||
static Future<File> getCachedTile(String url) async {
|
||||
final cacheDirectory = await getTemporaryDirectory();
|
||||
final fileName = url.split('/').last;
|
||||
return File('${cacheDirectory.path}/map_cache/$fileName');
|
||||
}
|
||||
|
||||
// 他のキャッシュ関連メソッドをここに追加できます
|
||||
}
|
||||
22
lib/provider/game_state_provider.dart
Normal file
22
lib/provider/game_state_provider.dart
Normal file
@ -0,0 +1,22 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:gifunavi/model/game_state_instance.dart';
|
||||
|
||||
final gameStateNotifierProvider =
|
||||
StateNotifierProvider<GameStaticState, GameInsStatetance>((ref) {
|
||||
return GameStaticState();
|
||||
});
|
||||
|
||||
class GameStaticState extends StateNotifier<GameInsStatetance> {
|
||||
GameStaticState() : super(GameInsStatetance());
|
||||
|
||||
@override
|
||||
GameInsStatetance get state => super.state;
|
||||
|
||||
void startGame(GameInsStatetance gi) {
|
||||
state = gi;
|
||||
}
|
||||
|
||||
void doCheckin() {}
|
||||
|
||||
void makeGoal() {}
|
||||
}
|
||||
18
lib/provider/map_state_provider.dart
Normal file
18
lib/provider/map_state_provider.dart
Normal file
@ -0,0 +1,18 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:gifunavi/model/map_state_instance.dart';
|
||||
|
||||
final mapStateNotifierProvider =
|
||||
StateNotifierProvider<MapState, MapStateInstance>((ref) {
|
||||
return MapState();
|
||||
});
|
||||
|
||||
class MapState extends StateNotifier<MapStateInstance> {
|
||||
MapState() : super(MapStateInstance());
|
||||
|
||||
@override
|
||||
MapStateInstance get state => super.state;
|
||||
|
||||
void startGame(MapStateInstance mi) {
|
||||
state = mi;
|
||||
}
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
https://rogaining.sumasen.net/api/perf_main/
|
||||
|
||||
https://rogaining.sumasen.net/api/allgifuareas/?perf=9
|
||||
|
||||
https://maps.googleapis.com/maps/api/directions/json?destination=&mode=walking&waypoints=optimize:false|&origin=&key=AIzaSyAUBI1ablMKuJwGj2-kSuEhvYxvB1A-mOE
|
||||
|
||||
https://rogaining.sumasen.net/api/login/
|
||||
|
||||
https://rogaining.sumasen.net/api/userdetials?user_id=67
|
||||
|
||||
https://rogaining.sumasen.net/api/locsext/
|
||||
|
||||
https://rogaining.sumasen.net/api/inbound?rog=True&grp=各務原&ln1=136.80345200298189&la1=35.29461396815819&ln2=136.80345200298189&la2=35.50765110021806&ln3=136.96420197631517&la3=35.50765110021806&ln4=136.96420197631517&la4=35.29461396815819
|
||||
|
||||
https://rogaining.sumasen.net/api/cats/?ln1=136.80345200298189&la1=35.29461396815819&ln2=136.80345200298189&la2=35.50765110021806&ln3=136.96420197631517&la3=35.50765110021806&ln4=136.96420197631517&la4=35.29461396815819
|
||||
|
||||
https://natnats.mobilous.com/start_from_rogapp
|
||||
|
||||
https://natnats.mobilous.com/checkin_from_rogapp
|
||||
|
||||
https://maps.googleapis.com/maps/api/directions/json?destination=&mode=walking&waypoints=optimize:false|&origin=35.3997059, 136.8434708&key=AIzaSyAUBI1ablMKuJwGj2-kSuEhvYxvB1A-mOE
|
||||
|
||||
https://natnats.mobilous.com/checkin_from_rogapp
|
||||
|
||||
https://maps.googleapis.com/maps/api/directions/json?destination=35.3995266, 136.8436693&mode=walking&waypoints=optimize:false|&origin=35.3997059, 136.8434708&key=AIzaSyAUBI1ablMKuJwGj2-kSuEhvYxvB1A-mOE
|
||||
|
||||
https://natnats.mobilous.com/goal_from_rogapp
|
||||
|
||||
https://maps.googleapis.com/maps/api/directions/json?destination=&mode=walking&waypoints=optimize:false|&origin=&key=AIzaSyAUBI1ablMKuJwGj2-kSuEhvYxvB1A-mOE
|
||||
|
||||
https://maps.googleapis.com/maps/api/directions/json?destination=&mode=walking&waypoints=optimize:false|&origin=&key=AIzaSyAUBI1ablMKuJwGj2-kSuEhvYxvB1A-mOE
|
||||
|
||||
https://maps.googleapis.com/maps/api/directions/json?destination=&mode=walking&waypoints=optimize:false|&origin=&key=AIzaSyAUBI1ablMKuJwGj2-kSuEhvYxvB1A-mOE
|
||||
@ -1,36 +1,44 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get/get_navigation/src/routes/get_route.dart';
|
||||
import 'package:rogapp/pages/camera/camera_page.dart';
|
||||
import 'package:rogapp/pages/category/category_page.dart';
|
||||
import 'package:rogapp/pages/changepassword/change_password_page.dart';
|
||||
import 'package:rogapp/pages/city/city_page.dart';
|
||||
import 'package:rogapp/pages/destination/destination_binding.dart';
|
||||
import 'package:rogapp/pages/destination/destination_page.dart';
|
||||
import 'package:rogapp/pages/history/history_page.dart';
|
||||
import 'package:rogapp/pages/home/home_binding.dart';
|
||||
import 'package:rogapp/pages/home/home_page.dart';
|
||||
import 'package:gifunavi/pages/changepassword/change_password_page.dart';
|
||||
import 'package:gifunavi/pages/city/city_page.dart';
|
||||
import 'package:gifunavi/pages/gps/gps_page.dart';
|
||||
import 'package:gifunavi/pages/history/history_page.dart';
|
||||
import 'package:gifunavi/pages/home/home_binding.dart';
|
||||
import 'package:gifunavi/pages/home/home_page.dart';
|
||||
|
||||
import 'package:rogapp/pages/index/index_page.dart';
|
||||
import 'package:rogapp/pages/landing/landing_page.dart';
|
||||
import 'package:rogapp/pages/loading/loading_page.dart';
|
||||
import 'package:rogapp/pages/login/login_page.dart';
|
||||
import 'package:rogapp/pages/mainperf/mainperf_page.dart';
|
||||
import 'package:rogapp/pages/permission/permission.dart';
|
||||
import 'package:rogapp/pages/progress/progress.dart';
|
||||
import 'package:rogapp/pages/register/register_page.dart';
|
||||
import 'package:rogapp/pages/search/search_binding.dart';
|
||||
import 'package:rogapp/pages/search/search_page.dart';
|
||||
import 'package:rogapp/pages/subperf/subperf_page.dart';
|
||||
import 'package:rogapp/spa/spa_binding.dart';
|
||||
import 'package:rogapp/spa/spa_page.dart';
|
||||
import 'package:gifunavi/pages/index/index_page.dart';
|
||||
import 'package:gifunavi/pages/landing/landing_page.dart';
|
||||
import 'package:gifunavi/pages/loading/loading_page.dart';
|
||||
import 'package:gifunavi/pages/login/login_page.dart';
|
||||
import 'package:gifunavi/pages/progress/progress.dart';
|
||||
import 'package:gifunavi/pages/register/register_page.dart';
|
||||
import 'package:gifunavi/pages/search/search_binding.dart';
|
||||
import 'package:gifunavi/pages/search/search_page.dart';
|
||||
import 'package:gifunavi/pages/settings/settings_page.dart';
|
||||
import 'package:gifunavi/pages/settings/settings_binding.dart';
|
||||
import 'package:gifunavi/pages/debug/debug_page.dart';
|
||||
import 'package:gifunavi/pages/debug/debug_binding.dart';
|
||||
import 'package:gifunavi/widgets/permission_handler_screen.dart';
|
||||
|
||||
import 'package:gifunavi/pages/team/team_binding.dart';
|
||||
import 'package:gifunavi/pages/team/team_list_page.dart';
|
||||
import 'package:gifunavi/pages/team/team_detail_page.dart';
|
||||
import 'package:gifunavi/pages/team/member_binding.dart';
|
||||
import 'package:gifunavi/pages/team/member_detail_page.dart';
|
||||
import 'package:gifunavi/pages/entry/entry_list_page.dart';
|
||||
import 'package:gifunavi/pages/entry/entry_detail_page.dart';
|
||||
import 'package:gifunavi/pages/entry/entry_binding.dart';
|
||||
|
||||
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';
|
||||
|
||||
part 'app_routes.dart';
|
||||
|
||||
class AppPages {
|
||||
|
||||
// ignore: constant_identifier_names
|
||||
static const INITIAL = Routes.INDEX;
|
||||
static const INDEX = Routes.INDEX;
|
||||
// ignore: constant_identifier_names
|
||||
static const SPA = Routes.SPA;
|
||||
static const LANDING = Routes.LANDING;
|
||||
@ -41,6 +49,7 @@ class AppPages {
|
||||
static const DESTINATION_MAP = Routes.DESTINATION_MAP;
|
||||
static const HOME = Routes.HOME;
|
||||
static const PERMISSION = Routes.PERMISSION;
|
||||
//static const PERMISSION = Routes.HOME;
|
||||
static const SEARCH = Routes.SEARCH;
|
||||
static const MAINPERF = Routes.MAINPERF;
|
||||
static const SUBPERF = Routes.SUBPERF;
|
||||
@ -50,28 +59,24 @@ class AppPages {
|
||||
static const CAMERA_PAGE = Routes.CAMERA_PAGE;
|
||||
static const PROGRESS = Routes.PROGRESS;
|
||||
static const HISTORY = Routes.HISTORY;
|
||||
static const GPS = Routes.GPS;
|
||||
static const SETTINGS = Routes.SETTINGS;
|
||||
static const DEBUG = Routes.DEBUG;
|
||||
static const TEAM_LIST = Routes.TEAM_LIST;
|
||||
static const TEAM_DETAIL = Routes.TEAM_DETAIL;
|
||||
static const MEMBER_DETAIL = Routes.MEMBER_DETAIL;
|
||||
static const ENTRY_LIST = Routes.ENTRY_LIST;
|
||||
static const ENTRY_DETAIL = Routes.ENTRY_DETAIL;
|
||||
static const EVENT_ENTRY = Routes.EVENT_ENTRIES;
|
||||
static const USER_DETAILS_EDIT = Routes.USER_DETAILS_EDIT;
|
||||
|
||||
|
||||
static final routes = [
|
||||
// GetPage(
|
||||
// name: Routes.HOME,
|
||||
// page: () => HomePage(),
|
||||
// binding: HomeBinding(),
|
||||
// ),
|
||||
// GetPage(
|
||||
// name: Routes.MAP,
|
||||
// page: () => MapPage(),
|
||||
// binding: MapBinding(),
|
||||
// )
|
||||
GetPage(
|
||||
name: Routes.INDEX,
|
||||
page: () => IndexPage(),
|
||||
page: () => const IndexPage(),
|
||||
//binding: IndexBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.SPA,
|
||||
page: () => const SpaPage(),
|
||||
binding: SpaBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.LANDING,
|
||||
page: () => const LandingPage(),
|
||||
@ -79,29 +84,19 @@ class AppPages {
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.LOGIN,
|
||||
page: () => LoginPage(),
|
||||
page: () => const LoginPage(),
|
||||
//binding: SpaBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.REGISTER,
|
||||
page: () => RegisterPage(),
|
||||
page: () => const RegisterPage(),
|
||||
//binding: SpaBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.TRAVEL,
|
||||
page: () => DestnationPage(),
|
||||
binding: DestinationBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.LOADING,
|
||||
page: () => const LoadingPage(),
|
||||
//binding: DestinationBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.DESTINATION_MAP,
|
||||
page: () => DestnationPage(),
|
||||
//binding: DestinationBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.HOME,
|
||||
page: () => const HomePage(),
|
||||
@ -109,37 +104,21 @@ class AppPages {
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.PERMISSION,
|
||||
page: () => const PermissionHandlerScreen(),
|
||||
page: () => const PermissionHandlerScreen(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.SEARCH,
|
||||
page: () => SearchPage(),
|
||||
binding: SearchBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.MAINPERF,
|
||||
page: () => MainPerfPage(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.SUBPERF,
|
||||
page: () => SubPerfPage(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.CITY,
|
||||
page: () => const CityPage(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.CATEOGORY,
|
||||
page: () => const CategoryPage(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.CHANGE_PASSWORD,
|
||||
page: () => ChangePasswordPage(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.CAMERA_PAGE,
|
||||
page: () => CameraPage(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.PROGRESS,
|
||||
page: () => const ProgressPage(),
|
||||
@ -147,6 +126,55 @@ class AppPages {
|
||||
GetPage(
|
||||
name: Routes.HISTORY,
|
||||
page: () => const HistoryPage(),
|
||||
)
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.GPS,
|
||||
page: () => const GpsPage(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.SETTINGS,
|
||||
page: () => const SettingsPage(),
|
||||
binding: SettingsBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.DEBUG,
|
||||
page: () => const DebugPage(),
|
||||
binding: DebugBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.TEAM_LIST,
|
||||
page: () => const TeamListPage(),
|
||||
binding: TeamBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.TEAM_DETAIL,
|
||||
page: () => const TeamDetailPage(),
|
||||
binding: TeamBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.MEMBER_DETAIL,
|
||||
page: () => const MemberDetailPage(),
|
||||
binding: MemberBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.ENTRY_LIST,
|
||||
page: () => const EntryListPage(),
|
||||
binding: EntryBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.ENTRY_DETAIL,
|
||||
page: () => const EntryDetailPage(),
|
||||
binding: EntryBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.EVENT_ENTRIES,
|
||||
page: () => const EventEntriesPage(),
|
||||
binding: EventEntriesBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.USER_DETAILS_EDIT,
|
||||
page: () => const UserDetailsEditPage(),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -25,4 +25,17 @@ abstract class Routes {
|
||||
static const CAMERA_PAGE = '/camera_page';
|
||||
static const PROGRESS = '/progress';
|
||||
static const HISTORY = '/history';
|
||||
static const GPS = '/gp';
|
||||
static const SETTINGS = '/settings';
|
||||
static const DEBUG = '/debug';
|
||||
|
||||
static const TEAM_LIST = '/team-list';
|
||||
static const TEAM_DETAIL = '/team-detail';
|
||||
static const MEMBER_DETAIL = '/member-detail';
|
||||
static const ENTRY_LIST = '/entry-list';
|
||||
static const ENTRY_DETAIL = '/entry-detail';
|
||||
|
||||
static const EVENT_ENTRIES = '/event-entries';
|
||||
static const USER_DETAILS_EDIT = '/user-details-edit';
|
||||
|
||||
}
|
||||
|
||||
55
lib/services/DatabaseService.dart
Normal file
55
lib/services/DatabaseService.dart
Normal file
@ -0,0 +1,55 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:gifunavi/model/destination.dart';
|
||||
import 'package:gifunavi/utils/database_helper.dart';
|
||||
|
||||
class DatabaseService {
|
||||
// Private constructor
|
||||
DatabaseService._privateConstructor();
|
||||
|
||||
// Static instance
|
||||
static final DatabaseService _instance =
|
||||
DatabaseService._privateConstructor();
|
||||
|
||||
// Factory constructor to return the instance
|
||||
factory DatabaseService() {
|
||||
return _instance;
|
||||
}
|
||||
|
||||
// StreamController for updates
|
||||
final StreamController<List<Destination>> _dbUpdateController =
|
||||
StreamController<List<Destination>>.broadcast();
|
||||
|
||||
// Getter for the stream
|
||||
Stream<List<Destination>> get destinationUpdatesStream =>
|
||||
_dbUpdateController.stream;
|
||||
|
||||
// Method to fetch destinations
|
||||
Future<List<Destination>> fetchDestinations() async {
|
||||
// Your database fetch logic here
|
||||
List<Destination> destinations = await _fetchFromDb();
|
||||
_dbUpdateController.add(destinations);
|
||||
return destinations;
|
||||
}
|
||||
|
||||
// Method to update the database and emit an update through the stream
|
||||
Future<void> updateDatabase() async {
|
||||
// Your update logic here
|
||||
|
||||
// After updating, fetch the updated list and add it to the stream
|
||||
fetchDestinations();
|
||||
}
|
||||
|
||||
// Method to fetch data from the database (placeholder)
|
||||
Future<List<Destination>> _fetchFromDb() async {
|
||||
// Simulate fetching data with a delay
|
||||
DatabaseHelper db = DatabaseHelper.instance;
|
||||
List<Destination> destinations = await db.getDestinations();
|
||||
return destinations; // Replace with your actual fetch operation
|
||||
}
|
||||
|
||||
// Dispose method to close the stream controller
|
||||
void dispose() {
|
||||
_dbUpdateController.close();
|
||||
}
|
||||
}
|
||||
@ -1,24 +1,22 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:rogapp/utils/const.dart';
|
||||
import 'package:gifunavi/utils/const.dart';
|
||||
|
||||
|
||||
class ActionService{
|
||||
|
||||
static Future<Map<String, dynamic>> makeAction(int userId, int locationId, bool wanttogo, bool like, bool checkin) async {
|
||||
print("----- action is ---- $like-- $wanttogo-- $checkin");
|
||||
class ActionService {
|
||||
static Future<Map<String, dynamic>> makeAction(int userId, int locationId,
|
||||
bool wanttogo, bool like, bool checkin) async {
|
||||
//print("----- action is ---- $like-- $wanttogo-- $checkin");
|
||||
Map<String, dynamic> cats = {};
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = "$serverUrl/api/makeaction/?user_id=$userId&location_id=$locationId&wanttogo=$wanttogo&like=$like&checkin=$checkin";
|
||||
String url =
|
||||
"$serverUrl/api/makeaction/?user_id=$userId&location_id=$locationId&wanttogo=$wanttogo&like=$like&checkin=$checkin";
|
||||
//String url = "http://localhost:8100/api/makeaction/?user_id=${user_id}&location_id=${location_id}&wanttogo=${wanttogo}&like=${like}&checkin=${checkin}";
|
||||
print('++++++++$url');
|
||||
print("url is ------ $url");
|
||||
final http.Response response = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
}
|
||||
);
|
||||
//print('++++++++$url');
|
||||
//print("url is ------ $url");
|
||||
final http.Response response =
|
||||
await http.get(Uri.parse(url), headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
});
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
cats = json.decode(utf8.decode(response.bodyBytes));
|
||||
@ -26,27 +24,23 @@ class ActionService{
|
||||
return cats;
|
||||
}
|
||||
|
||||
|
||||
static Future<List<dynamic>?> userAction(int userId, int locationId) async {
|
||||
static Future<List<dynamic>?> userAction(int userId, int locationId) async {
|
||||
List<dynamic> cats = [];
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = '$serverUrl/api/useraction/?user_id=$userId&location_id=$locationId';
|
||||
print('++++++++$url');
|
||||
String url =
|
||||
'$serverUrl/api/useraction/?user_id=$userId&location_id=$locationId';
|
||||
//print('++++++++$url');
|
||||
//String url = 'http://localhost:8100/api/useraction/?user_id=${user_id}&location_id=${location_id}';
|
||||
final response = await http.get(Uri.parse(url),
|
||||
final response = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
|
||||
cats = json.decode(utf8.decode(response.bodyBytes));
|
||||
}
|
||||
return cats;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
705
lib/services/api_service.dart
Normal file
705
lib/services/api_service.dart
Normal file
@ -0,0 +1,705 @@
|
||||
// lib/services/api_service.dart
|
||||
import 'package:get/get.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'package:gifunavi/model/entry.dart';
|
||||
import 'package:gifunavi/model/event.dart';
|
||||
import 'package:gifunavi/model/team.dart';
|
||||
import 'package:gifunavi/model/category.dart';
|
||||
import 'package:gifunavi/model/user.dart';
|
||||
import 'package:gifunavi/pages/index/index_controller.dart';
|
||||
import '../utils/const.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
|
||||
|
||||
class ApiService extends GetxService{
|
||||
static ApiService get to => Get.find<ApiService>();
|
||||
String serverUrl = '';
|
||||
String baseUrl = '';
|
||||
String token = 'your-auth-token';
|
||||
|
||||
Future<ApiService> init() async {
|
||||
try {
|
||||
// ここで必要な初期化処理を行う
|
||||
serverUrl = ConstValues.currentServer();
|
||||
baseUrl = '$serverUrl/api';
|
||||
//await Future.delayed(Duration(seconds: 2)); // 仮の遅延(実際の初期化処理に置き換えてください)
|
||||
print('ApiService initialized successfully');
|
||||
return this;
|
||||
} catch(e) {
|
||||
print('Error in ApiService initialization: $e');
|
||||
rethrow; // エラーを再スローして、呼び出し元で処理できるようにする
|
||||
//return this;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
このメソッドは以下のように動作します:
|
||||
|
||||
まず、渡された type パラメータに基づいて、どのクラスのフィールドを扱っているかを判断します。
|
||||
次に、クラス内で fieldName に対応する期待される型を返します。
|
||||
クラスや フィールド名が予期されていないものである場合、'Unknown' または 'Unknown Type' を返します。
|
||||
|
||||
このメソッドを ApiService クラスに追加することで、_printDataComparison メソッドは各フィールドの期待される型を正確に表示できるようになります。
|
||||
さらに、このメソッドを使用することで、API レスポンスのデータ型が期待と異なる場合に簡単に検出できるようになります。例えば、Category クラスの duration フィールドが整数型(秒数)で期待されているのに対し、API が文字列を返した場合、すぐに問題を特定できます。
|
||||
注意点として、API のレスポンス形式が変更された場合や、新しいフィールドが追加された場合は、このメソッドも更新する必要があります。そのため、API の変更とクライアントサイドのコードの同期を保つことが重要です。
|
||||
*/
|
||||
|
||||
String getToken()
|
||||
{
|
||||
// IndexControllerの初期化を待つ
|
||||
final indexController = Get.find<IndexController>();
|
||||
|
||||
if (indexController.currentUser.isNotEmpty) {
|
||||
token = indexController.currentUser[0]['token'] ?? '';
|
||||
print("Get token = $token");
|
||||
}else{
|
||||
token = "";
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
Future<List<Team>> getTeams() async {
|
||||
init();
|
||||
getToken();
|
||||
|
||||
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();
|
||||
|
||||
try {
|
||||
final response = await http.get(
|
||||
Uri.parse('$baseUrl/categories/'),
|
||||
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> categoriesJson = json.decode(decodedResponse);
|
||||
|
||||
List<NewCategory> categories = [];
|
||||
for (var categoryJson in categoriesJson) {
|
||||
try {
|
||||
//print('\nCategory Data:');
|
||||
//_printDataComparison(categoryJson, NewCategory);
|
||||
categories.add(NewCategory.fromJson(categoryJson));
|
||||
}catch(e){
|
||||
print('Error parsing category: $e');
|
||||
print('Problematic JSON: $categoryJson');
|
||||
}
|
||||
}
|
||||
|
||||
return categories;
|
||||
} else {
|
||||
throw Exception(
|
||||
'Failed to load categories. Status code: ${response.statusCode}');
|
||||
}
|
||||
}catch(e, stackTrace){
|
||||
print('Error in getCategories: $e');
|
||||
print('Stack trace: $stackTrace');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<NewCategory> getZekkenNumber(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();
|
||||
getToken();
|
||||
|
||||
try {
|
||||
final response = await http.get(
|
||||
Uri.parse('$baseUrl/user/'),
|
||||
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 jsonData = json.decode(decodedResponse);
|
||||
|
||||
//print('\nUser Data Comparison:');
|
||||
//_printDataComparison(jsonData, User);
|
||||
|
||||
return User.fromJson(jsonData);
|
||||
} else {
|
||||
throw Exception('Failed to get current user. Status code: ${response.statusCode}');
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
print('Error in getCurrentUser: $e');
|
||||
print('Stack trace: $stackTrace');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
void _printDataComparison(Map<String, dynamic> data, Type expectedType) {
|
||||
print('Field\t\t| Expected Type\t| Actual Type\t| Actual Value');
|
||||
print('------------------------------------------------------------');
|
||||
data.forEach((key, value) {
|
||||
String expectedFieldType = _getExpectedFieldType(expectedType, key);
|
||||
_printComparison(key, expectedFieldType, value);
|
||||
});
|
||||
}
|
||||
|
||||
String _getExpectedFieldType(Type type, String fieldName) {
|
||||
// This method should return the expected type for each field based on the class definition
|
||||
// You might need to implement this based on your class structures
|
||||
|
||||
switch (type) {
|
||||
case NewCategory:
|
||||
switch (fieldName) {
|
||||
case 'id': return 'int';
|
||||
case 'category_name': return 'String';
|
||||
case 'category_number': return 'int';
|
||||
case 'duration': return 'int (seconds)';
|
||||
case 'num_of_member': return 'int';
|
||||
case 'family': return 'bool';
|
||||
case 'female': return 'bool';
|
||||
default: return 'Unknown';
|
||||
}
|
||||
case Team:
|
||||
switch (fieldName) {
|
||||
case 'id': return 'int';
|
||||
case 'zekken_number': return 'String';
|
||||
case 'team_name': return 'String';
|
||||
case 'category': return 'NewCategory (Object)';
|
||||
case 'owner': return 'User (Object)';
|
||||
default: return 'Unknown';
|
||||
}
|
||||
case User:
|
||||
switch (fieldName) {
|
||||
case 'id': return 'int';
|
||||
case 'email': return 'String';
|
||||
case 'firstname': return 'String';
|
||||
case 'lastname': return 'String';
|
||||
case 'date_of_birth': return 'String (ISO8601)';
|
||||
case 'female': return 'bool';
|
||||
case 'is_active': return 'bool';
|
||||
default: return 'Unknown';
|
||||
}
|
||||
default:
|
||||
return 'Unknown Type';
|
||||
}
|
||||
}
|
||||
|
||||
void _printComparison(String fieldName, String expectedType, dynamic actualValue) {
|
||||
String actualType = actualValue?.runtimeType.toString() ?? 'null';
|
||||
String displayValue = actualValue.toString();
|
||||
if (displayValue.length > 50) {
|
||||
displayValue = '${displayValue.substring(0, 47)}...';
|
||||
}
|
||||
print('$fieldName\t\t| $expectedType\t\t| $actualType\t\t| $displayValue');
|
||||
}
|
||||
|
||||
Future<Team> createTeam(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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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'),
|
||||
headers: {'Authorization': 'Token $token'},
|
||||
);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Failed to delete team members');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> resendMemberInvitation(int memberId) async {
|
||||
init();
|
||||
getToken();
|
||||
|
||||
final response = await http.post(
|
||||
Uri.parse('$baseUrl/members/$memberId/resend-invitation/'),
|
||||
headers: {'Authorization': 'Token $token', 'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Failed to resend invitation');
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<Entry>> getEntries() 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 {
|
||||
final response = await http.get(
|
||||
Uri.parse('$baseUrl/teams/$teamId/entries'),
|
||||
headers: {'Authorization': 'Token $token'},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
List<dynamic> entriesJson = jsonDecode(response.body);
|
||||
return entriesJson.map((json) => Entry.fromJson(json)).toList();
|
||||
} else {
|
||||
throw Exception('Failed to load team entries: ${response.statusCode}');
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error in getTeamEntries: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<Event>> getEvents() async {
|
||||
init();
|
||||
getToken();
|
||||
|
||||
final response = await http.get(
|
||||
Uri.parse('$baseUrl/new-events/',),
|
||||
headers: {'Authorization': 'Token $token', 'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final decodedResponse = utf8.decode(response.bodyBytes);
|
||||
print('Response body: $decodedResponse');
|
||||
List<dynamic> eventsJson = json.decode(decodedResponse);
|
||||
|
||||
return eventsJson.map((json) => Event.fromJson(json)).toList();
|
||||
} else {
|
||||
throw Exception('Failed to load events');
|
||||
}
|
||||
}
|
||||
|
||||
Future<Entry> createEntry(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");
|
||||
throw Exception('Failed to create entry');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateUserInfo(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();
|
||||
getToken();
|
||||
|
||||
String? formattedDate;
|
||||
formattedDate = DateFormat('yyyy-MM-dd').format(date);
|
||||
|
||||
final response = await http.put(
|
||||
Uri.parse('$baseUrl/entry/$entryId/'),
|
||||
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 == 200) {
|
||||
final decodedResponse = utf8.decode(response.bodyBytes);
|
||||
|
||||
return Entry.fromJson(json.decode(decodedResponse));
|
||||
} else {
|
||||
final decodedResponse = utf8.decode(response.bodyBytes);
|
||||
final blk = json.decode(decodedResponse);
|
||||
|
||||
throw Exception('Failed to update entry');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteEntry(int entryId) async {
|
||||
init();
|
||||
getToken();
|
||||
|
||||
final response = await http.delete(
|
||||
Uri.parse('$baseUrl/entry/$entryId/'),
|
||||
headers: {'Authorization': 'Token $token'},
|
||||
);
|
||||
|
||||
if (response.statusCode != 204) {
|
||||
throw Exception('Failed to delete entry');
|
||||
}
|
||||
}
|
||||
|
||||
static Future<bool> updateUserDetail(User user, String token) async {
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
int? userid = user.id;
|
||||
String url = '$serverUrl/api/userdetail/$userid/';
|
||||
|
||||
try {
|
||||
String? formattedDate;
|
||||
if (user.dateOfBirth != null) {
|
||||
formattedDate = DateFormat('yyyy-MM-dd').format(user.dateOfBirth!);
|
||||
}
|
||||
final http.Response response = await http.put(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
'Authorization': 'Token $token'
|
||||
},
|
||||
body: jsonEncode({
|
||||
'firstname': user.firstname,
|
||||
'lastname': user.lastname,
|
||||
'date_of_birth': formattedDate,
|
||||
'female': user.female,
|
||||
}),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return true;
|
||||
} else {
|
||||
print('Update failed with status code: ${response.statusCode}');
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error in updateUserDetail: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> resetPassword(String email) async {
|
||||
init();
|
||||
|
||||
try {
|
||||
final response = await http.post(
|
||||
Uri.parse('$baseUrl/password-reset/'),
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
body: json.encode({
|
||||
'email': email,
|
||||
}),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return true;
|
||||
} else {
|
||||
print('Password reset failed with status code: ${response.statusCode}');
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error in resetPassword: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<DateTime?> getLastGoalTime(int userId) async {
|
||||
try {
|
||||
final response = await http.get(
|
||||
Uri.parse('$baseUrl/users/$userId/last-goal/'),
|
||||
headers: {
|
||||
'Authorization': 'Token $token',
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final decodedResponse = json.decode(utf8.decode(response.bodyBytes));
|
||||
if (decodedResponse['last_goal_time'] != null) {
|
||||
return DateTime.parse(decodedResponse['last_goal_time']).toLocal();
|
||||
}
|
||||
} else {
|
||||
print('Failed to get last goal time. Status code: ${response.statusCode}');
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error in getLastGoalTime: $e');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,16 +1,74 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:gifunavi/model/auth_user.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../utils/const.dart';
|
||||
//import 'package:rogapp/services/team_service.dart';
|
||||
//import 'package:rogapp/services/member_service.dart';
|
||||
|
||||
|
||||
class AuthService{
|
||||
class AuthService {
|
||||
Future<AuthUser?> userLogin(String email, String password) async {
|
||||
final serverUrl = ConstValues.currentServer();
|
||||
final url = '$serverUrl/api/login/';
|
||||
|
||||
static Future<Map<String, dynamic>> changePassword(String oldpassword, String newpassword, String token) async {
|
||||
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}),
|
||||
);
|
||||
switch (response.statusCode) {
|
||||
case 200:
|
||||
final data = json.decode(utf8.decode(response.bodyBytes));
|
||||
AuthUser user = AuthUser.fromMap(data["user"]);
|
||||
final String token = data["token"];
|
||||
user.auth_token = token;
|
||||
return user;
|
||||
default:
|
||||
return null;
|
||||
//throw Exception(response.reasonPhrase);
|
||||
}
|
||||
} on Exception catch (_) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<AuthUser?> userFromToken(String token) async {
|
||||
final serverUrl = ConstValues.currentServer();
|
||||
final url = '$serverUrl/api/user/';
|
||||
try {
|
||||
final http.Response response =
|
||||
await http.get(Uri.parse(url), headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
'Authorization': 'Token $token'
|
||||
});
|
||||
switch (response.statusCode) {
|
||||
case 200:
|
||||
final data = json.decode(utf8.decode(response.bodyBytes));
|
||||
AuthUser user = AuthUser.fromMap(data);
|
||||
user.auth_token = token;
|
||||
return user;
|
||||
default:
|
||||
return null;
|
||||
//throw Exception(response.reasonPhrase);
|
||||
}
|
||||
} on Exception catch (_) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> changePassword(
|
||||
String oldpassword, String newpassword, String token) async {
|
||||
Map<String, dynamic> changePassword = {};
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = '$serverUrl/api/change-password/';
|
||||
print('++++++++$url');
|
||||
//print('++++++++$url');
|
||||
final http.Response response = await http.put(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
@ -24,71 +82,106 @@ class AuthService{
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
changePassword = json.decode(utf8.decode(response.bodyBytes));
|
||||
changePassword = json.decode(utf8.decode(response.bodyBytes));
|
||||
}
|
||||
return changePassword;
|
||||
}
|
||||
|
||||
|
||||
static Future<Map<String, dynamic>> login(String email, String password) async {
|
||||
print("------- in logged email $email pwd $password ###### --------");
|
||||
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();
|
||||
String url = '$serverUrl/api/login/';
|
||||
print('++++++++$url');
|
||||
//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("通信エラーがおきました", "サーバーと通信できませんでした",
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white);
|
||||
Get.snackbar(
|
||||
"通信エラーがおきました",
|
||||
"サーバーと通信できませんでした",
|
||||
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),
|
||||
//backgroundColor: Colors.yellow,
|
||||
);
|
||||
cats = {};
|
||||
}
|
||||
return cats;
|
||||
}
|
||||
|
||||
|
||||
static Future<Map<String, dynamic>> register(String email, String password) async {
|
||||
|
||||
// ユーザー登録
|
||||
//
|
||||
/*
|
||||
Future<void> registerUser(String email, String password, bool isFemale) async {
|
||||
final user = await register(email, password);
|
||||
if (user != null) {
|
||||
final _teamController = TeamController();
|
||||
_teamController.createTeam(String teamName, int categoryId) ;
|
||||
final teamService = TeamService();
|
||||
final memberService = MemberService();
|
||||
|
||||
final team = await teamService.createSoloTeam(user.id, isFemale);
|
||||
await memberService.addMember(team.id, user.id);
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
static Future<Map<String, dynamic>> register(
|
||||
String email, String password, String password2) async {
|
||||
Map<String, dynamic> cats = {};
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = '$serverUrl/api/register/';
|
||||
print('++++++++$url');
|
||||
debugPrint('++++++++$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>{
|
||||
'email': email,
|
||||
'password': password
|
||||
}),
|
||||
body: jsonEncode(<String, String>{'email': email, 'password': password, 'password2': password2}),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
cats = json.decode(utf8.decode(response.bodyBytes));
|
||||
cats = json.decode(utf8.decode(response.bodyBytes));
|
||||
print("result=$cats");
|
||||
if (response.statusCode == 201) {
|
||||
}else{
|
||||
}
|
||||
return cats;
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> deleteUser(String token) async {
|
||||
static Future<Map<String, dynamic>> deleteUser(String token) async {
|
||||
Map<String, dynamic> cats = {};
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = '$serverUrl/api/delete-account/';
|
||||
print('++++++++$url');
|
||||
final http.Response response = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
'Authorization': 'Token $token'
|
||||
}
|
||||
);
|
||||
//print('++++++++$url');
|
||||
final http.Response response =
|
||||
await http.get(Uri.parse(url), headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
'Authorization': 'Token $token'
|
||||
});
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
cats = json.decode(utf8.decode(response.bodyBytes));
|
||||
@ -96,52 +189,46 @@ class AuthService{
|
||||
return cats;
|
||||
}
|
||||
|
||||
|
||||
static Future<List<dynamic>?> UserDetails(int userid) async {
|
||||
|
||||
static Future<List<dynamic>?> userDetails(int userid) async {
|
||||
List<dynamic> cats = [];
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = '$serverUrl/api/userdetials?user_id=$userid';
|
||||
print('++++++++$url');
|
||||
print("---- UserDetails url is $url");
|
||||
final response = await http.get(Uri.parse(url),
|
||||
//print('++++++++$url');
|
||||
//print("---- UserDetails url is $url");
|
||||
final response = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
|
||||
cats = json.decode(utf8.decode(response.bodyBytes));
|
||||
}
|
||||
return cats;
|
||||
}
|
||||
|
||||
|
||||
static Future<List<dynamic>?> userForToken(String token) async {
|
||||
Map<String, dynamic> cats = {};
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = '$serverUrl/api/user/';
|
||||
print('++++++++$url');
|
||||
print("---- UserDetails url is $url");
|
||||
final response = await http.get(Uri.parse(url),
|
||||
//print('++++++++$url');
|
||||
//print("---- UserDetails url is $url");
|
||||
final response = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
'Authorization': 'Token $token'
|
||||
'Authorization': 'Token $token'
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
|
||||
cats = json.decode(utf8.decode(response.bodyBytes));
|
||||
print("--- eeeeee $cats ----");
|
||||
//print("--- eeeeee $cats ----");
|
||||
}
|
||||
return [{"user":cats}];
|
||||
return [
|
||||
{"user": cats, "token": token}
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -3,50 +3,42 @@ import 'package:http/http.dart' as http;
|
||||
|
||||
import '../utils/const.dart';
|
||||
|
||||
|
||||
class CatService{
|
||||
|
||||
static Future<List<dynamic>?> loadCats(double lat1, double lon1, double lat2, double lon2, double lat3, double lon3, double lat4, double lon4) async {
|
||||
class CatService {
|
||||
static Future<List<dynamic>?> loadCats(double lat1, double lon1, double lat2,
|
||||
double lon2, double lat3, double lon3, double lat4, double lon4) async {
|
||||
List<dynamic> cats = [];
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = '$serverUrl/api/cats/?ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4';
|
||||
print('++++++++$url');
|
||||
final response = await http.get(Uri.parse(url),
|
||||
String url =
|
||||
'$serverUrl/api/cats/?ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4';
|
||||
//print('++++++++$url');
|
||||
final response = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
|
||||
cats = json.decode(utf8.decode(response.bodyBytes));
|
||||
}
|
||||
return cats;
|
||||
}
|
||||
|
||||
|
||||
static Future<List<dynamic>?> loadCatByCity(String cityname)async {
|
||||
static Future<List<dynamic>?> loadCatByCity(String cityname) async {
|
||||
List<dynamic> cats = [];
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = '$serverUrl/api/catbycity/?$cityname';
|
||||
print('++++++++$url');
|
||||
final response = await http.get(Uri.parse(url),
|
||||
//print('++++++++$url');
|
||||
final response = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
|
||||
cats = json.decode(utf8.decode(response.bodyBytes));
|
||||
}
|
||||
return cats;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -3,25 +3,21 @@ import 'package:flutter_polyline_points/flutter_polyline_points.dart';
|
||||
import 'package:get/get.dart';
|
||||
//import 'package:google_maps_webservice/directions.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:rogapp/model/destination.dart';
|
||||
import 'package:rogapp/pages/destination/destination_controller.dart';
|
||||
import 'package:gifunavi/model/destination.dart';
|
||||
import 'package:gifunavi/pages/destination/destination_controller.dart';
|
||||
|
||||
import '../utils/const.dart';
|
||||
|
||||
|
||||
class DestinationService{
|
||||
|
||||
static Future<List<dynamic>> getDestinations(int userId) async {
|
||||
class DestinationService {
|
||||
static Future<List<dynamic>> getDestinations(int userId) async {
|
||||
List<dynamic> cats = [];
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = "$serverUrl/api/destinations/?user_id=$userId";
|
||||
print('++++++++$url');
|
||||
final http.Response response = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
}
|
||||
);
|
||||
//print('++++++++$url');
|
||||
final http.Response response =
|
||||
await http.get(Uri.parse(url), headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
});
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
cats = json.decode(utf8.decode(response.bodyBytes));
|
||||
@ -29,36 +25,33 @@ class DestinationService{
|
||||
return cats;
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> deleteDestination(int destId) async {
|
||||
static Future<Map<String, dynamic>> deleteDestination(int destId) async {
|
||||
Map<String, dynamic> cats = {};
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = "$serverUrl/api/delete_destination/?dest_id=$destId";
|
||||
print('++++++++$url');
|
||||
final http.Response response = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
}
|
||||
);
|
||||
//print('++++++++$url');
|
||||
final http.Response response =
|
||||
await http.get(Uri.parse(url), headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
});
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
cats = json.decode(utf8.decode(response.bodyBytes));
|
||||
}
|
||||
print("####### ---- $cats");
|
||||
//print("####### ---- $cats");
|
||||
return cats;
|
||||
}
|
||||
|
||||
static Future<int> updateOrder(int actionId, int order, String dir) async {
|
||||
static Future<int> updateOrder(int actionId, int order, String dir) async {
|
||||
int cats = 0;
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = "$serverUrl/api/updateorder/?user_action_id=$actionId&order=$order&dir=$dir";
|
||||
print('++++++++$url');
|
||||
final http.Response response = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
}
|
||||
);
|
||||
String url =
|
||||
"$serverUrl/api/updateorder/?user_action_id=$actionId&order=$order&dir=$dir";
|
||||
//print('++++++++$url');
|
||||
final http.Response response =
|
||||
await http.get(Uri.parse(url), headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
});
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
cats = json.decode(utf8.decode(response.bodyBytes));
|
||||
@ -66,23 +59,25 @@ class DestinationService{
|
||||
return cats;
|
||||
}
|
||||
|
||||
static Future<List<PointLatLng>>? getDestinationLine(List<Destination> destinations, Map<String, dynamic> mtx) async{
|
||||
static Future<List<PointLatLng>>? getDestinationLine(
|
||||
List<Destination> destinations, Map<String, dynamic> mtx) async {
|
||||
PolylinePoints polylinePoints = PolylinePoints();
|
||||
|
||||
if(destinations.isEmpty){
|
||||
if (destinations.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
//print("##### @@@@@ ${destinations[0].lat}");
|
||||
PointLatLng origin = PointLatLng(destinations[0].lat!, destinations[0].lon!);
|
||||
PointLatLng dest = PointLatLng(destinations[destinations.length -1].lat!, destinations[destinations.length -1].lon!);
|
||||
|
||||
PointLatLng origin =
|
||||
PointLatLng(destinations[0].lat!, destinations[0].lon!);
|
||||
PointLatLng dest = PointLatLng(destinations[destinations.length - 1].lat!,
|
||||
destinations[destinations.length - 1].lon!);
|
||||
|
||||
List<PolylineWayPoint> wayPoints = [];
|
||||
int i=0;
|
||||
for(dynamic d in destinations){
|
||||
if(i == 0 || i == (destinations.length -1)){
|
||||
i+=1;
|
||||
int i = 0;
|
||||
for (dynamic d in destinations) {
|
||||
if (i == 0 || i == (destinations.length - 1)) {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
double la = d.lat;
|
||||
@ -90,39 +85,36 @@ class DestinationService{
|
||||
|
||||
int j = 0;
|
||||
|
||||
PolylineWayPoint pwp = PolylineWayPoint(location: "$la,$ln", stopOver: false);
|
||||
|
||||
PolylineWayPoint pwp =
|
||||
PolylineWayPoint(location: "$la,$ln", stopOver: false);
|
||||
|
||||
//print("----- UUUUUU ${pwp}");
|
||||
//PointLatLng wp = PointLatLng(d["Location"]["geometry"][0][1], d["Location"]["geometry"][0][0]);
|
||||
wayPoints.add(pwp);
|
||||
i+=1;
|
||||
i += 1;
|
||||
j += 4;
|
||||
}
|
||||
|
||||
final DestinationController destinationController = Get.find<DestinationController>();
|
||||
int travMode = destinationController.travelMode.value;
|
||||
String mode = "WALKING";
|
||||
if(travMode == 1){
|
||||
//_mode = TravelMode.driving;
|
||||
mode = "DRIVING";
|
||||
}
|
||||
else if(travMode == 2) {
|
||||
//_mode = TravelMode.transit;
|
||||
mode = "TRANSIT";
|
||||
}
|
||||
|
||||
final DestinationController destinationController =
|
||||
Get.find<DestinationController>();
|
||||
// int travMode = destinationController.travelMode.value;
|
||||
// String mode = "WALKING";
|
||||
// if (travMode == 1) {
|
||||
// //_mode = TravelMode.driving;
|
||||
// mode = "DRIVING";
|
||||
// } else if (travMode == 2) {
|
||||
// //_mode = TravelMode.transit;
|
||||
// mode = "TRANSIT";
|
||||
// }
|
||||
|
||||
//PolylineResult result = await polylinePoints.getRouteBetweenCoordinates("AIzaSyAUBI1ablMKuJwGj2-kSuEhvYxvB1A-mOE", PointLatLng(35.389282, 136.498027), PointLatLng(36.285848, 137.575186));
|
||||
Map<String, dynamic> pl = destinationController.matrix["routes"][0]["overview_polyline"];
|
||||
Map<String, dynamic> pl =
|
||||
destinationController.matrix["routes"][0]["overview_polyline"];
|
||||
List<PointLatLng> result = polylinePoints.decodePolyline(pl["points"]);
|
||||
//List<PointLatLng> result = polylinePoints.decodePolyline("qkyvEq`z`Yp@DBMr@XL@Td@Eb@PREd@IFe@rKIzCY|GEvCBzCHvS@xC?HnBHtBHlBFnBFhGRtDVW~BE`@ICHLk@dE_ClPgAtHu@bFsAhPg@~Ge@bFaEtg@kEpi@oCd\\w@nIw@hGe@fBy@nBqAjC{@zBgBtFOd@M@Wv@i@`BQf@ITKCuE`@yDZqBRCHa@DKG_AHwBRiBR_Fp@y@LYBY]M@KJo@v@M@cAGoGN_Cx@}Cf@}@@mM~@qF`@gCLwBj@sBrAeAhAsAtCoF|MmAbD{@fBwAdBw@p@_Ax@BFOHAl@?`@MAQCEAOIQSaBx@{Ah@eATsAHSB?d@A`D?f@IdWy@AS??V?|BCJ}@?cA?k@Au@wBqDuKQaACg@z@gELg@GK~@uEp@{A@q@y@CHwFHcG?KDqCDK^ABABEH{AE{B{@_Ho@uFIaBFQhBaC@SQSg@k@g@q@Yw@qA{De@}B]uDCsAMEWDqAFu@@^A@TDjA@vDA`CETK|AEtAIFY@o@ALpBZ~HBlCBn@EDGPu@pASJO`@Qf@?ROr@K?qDLHnEUTsDNkENYB{Ab@I^?zA}CrCkBfBw@t@@LwA`Bo@r@eGvD}BrAGGuAj@[?i@rBVi@P}@T?F?^MxDuBhDsBzAcAn@s@zCgDAI~A{A|CsC?{A?UHItA_@DCXC~J_@TUIoEvDKTm@?Y^iALIb@k@f@aAE}AA_BC{@\\Cv@CxAEj@ExCwDDc@CYFANCh@WHEIIRQhB}B|C_E\\w@Hq@JE?a@O}CGkAIwEGmDEmDAKLA^?A}@C{@?e@E_DFQNi@LcB\\eBPsADGKOEWBOH[GCPs@Pq@\\cANs@^q@jAu@fCqAf@]HCXoCV_BVmAZmBVcDBeCCgDAaB?s@RE?aCCaEAyHAoDd@EJiD@_@AaAj@A\\A?Gp@@r@oBXm@LQ?IEy@Fy@tA[n@Gj@Tz@[~ACdAAx@Lp@Kr@]hAa@HAQoCMwCSwGSiGK_CCCKaBCgCOoCOgECwGB_OB{JHkBEmC?yCDyFF{QFue@BsYByE?oAEgAByLBiL?gLBuGXsEd@cCNA?OHa@jAuCn@eAtCyDh@k@v@EvBKr@EEkACUKaC?G~@gAlCeDFBT[jFeGZAfBEh@UpBM`AEMaFjFYIhE?hEPpCJzAPt@Fj@GNUFu@N[FyBbAuB`@_@LEIOB}@HUBQFk@FcAACGQA}@Bi@F@F[Dc@D[FQHELGhBMtDGR?D");
|
||||
//PolylineResult result = await polylinePoints.getRouteBetweenCoordinates("AIzaSyAUBI1ablMKuJwGj2-kSuEhvYxvB1A-mOE", origin,dest, travelMode: _mode, wayPoints: wayPoints);
|
||||
|
||||
|
||||
//print("#####@@@@@ ${result.points}");
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
63
lib/services/error_service.dart
Normal file
63
lib/services/error_service.dart
Normal file
@ -0,0 +1,63 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
class ErrorService {
|
||||
static Future<void> reportError(dynamic error, StackTrace stackTrace, Map<String, dynamic> deviceInfo, List<String> operationLogs) async {
|
||||
try {
|
||||
final String errorMessage = error.toString();
|
||||
final String stackTraceString = stackTrace.toString();
|
||||
final String estimatedCause = _estimateErrorCause(errorMessage);
|
||||
//final String deviceInfo = await _getDeviceInfo();
|
||||
|
||||
final Uri url = Uri.parse('https://rogaining.sumasen.net/report-error');
|
||||
final response = await http.post(
|
||||
url,
|
||||
body: {
|
||||
'error_message': errorMessage,
|
||||
'stack_trace': stackTraceString,
|
||||
'estimated_cause': estimatedCause,
|
||||
'device_info': deviceInfo,
|
||||
'operation_logs': operationLogs.join('\n'), // オペレーションログを改行で結合して送信
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
// エラー報告が成功した場合の処理(必要に応じて)
|
||||
debugPrint("===== エラーログ送信成功しました。 ====");
|
||||
} else {
|
||||
// エラー報告が失敗した場合の処理(必要に応じて)
|
||||
debugPrint("===== エラーログ送信失敗しました。 ====");
|
||||
}
|
||||
} catch (e) {
|
||||
// エラー報告中にエラーが発生した場合の処理(必要に応じて)
|
||||
debugPrint("===== エラーログ送信中にエラーになりました。 ====");
|
||||
}
|
||||
}
|
||||
|
||||
static String _estimateErrorCause(String errorMessage) {
|
||||
// エラーメッセージに基づいてエラーの原因を推定するロジックを追加する
|
||||
if (errorMessage.contains('NetworkException')) {
|
||||
return 'ネットワーク接続エラー';
|
||||
} else if (errorMessage.contains('DatabaseException')) {
|
||||
return 'データベースエラー';
|
||||
} else if (errorMessage.contains('AuthenticationException')) {
|
||||
return '認証エラー';
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@ -1,280 +1,443 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:rogapp/model/rog.dart';
|
||||
import 'package:rogapp/pages/destination/destination_controller.dart';
|
||||
import 'package:rogapp/pages/index/index_controller.dart';
|
||||
import 'package:rogapp/utils/database_helper.dart';
|
||||
import 'package:gifunavi/model/rog.dart';
|
||||
import 'package:gifunavi/pages/destination/destination_controller.dart';
|
||||
import 'package:gifunavi/pages/index/index_controller.dart';
|
||||
import 'package:gifunavi/utils/database_gps.dart';
|
||||
import 'package:gifunavi/utils/database_helper.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
import '../model/gps_data.dart';
|
||||
import '../utils/const.dart';
|
||||
|
||||
//
|
||||
//
|
||||
// Rog type 0- start 1- checkin 2- goal
|
||||
//
|
||||
//
|
||||
|
||||
class ExternalService {
|
||||
static final ExternalService _instance = ExternalService._internal();
|
||||
|
||||
factory ExternalService(){
|
||||
factory ExternalService() {
|
||||
return _instance;
|
||||
}
|
||||
|
||||
ExternalService._internal();
|
||||
|
||||
String getFormatedTime(DateTime datetime){
|
||||
return DateFormat('yyyy-MM-dd HH:mm:ss').format(datetime);
|
||||
String getFormatedTime(DateTime datetime) {
|
||||
return DateFormat('yyyy-MM-dd HH:mm:ss').format(datetime);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> StartRogaining() async {
|
||||
|
||||
Future<Map<String, dynamic>> startRogaining() async {
|
||||
final IndexController indexController = Get.find<IndexController>();
|
||||
//final TeamController teamController = Get.find<TeamController>();
|
||||
|
||||
debugPrint("== startRogaining ==");
|
||||
|
||||
Map<String, dynamic> res = {};
|
||||
|
||||
//final teamController = TeamController();
|
||||
|
||||
//Team team0 = teamController.teams[0];
|
||||
//print("team={team0}");
|
||||
|
||||
//int teamId = indexController.currentUser[0]["user"]["team"]["id"];
|
||||
|
||||
int userId = indexController.currentUser[0]["user"]["id"];
|
||||
//print("--- Pressed -----");
|
||||
String team = indexController.currentUser[0]["user"]['team_name'];
|
||||
//print("--- _team : ${_team}-----");
|
||||
String eventCode = indexController.currentUser[0]["user"]["event_code"];
|
||||
|
||||
if(indexController.connectionStatusName != "wifi" && indexController.connectionStatusName != "mobile"){
|
||||
if (indexController.connectionStatusName.value != "wifi" &&
|
||||
indexController.connectionStatusName.value != "mobile") {
|
||||
debugPrint("== No network ==");
|
||||
DatabaseHelper db = DatabaseHelper.instance;
|
||||
Rog rog = Rog(
|
||||
id:1,
|
||||
team_name: team,
|
||||
event_code : eventCode,
|
||||
user_id: userId,
|
||||
cp_number: -1,
|
||||
checkintime: DateTime.now().toUtc().microsecondsSinceEpoch,
|
||||
image: null,
|
||||
rog_action_type: 0
|
||||
);
|
||||
id: 1,
|
||||
team_name: team,
|
||||
event_code: eventCode,
|
||||
user_id: userId,
|
||||
cp_number: -1,
|
||||
checkintime: DateTime.now().toUtc().microsecondsSinceEpoch,
|
||||
image: null,
|
||||
rog_action_type: 0);
|
||||
db.insertRogaining(rog);
|
||||
}
|
||||
else {
|
||||
String url = 'https://natnats.mobilous.com/start_from_rogapp';
|
||||
} 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
|
||||
}),
|
||||
body: jsonEncode(
|
||||
<String, String>{'team_name': team, 'event_code': eventCode}),
|
||||
);
|
||||
|
||||
print("---- start rogianing api status ---- ${response.statusCode}");
|
||||
//print("---- start rogianing api status ---- ${response.statusCode}");
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
res = json.decode(utf8.decode(response.bodyBytes));
|
||||
print('----_res : $res ----');
|
||||
res = json.decode(utf8.decode(response.bodyBytes));
|
||||
//print('----_res : $res ----');
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> makeCheckpoint(int userId, String token, String checkinTime, String teamname, int cp, String eventcode, String imageurl) async {
|
||||
Future<Map<String, dynamic>> makeCheckpoint(
|
||||
int userId, // 中身はteamId
|
||||
String token,
|
||||
String checkinTime,
|
||||
String teamname,
|
||||
int cp,
|
||||
String eventcode,
|
||||
String imageurl) async {
|
||||
// print("~~~~ in API call function ~~~~");
|
||||
// print("~~~~ cp is $cp ~~~~");
|
||||
//print("--cpcp-- ${cp}");
|
||||
Map<String, dynamic> res = {};
|
||||
String url = 'https://natnats.mobilous.com/checkin_from_rogapp';
|
||||
print('++++++++$url');
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = '$serverUrl/gifuroge/checkin_from_rogapp';
|
||||
//print('++++++++$url');
|
||||
final IndexController indexController = Get.find<IndexController>();
|
||||
//final TeamController teamController = Get.find<TeamController>();
|
||||
|
||||
if(imageurl != null){
|
||||
|
||||
if(indexController.connectionStatusName != "wifi" && indexController.connectionStatusName != "mobile"){
|
||||
DatabaseHelper db = DatabaseHelper.instance;
|
||||
Rog rog = Rog(
|
||||
id:1,
|
||||
team_name: teamname,
|
||||
event_code : eventcode,
|
||||
user_id: userId,
|
||||
cp_number: cp,
|
||||
checkintime: DateTime.now().toUtc().microsecondsSinceEpoch,
|
||||
image: imageurl,
|
||||
rog_action_type: 1,
|
||||
);
|
||||
db.insertRogaining(rog);
|
||||
}
|
||||
else {
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url1 = "$serverUrl/api/checkinimage/";
|
||||
final im1Bytes = File(imageurl).readAsBytesSync();
|
||||
String im1_64 = base64Encode(im1Bytes);
|
||||
// Team team0 = indexController.teamController.teams[0];
|
||||
// print("team={team0}");
|
||||
|
||||
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()
|
||||
}),
|
||||
);
|
||||
//int teamId = indexController.teamController.teams[0];
|
||||
|
||||
res = json.decode(utf8.decode(response.bodyBytes));
|
||||
if (indexController.connectionStatusName.value != "wifi" &&
|
||||
indexController.connectionStatusName.value != "mobile") {
|
||||
debugPrint("== checkin without network ==");
|
||||
|
||||
print("-----@@@@@ $res -----");
|
||||
|
||||
if(response.statusCode == 201){
|
||||
//print('---- toekn is ${token} -----');
|
||||
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')
|
||||
}),
|
||||
);
|
||||
print("--- checnin response ${response2.statusCode}----");
|
||||
if (response2.statusCode == 200) {
|
||||
res = json.decode(utf8.decode(response2.bodyBytes));
|
||||
print('----checkin res _res : $res ----');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else{
|
||||
if(indexController.connectionStatusName != "wifi" || indexController.connectionStatusName != "mobile"){
|
||||
DatabaseHelper db = DatabaseHelper.instance;
|
||||
Rog rog = Rog(
|
||||
id:1,
|
||||
team_name: teamname,
|
||||
event_code : eventcode,
|
||||
user_id: userId,
|
||||
cp_number: cp,
|
||||
checkintime: DateTime.now().toUtc().microsecondsSinceEpoch,
|
||||
image: null,
|
||||
rog_action_type: 1,
|
||||
);
|
||||
db.insertRogaining(rog);
|
||||
}
|
||||
else {
|
||||
final http.Response response3 = 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': ""
|
||||
}),
|
||||
);
|
||||
print("--- checnin response ${response3.statusCode}----");
|
||||
if (response3.statusCode == 200) {
|
||||
res = json.decode(utf8.decode(response3.bodyBytes));
|
||||
print('----checkin res _res : $res ----');
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
Future<Map<String, dynamic>> makeGoal(int userId, String token, String teamname, String image, String goalTime, String eventcode) async {
|
||||
Map<String, dynamic> res2 = {};
|
||||
|
||||
final IndexController indexController = Get.find<IndexController>();
|
||||
final DestinationController destinationController = Get.find<DestinationController>();
|
||||
|
||||
//if(indexController.connectionStatusName != "wifi" && indexController.connectionStatusName != "mobile"){
|
||||
DatabaseHelper db = DatabaseHelper.instance;
|
||||
Rog rog = Rog(
|
||||
id:1,
|
||||
team_name: teamname,
|
||||
event_code : eventcode,
|
||||
user_id: userId,
|
||||
cp_number: -1,
|
||||
checkintime: DateTime.now().toUtc().microsecondsSinceEpoch,
|
||||
image: image,
|
||||
rog_action_type: 1,
|
||||
);
|
||||
db.insertRogaining(rog);
|
||||
// }
|
||||
// else{
|
||||
DatabaseHelper db = DatabaseHelper.instance;
|
||||
Rog rog = Rog(
|
||||
id: 1,
|
||||
team_name: teamname,
|
||||
event_code: eventcode,
|
||||
user_id: userId,
|
||||
cp_number: cp,
|
||||
checkintime: DateTime.now().toUtc().microsecondsSinceEpoch,
|
||||
image: imageurl,
|
||||
rog_action_type: 1,
|
||||
);
|
||||
db.insertRogaining(rog);
|
||||
} else {
|
||||
debugPrint("== Normal Check in ===");
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url1 = "$serverUrl/api/goalimage/";
|
||||
final im1Bytes = File(image).readAsBytesSync();
|
||||
String url1 = "$serverUrl/api/checkinimage/";
|
||||
final im1Bytes = File(imageurl).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(),
|
||||
'team_name': teamname,
|
||||
'event_code': eventcode,
|
||||
'goaltime' : goalTime,
|
||||
'goalimage' : im1_64,
|
||||
'cp_number' : "-1"
|
||||
}),
|
||||
);
|
||||
//print("~~~~ before calling api 1 ~~~~");
|
||||
|
||||
String url = 'https://natnats.mobilous.com/goal_from_rogapp';
|
||||
print('++++++++$url');
|
||||
if (response.statusCode == 201) {
|
||||
Map<String, dynamic> res = json.decode(utf8.decode(response.bodyBytes));
|
||||
print('----_res : $res ----');
|
||||
print('---- image url ${res["goalimage"]} ----');
|
||||
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,
|
||||
'event_code': eventcode,
|
||||
'goal_time' : goalTime,
|
||||
'image' : res["goalimage"].toString().replaceAll('http://localhost:8100', 'http://rogaining.sumasen.net')
|
||||
}
|
||||
),
|
||||
'team_name': teamname,
|
||||
'cp_number': cp.toString(),
|
||||
'event_code': eventcode,
|
||||
'image': res["checkinimage"].toString().replaceAll(
|
||||
'http://localhost:8100', serverUrl) //'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', serverUrl) //'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" && cp>0 ) {
|
||||
// スタート・ゴールはエラー除外。
|
||||
Get.snackbar("エラーがおきました", res["detail"],
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
Get.snackbar("サーバーエラーがおきました", "サーバーと通信できませんでした",
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white
|
||||
);
|
||||
}
|
||||
} catch( e ) {
|
||||
print('Error in makeCheckpoint: $e');
|
||||
Get.snackbar("通信エラーがおきました", "サーバーと通信できませんでした",
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white
|
||||
);
|
||||
print('----- response2 is $response2 --------');
|
||||
}
|
||||
}
|
||||
//print("~~~~ done checkin ~~~~");
|
||||
return res;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> makeGoal(int userId, String token,
|
||||
String teamname, String image, String goalTime, String eventcode) async {
|
||||
Map<String, dynamic> res2 = {};
|
||||
|
||||
final IndexController indexController = Get.find<IndexController>();
|
||||
final DestinationController destinationController =
|
||||
Get.find<DestinationController>();
|
||||
|
||||
// チームIDを取得
|
||||
|
||||
//int teamId = indexController.currentUser[0]["user"]["team"]["id"];
|
||||
|
||||
debugPrint("== goal Rogaining ==");
|
||||
|
||||
//if(indexController.connectionStatusName != "wifi" && indexController.connectionStatusName != "mobile"){
|
||||
DatabaseHelper db = DatabaseHelper.instance;
|
||||
Rog rog = Rog(
|
||||
id: 1,
|
||||
team_name: teamname,
|
||||
event_code: eventcode,
|
||||
user_id: userId, // 中身はteamid
|
||||
cp_number: -1,
|
||||
checkintime: DateTime.now().toUtc().microsecondsSinceEpoch,
|
||||
image: image,
|
||||
rog_action_type: 1,
|
||||
);
|
||||
db.insertRogaining(rog);
|
||||
// }
|
||||
// else{
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url1 = "$serverUrl/api/goalimage/";
|
||||
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"
|
||||
}),
|
||||
);
|
||||
|
||||
//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)
|
||||
//'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));
|
||||
}
|
||||
}
|
||||
} catch(e){
|
||||
print( "Error {$e}" );
|
||||
}
|
||||
}
|
||||
//}
|
||||
destinationController.resetRogaining();
|
||||
destinationController.resetRogaining(isgoal: true);
|
||||
return res2;
|
||||
}
|
||||
|
||||
Future<bool> removeCheckin(int cp) async {
|
||||
final IndexController indexController = Get.find<IndexController>();
|
||||
|
||||
static Future<Map<String, dynamic>> usersEventCode(String teamcode, String password) async {
|
||||
Map<String, dynamic> res = {};
|
||||
String url = "https://natnats.mobilous.com/check_event_code?zekken_number=$teamcode&password=$password";
|
||||
print('++++++++$url');
|
||||
final http.Response response = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
//int userId = indexController.currentUser[0]["user"]["id"];
|
||||
//print("--- Pressed -----");
|
||||
String team = indexController.currentUser[0]["user"]['team_name'];
|
||||
//print("--- _team : ${_team}-----");
|
||||
String eventCode = indexController.currentUser[0]["user"]["event_code"];
|
||||
|
||||
if (indexController.connectionStatusName.value != "wifi" &&
|
||||
indexController.connectionStatusName.value != "mobile") {
|
||||
return Future.value(false);
|
||||
} else {
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = '$serverUrl/gifuroge/remove_checkin_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>{
|
||||
'event_code': eventCode,
|
||||
'team_name': team,
|
||||
'cp_number': cp.toString()
|
||||
}),
|
||||
);
|
||||
|
||||
//print("---- remove checkin ---- ${response.statusCode}");
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return Future.value(true);
|
||||
//print('----_res : $res ----');
|
||||
}
|
||||
);
|
||||
}
|
||||
return Future.value(false);
|
||||
}
|
||||
|
||||
String timestampToTimeString(int timestamp) {
|
||||
// Convert timestamp to DateTime and format it as needed
|
||||
var dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp);
|
||||
//print("^^^^ time ${dateTime}");
|
||||
// Format dateTime to a time string (e.g., '12:00:00')
|
||||
// Adjust the format as needed
|
||||
return "${dateTime.hour}:${dateTime.minute}:${dateTime.second}";
|
||||
}
|
||||
|
||||
Future<bool> pushGPS() async {
|
||||
//print("^^^^ pushed ^^^");
|
||||
final IndexController indexController = Get.find<IndexController>();
|
||||
|
||||
//int userId = indexController.currentUser[0]["user"]["id"];
|
||||
//print("--- Pressed -----");
|
||||
String team = indexController.currentUser[0]["user"]['team_name'];
|
||||
//print("--- _team : ${_team}-----");
|
||||
String eventCode = indexController.currentUser[0]["user"]["event_code"];
|
||||
|
||||
List<GpsData> gpsDataList = [];
|
||||
|
||||
if (indexController.connectionStatusName.value != "wifi" &&
|
||||
indexController.connectionStatusName.value != "mobile") {
|
||||
return Future.value(false);
|
||||
} else {
|
||||
// Step 1: Fetch data from the local database
|
||||
gpsDataList =
|
||||
await GpsDatabaseHelper.instance.getUnsyncedGPSData(team, eventCode);
|
||||
|
||||
// Step 2: Transform data into the required format
|
||||
var payload = {
|
||||
'team_name': team,
|
||||
'event_code': eventCode,
|
||||
'waypoints': gpsDataList.map((gpsData) {
|
||||
return {
|
||||
'latitude': gpsData.lat.toString(),
|
||||
'longitude': gpsData.lon.toString(),
|
||||
// Convert the timestamp to a formatted time string
|
||||
'time': timestampToTimeString(gpsData.created_at),
|
||||
};
|
||||
}).toList(),
|
||||
};
|
||||
|
||||
//print("calling push gps step 2 ${payload}");
|
||||
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String urlS = '$serverUrl/gifuroge/get_waypoint_datas_from_rogapp';
|
||||
//print('++++++++$url');
|
||||
var url = Uri.parse(urlS); // Replace with your server URL
|
||||
var response = await http.post(
|
||||
url,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: json.encode(payload),
|
||||
);
|
||||
|
||||
//print("GPS Data res ${response.statusCode}");
|
||||
if (response.statusCode == 200) {
|
||||
// Handle success
|
||||
// make local data as synced
|
||||
await GpsDatabaseHelper.instance.setSyncData(gpsDataList);
|
||||
//print("GPS Data sent successfully");
|
||||
} else {
|
||||
// Handle error
|
||||
//print("Failed to send data");
|
||||
}
|
||||
}
|
||||
return Future.value(false);
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> usersEventCode(
|
||||
String teamcode, String password) async {
|
||||
Map<String, dynamic> res = {};
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = "$serverUrl/gifuroge/check_event_code?zekken_number=$teamcode&password=$password";
|
||||
//print('++++++++$url');
|
||||
final http.Response response =
|
||||
await http.get(Uri.parse(url), headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
});
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
res = json.decode(utf8.decode(response.bodyBytes));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,35 +1,32 @@
|
||||
import 'package:geojson/geojson.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
// import 'package:geojson/geojson.dart';
|
||||
// import 'package:http/http.dart' as http;
|
||||
|
||||
import '../utils/const.dart';
|
||||
// import '../utils/const.dart';
|
||||
|
||||
class LocationLineService{
|
||||
// class LocationLineService {
|
||||
// static Future<GeoJsonFeature?> loadLocationLines() async {
|
||||
// final geo = GeoJson();
|
||||
// GeoJsonFeature? fs;
|
||||
// String serverUrl = ConstValues.currentServer();
|
||||
// String url = '$serverUrl/api/location_line/';
|
||||
// //print('++++++++$url');
|
||||
// final response = await http.get(
|
||||
// Uri.parse(url),
|
||||
// headers: <String, String>{
|
||||
// 'Content-Type': 'application/json; charset=UTF-8',
|
||||
// },
|
||||
// );
|
||||
|
||||
static Future<GeoJsonFeature?> loadLocationLines() async {
|
||||
final geo = GeoJson();
|
||||
GeoJsonFeature? fs;
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = '$serverUrl/api/location_line/';
|
||||
print('++++++++$url');
|
||||
final response = await http.get(Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
);
|
||||
// if (response.statusCode == 200) {
|
||||
// geo.processedFeatures.listen((fst) {
|
||||
// fs = fst;
|
||||
// });
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
// await geo.parse(response.body, verbose: true);
|
||||
|
||||
geo.processedFeatures.listen((fst) {
|
||||
fs = fst;
|
||||
});
|
||||
|
||||
await geo.parse(response.body, verbose:true);
|
||||
|
||||
return fs;
|
||||
|
||||
} else {
|
||||
throw Exception('Failed to create album.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// return fs;
|
||||
// } else {
|
||||
// throw Exception('Failed to create album.');
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
@ -1,36 +1,33 @@
|
||||
import 'package:geojson/geojson.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
// import 'package:geojson/geojson.dart';
|
||||
// import 'package:http/http.dart' as http;
|
||||
|
||||
import '../utils/const.dart';
|
||||
// import '../utils/const.dart';
|
||||
|
||||
class LocationPolygonervice{
|
||||
// class LocationPolygonervice {
|
||||
// static Future<GeoJsonFeature?> loadLocationLines() async {
|
||||
// final geo = GeoJson();
|
||||
// GeoJsonFeature? fs;
|
||||
|
||||
static Future<GeoJsonFeature?> loadLocationLines() async {
|
||||
final geo = GeoJson();
|
||||
GeoJsonFeature? fs;
|
||||
// String serverUrl = ConstValues.currentServer();
|
||||
// String url = '$serverUrl/api/location_polygon/';
|
||||
// //print('++++++++$url');
|
||||
// final response = await http.get(
|
||||
// Uri.parse(url),
|
||||
// headers: <String, String>{
|
||||
// 'Content-Type': 'application/json; charset=UTF-8',
|
||||
// },
|
||||
// );
|
||||
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = '$serverUrl/api/location_polygon/';
|
||||
print('++++++++$url');
|
||||
final response = await http.get(Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
);
|
||||
// if (response.statusCode == 200) {
|
||||
// geo.processedFeatures.listen((fst) {
|
||||
// fs = fst;
|
||||
// });
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
// await geo.parse(response.body, verbose: true);
|
||||
|
||||
geo.processedFeatures.listen((fst) {
|
||||
fs = fst;
|
||||
});
|
||||
|
||||
await geo.parse(response.body, verbose:true);
|
||||
|
||||
return fs;
|
||||
|
||||
} else {
|
||||
throw Exception('Failed to create album.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// return fs;
|
||||
// } else {
|
||||
// throw Exception('Failed to create album.');
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
@ -1,75 +1,52 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:geojson/geojson.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:geojson_vi/geojson_vi.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:rogapp/pages/index/index_controller.dart';
|
||||
import 'package:rogapp/utils/const.dart';
|
||||
import 'package:gifunavi/pages/destination/destination_controller.dart';
|
||||
import 'package:gifunavi/pages/index/index_controller.dart';
|
||||
import 'package:gifunavi/utils/const.dart';
|
||||
|
||||
class LocationService{
|
||||
class LocationService {
|
||||
|
||||
// static Future<GeoJsonFeatureCollection?> loadLocations() async {
|
||||
|
||||
// final IndexController indexController = Get.find<IndexController>();
|
||||
|
||||
// String server_url = ConstValues.currentServer();
|
||||
// String url = "";
|
||||
// if(indexController.currentUser.length > 0){
|
||||
// url = '${server_url}/api/location/?is_rog=True';
|
||||
// }
|
||||
// else {
|
||||
// url = '${server_url}/api/location/';
|
||||
// }
|
||||
// //String url = 'http://localhost:8100/api/location/';
|
||||
// final response = await http.get(Uri.parse(url),
|
||||
// headers: <String, String>{
|
||||
// 'Content-Type': 'application/json; charset=UTF-8',
|
||||
// },
|
||||
// );
|
||||
|
||||
// if (response.statusCode == 200) {
|
||||
|
||||
// return featuresFromGeoJson(utf8.decode(response.bodyBytes));
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
|
||||
static Future<GeoJsonFeatureCollection?> loadLocationsFor(String perfecture, String cat) async {
|
||||
static Future<GeoJSONFeatureCollection?> loadLocationsFor(
|
||||
String perfecture, String cat) async {
|
||||
final IndexController indexController = Get.find<IndexController>();
|
||||
String url = "";
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
|
||||
if(cat.isNotEmpty){
|
||||
if(indexController.currentUser.isNotEmpty){
|
||||
|
||||
if (cat.isNotEmpty) {
|
||||
if (indexController.currentUser.isNotEmpty) {
|
||||
bool rog = indexController.currentUser[0]['user']['is_rogaining'];
|
||||
String r = rog == true ? 'True': 'False';
|
||||
String r = rog == true ? 'True' : 'False';
|
||||
var grp = indexController.currentUser[0]['user']['event_code'];
|
||||
url = '$serverUrl/api/inperf/?rog=$r&perf=$perfecture&cat=$cat';
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
url = '$serverUrl/api/inperf/?perf=$perfecture&cat=$cat';
|
||||
}
|
||||
}
|
||||
else{
|
||||
if(indexController.currentUser.isNotEmpty){
|
||||
} else {
|
||||
if (indexController.currentUser.isNotEmpty) {
|
||||
bool rog = indexController.currentUser[0]['user']['is_rogaining'];
|
||||
String r = rog == true ? 'True': 'False';
|
||||
String r = rog == true ? 'True' : 'False';
|
||||
var grp = indexController.currentUser[0]['user']['event_code'];
|
||||
url = '$serverUrl/api/inperf/?rog=$r&perf=$perfecture';
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
url = '$serverUrl/api/inperf/?perf=$perfecture';
|
||||
}
|
||||
}
|
||||
print('++++++++$url');
|
||||
final response = await http.get(Uri.parse(url),
|
||||
//print('++++++++$url');
|
||||
final response = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
GeoJsonFeatureCollection cc = await featuresFromGeoJson(utf8.decode(response.bodyBytes));
|
||||
GeoJSONFeatureCollection cc =
|
||||
GeoJSONFeatureCollection.fromJSON(utf8.decode(response.bodyBytes));
|
||||
//print(cc);
|
||||
return cc; //featuresFromGeoJson(utf8.decode(response.bodyBytes));
|
||||
}
|
||||
@ -80,175 +57,182 @@ class LocationService{
|
||||
List<dynamic> ext = [];
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = '$serverUrl/api/locsext/';
|
||||
print('++++++++$url');
|
||||
//print('++++++++$url');
|
||||
final response = await http.post(Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
'Authorization': 'Token $token'
|
||||
},
|
||||
body: jsonEncode(<String, String>{
|
||||
'token': token,
|
||||
})
|
||||
);
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
'Authorization': 'Token $token'
|
||||
},
|
||||
body: jsonEncode(<String, String>{
|
||||
'token': token,
|
||||
}));
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
|
||||
ext = json.decode(utf8.decode(response.bodyBytes));
|
||||
}
|
||||
return ext;
|
||||
}
|
||||
|
||||
|
||||
static Future<GeoJsonFeatureCollection?> loadLocationsSubFor(String subperfecture, String cat) async {
|
||||
static Future<GeoJSONFeatureCollection?> loadLocationsBound(
|
||||
double lat1,
|
||||
double lon1,
|
||||
double lat2,
|
||||
double lon2,
|
||||
double lat3,
|
||||
double lon3,
|
||||
double lat4,
|
||||
double lon4,
|
||||
String cat,
|
||||
String eventCode) async {
|
||||
//print("-------- in location for bound -------------");
|
||||
final IndexController indexController = Get.find<IndexController>();
|
||||
String url = "";
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
if(cat.isNotEmpty){
|
||||
if(indexController.currentUser.isNotEmpty){
|
||||
bool rog = indexController.currentUser[0]['user']['is_rogaining'];
|
||||
String r = rog == true ? 'True': 'False';
|
||||
var grp = indexController.currentUser[0]['user']['event_code'];
|
||||
url = '$serverUrl/api/insubperf?rog=$r&subperf=$subperfecture&cat=$cat';
|
||||
}
|
||||
else{
|
||||
url = '$serverUrl/api/insubperf?subperf=$subperfecture&cat=$cat';
|
||||
}
|
||||
}
|
||||
else{
|
||||
if(indexController.currentUser.isNotEmpty){
|
||||
bool rog = indexController.currentUser[0]['user']['is_rogaining'];
|
||||
String r = rog == true ? 'True': 'False';
|
||||
var grp = indexController.currentUser[0]['user']['event_code'];
|
||||
url = '$serverUrl/api/insubperf?rog=$r&subperf=$subperfecture';
|
||||
}
|
||||
else{
|
||||
url = '$serverUrl/api/insubperf?subperf=$subperfecture';
|
||||
}
|
||||
}
|
||||
print('++++++++$url');
|
||||
final response = await http.get(Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
);
|
||||
final updateTime = indexController.lastUserUpdateTime.value;
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
GeoJsonFeatureCollection cc = await featuresFromGeoJson(utf8.decode(response.bodyBytes));
|
||||
//print(cc);
|
||||
return cc; //featuresFromGeoJson(utf8.decode(response.bodyBytes));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// ユーザー情報の更新を最大5秒間待つ
|
||||
|
||||
try {
|
||||
/*
|
||||
// ユーザー情報の更新を最大5秒間待つ
|
||||
final newUpdateTime = await indexController.lastUserUpdateTime.stream
|
||||
.firstWhere(
|
||||
(time) => time.isAfter(updateTime),
|
||||
orElse: () => updateTime,
|
||||
).timeout(Duration(seconds: 5));
|
||||
|
||||
static Future<GeoJsonFeatureCollection?> loadLocationsBound(double lat1, double lon1, double lat2, double lon2, double lat3, double lon3, double lat4, double lon4, String cat) async {
|
||||
final IndexController indexController = Get.find<IndexController>();
|
||||
print("-------- in location for bound -------------");
|
||||
print("-------- in location for bound current user ${indexController.currentUser} -------------");
|
||||
String url = "";
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
if(cat.isNotEmpty){
|
||||
if(indexController.currentUser.isNotEmpty){
|
||||
bool rog = indexController.currentUser[0]['user']['is_rogaining'];
|
||||
String r = rog == true ? 'True': 'False';
|
||||
var grp = indexController.currentUser[0]['user']['event_code'];
|
||||
url = '$serverUrl/api/inbound?rog=$r&grp=$grp&ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4&cat=$cat';
|
||||
}
|
||||
else{
|
||||
url = '$serverUrl/api/inbound?ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4&cat=$cat';
|
||||
}
|
||||
}
|
||||
else{
|
||||
if(indexController.currentUser.isNotEmpty){
|
||||
bool rog = indexController.currentUser[0]['user']['is_rogaining'];
|
||||
String r = rog == true ? 'True': 'False';
|
||||
var grp = indexController.currentUser[0]['user']['event_code'];
|
||||
print("-------- requested user group $grp -------------");
|
||||
url = '$serverUrl/api/inbound?rog=$r&grp=$grp&ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4';
|
||||
}
|
||||
else{
|
||||
url = '$serverUrl/api/inbound?ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4';
|
||||
}
|
||||
}
|
||||
print('++++++++$url');
|
||||
final response = await http.get(Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 500) {
|
||||
return GeoJsonFeatureCollection(); //featuresFromGeoJson(utf8.decode(response.bodyBytes));
|
||||
}
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
GeoJsonFeatureCollection cc = await featuresFromGeoJson(utf8.decode(response.bodyBytes));
|
||||
if(cc.collection.isEmpty){
|
||||
if (newUpdateTime == updateTime) {
|
||||
print('ユーザー情報の更新がタイムアウトしました');
|
||||
// タイムアウト時の処理(例:エラー表示やリトライ)
|
||||
return null;
|
||||
}
|
||||
else{
|
||||
//print("---- feature got from server is ${cc.collection[0].properties} ------");
|
||||
return cc;
|
||||
*/
|
||||
|
||||
/*
|
||||
await indexController.lastUserUpdateTime.stream.firstWhere(
|
||||
(time) => time.isAfter(updateTime),
|
||||
orElse: () => updateTime,
|
||||
).timeout(Duration(seconds: 2), onTimeout: () => updateTime);
|
||||
*/
|
||||
|
||||
String url = "";
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
if (cat.isNotEmpty) {
|
||||
if (indexController.currentUser.isNotEmpty) {
|
||||
bool rog = indexController.currentUser[0]['user']['is_rogaining'];
|
||||
String r = rog == true ? 'True' : 'False';
|
||||
var grp = eventCode; //indexController.currentUser[0]['user']['event_code'];
|
||||
print("Group=$grp");
|
||||
url =
|
||||
'$serverUrl/api/inbound2?rog=$r&grp=$grp&ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4&cat=$cat';
|
||||
} else {
|
||||
url =
|
||||
'$serverUrl/api/inbound2?ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4&cat=$cat';
|
||||
}
|
||||
} else {
|
||||
if (indexController.currentUser.isNotEmpty) {
|
||||
bool rog = indexController.currentUser[0]['user']['is_rogaining'];
|
||||
String r = rog == true ? 'True' : 'False';
|
||||
var grp = indexController.currentUser[0]['user']['event_code'];
|
||||
print("-------- requested user group $grp -------------");
|
||||
url =
|
||||
'$serverUrl/api/inbound2?rog=$r&grp=$grp&ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4';
|
||||
} else {
|
||||
url =
|
||||
'$serverUrl/api/inbound2?ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4';
|
||||
}
|
||||
print('++++++++$url');
|
||||
final response = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 500) {
|
||||
return null; //featuresFromGeoJson(utf8.decode(response.bodyBytes));
|
||||
}
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
DestinationController destinationController =
|
||||
Get.find<DestinationController>();
|
||||
|
||||
GeoJSONFeatureCollection cc =
|
||||
GeoJSONFeatureCollection.fromJSON(utf8.decode(response.bodyBytes));
|
||||
if (cc.features.isEmpty) {
|
||||
return null;
|
||||
} else {
|
||||
//print("---- feature got from server is ${cc.collection[0].properties} ------");
|
||||
return cc;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
print("Error: $e");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
static Future<GeoJsonFeatureCollection?> loadCustomLocations(String name, String cat) async {
|
||||
static Future<GeoJSONFeatureCollection?> loadCustomLocations(
|
||||
String name, String cat) async {
|
||||
final IndexController indexController = Get.find<IndexController>();
|
||||
String url = "";
|
||||
if(cat == "-all-"){
|
||||
if (cat == "-all-") {
|
||||
cat = "";
|
||||
}
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
print("loadCustomLocations url is ----- $cat");
|
||||
if(cat.isNotEmpty){
|
||||
if(indexController.currentUser.isNotEmpty){
|
||||
//print("loadCustomLocations url is ----- $cat");
|
||||
if (cat.isNotEmpty) {
|
||||
if (indexController.currentUser.isNotEmpty) {
|
||||
bool rog = indexController.currentUser[0]['user']['is_rogaining'];
|
||||
String r = rog == true ? 'True': 'False';
|
||||
String r = rog == true ? 'True' : 'False';
|
||||
var grp = indexController.currentUser[0]['user']['event_code'];
|
||||
url = '$serverUrl/api/custom_area/?rog=$r&&cat=$cat';
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
url = '$serverUrl/api/custom_area/?&cat=$cat';
|
||||
}
|
||||
}
|
||||
else{
|
||||
if(indexController.currentUser.isNotEmpty){
|
||||
} else {
|
||||
if (indexController.currentUser.isNotEmpty) {
|
||||
bool rog = indexController.currentUser[0]['user']['is_rogaining'];
|
||||
String r = rog == true ? 'True': 'False';
|
||||
String r = rog == true ? 'True' : 'False';
|
||||
var grp = indexController.currentUser[0]['user']['event_code'];
|
||||
url = '$serverUrl/api/customarea?rog=$r&name=$name';
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
url = '$serverUrl/api/customarea?name=$name';
|
||||
}
|
||||
}
|
||||
print('++++++++$url');
|
||||
final response = await http.get(Uri.parse(url),
|
||||
//print('++++++++$url');
|
||||
final response = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 500) {
|
||||
return GeoJsonFeatureCollection(); //featuresFromGeoJson(utf8.decode(response.bodyBytes));
|
||||
return null; //featuresFromGeoJson(utf8.decode(response.bodyBytes));
|
||||
}
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
GeoJsonFeatureCollection cc = await featuresFromGeoJson(utf8.decode(response.bodyBytes));
|
||||
if(cc.collection.isEmpty){
|
||||
GeoJSONFeatureCollection cc =
|
||||
GeoJSONFeatureCollection.fromJSON(utf8.decode(response.bodyBytes));
|
||||
if (cc.features.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
else{
|
||||
return cc;
|
||||
} else {
|
||||
return cc;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static const platform = MethodChannel('location');
|
||||
|
||||
|
||||
|
||||
}
|
||||
static Future<bool> isLocationServiceRunning() async {
|
||||
try {
|
||||
final bool isRunning = await platform.invokeMethod('isLocationServiceRunning');
|
||||
return isRunning;
|
||||
} catch (e) {
|
||||
print("Failed to check if location service is running: $e");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,38 +1,35 @@
|
||||
|
||||
import 'dart:convert';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:rogapp/model/destination.dart';
|
||||
import 'package:rogapp/pages/destination/destination_controller.dart';
|
||||
import 'package:gifunavi/model/destination.dart';
|
||||
import 'package:gifunavi/pages/destination/destination_controller.dart';
|
||||
|
||||
|
||||
class MatrixService{
|
||||
|
||||
static Future<Map<String, dynamic>> getDestinations(List<Destination> destinations) async {
|
||||
|
||||
final DestinationController destinationController = Get.find<DestinationController>();
|
||||
class MatrixService {
|
||||
static Future<Map<String, dynamic>> getDestinations(
|
||||
List<Destination> destinations) async {
|
||||
final DestinationController destinationController =
|
||||
Get.find<DestinationController>();
|
||||
|
||||
String locs = "";
|
||||
String origin = "";
|
||||
String destination = "";
|
||||
int i = 0;
|
||||
for(Destination d in destinations){
|
||||
for (Destination d in destinations) {
|
||||
//print("---- getting matrix for $d ------------");
|
||||
|
||||
print("---- getting matrix for $d ------------");
|
||||
|
||||
if(i==0){
|
||||
if (i == 0) {
|
||||
origin = "${d.lat}, ${d.lon}";
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(i == (destinations.length - 1)){
|
||||
if (i == (destinations.length - 1)) {
|
||||
destination = "${d.lat}, ${d.lon}";
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
print("lat is ${d.lat}, long is ${d.lon}");
|
||||
//print("lat is ${d.lat}, long is ${d.lon}");
|
||||
locs += "${d.lat}, ${d.lon}|";
|
||||
i++;
|
||||
}
|
||||
@ -46,28 +43,24 @@ class MatrixService{
|
||||
break;
|
||||
case 2:
|
||||
mode = "transit";
|
||||
break;
|
||||
break;
|
||||
default:
|
||||
mode = "walking";
|
||||
break;
|
||||
}
|
||||
|
||||
Map<String, dynamic> cats = {};
|
||||
String url = "https://maps.googleapis.com/maps/api/directions/json?destination=$destination&mode=$mode&waypoints=$locs&origin=$origin&key=AIzaSyAUBI1ablMKuJwGj2-kSuEhvYxvB1A-mOE";
|
||||
print('++++++++$url');
|
||||
final http.Response response = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
}
|
||||
);
|
||||
String url =
|
||||
"https://maps.googleapis.com/maps/api/directions/json?destination=$destination&mode=$mode&waypoints=$locs&origin=$origin&key=AIzaSyCN2xFsqFyadWwpjiFxymrxzS6G1tNzraI";
|
||||
//print('++++++++$url');
|
||||
final http.Response response =
|
||||
await http.get(Uri.parse(url), headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
});
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
cats = json.decode(utf8.decode(response.bodyBytes));
|
||||
}
|
||||
return cats;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -1,23 +1,21 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:rogapp/utils/const.dart';
|
||||
|
||||
|
||||
class PerfectureService{
|
||||
import 'package:gifunavi/utils/const.dart';
|
||||
|
||||
class PerfectureService {
|
||||
static Future<List<dynamic>?> loadPerfectures() async {
|
||||
List<dynamic> perfs = [];
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = '$serverUrl/api/perf_main/';
|
||||
print('++++++++$url');
|
||||
final response = await http.get(Uri.parse(url),
|
||||
//print('++++++++$url');
|
||||
final response = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
|
||||
perfs = json.decode(utf8.decode(response.bodyBytes));
|
||||
}
|
||||
return perfs;
|
||||
@ -27,34 +25,33 @@ class PerfectureService{
|
||||
List<dynamic> perfs = [];
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = '$serverUrl/api/subperfinmain/?area=$area';
|
||||
print('++++++++$url');
|
||||
final response = await http.get(Uri.parse(url),
|
||||
//print('++++++++$url');
|
||||
final response = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
|
||||
perfs = json.decode(utf8.decode(response.bodyBytes));
|
||||
}
|
||||
return perfs;
|
||||
}
|
||||
|
||||
|
||||
static Future<List<dynamic>?> getMainPerfExt(String id) async {
|
||||
List<dynamic> perfs = [];
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = '$serverUrl/api/mainperfext/?perf=$id';
|
||||
print('++++++++$url');
|
||||
final response = await http.get(Uri.parse(url),
|
||||
//print('++++++++$url');
|
||||
final response = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
|
||||
perfs = json.decode(utf8.decode(response.bodyBytes));
|
||||
}
|
||||
return perfs;
|
||||
@ -64,15 +61,15 @@ class PerfectureService{
|
||||
List<dynamic> perfs = [];
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = '$serverUrl/api/allgifuareas/?perf=$perf';
|
||||
print('++++++++$url');
|
||||
final response = await http.get(Uri.parse(url),
|
||||
//print('++++++++$url');
|
||||
final response = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
|
||||
perfs = json.decode(utf8.decode(response.bodyBytes));
|
||||
}
|
||||
return perfs;
|
||||
@ -82,40 +79,35 @@ class PerfectureService{
|
||||
List<dynamic> perfs = [];
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = '$serverUrl/api/customareanames';
|
||||
print('++++++++$url');
|
||||
final response = await http.get(Uri.parse(url),
|
||||
//print('++++++++$url');
|
||||
final response = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
|
||||
perfs = json.decode(utf8.decode(response.bodyBytes));
|
||||
}
|
||||
return perfs;
|
||||
}
|
||||
|
||||
|
||||
static Future<List<dynamic>?> getSubExt(String id) async {
|
||||
List<dynamic> perfs = [];
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = '$serverUrl/api/perfext/?sub_perf=$id';
|
||||
print('++++++++$url');
|
||||
final response = await http.get(Uri.parse(url),
|
||||
//print('++++++++$url');
|
||||
final response = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
|
||||
perfs = json.decode(utf8.decode(response.bodyBytes));
|
||||
}
|
||||
return perfs;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -1,27 +1,22 @@
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:rogapp/utils/const.dart';
|
||||
|
||||
import 'package:gifunavi/utils/const.dart';
|
||||
|
||||
class TrackingService {
|
||||
|
||||
static Future<Map<String, dynamic>> addTrack(String userId, double lat, double lon) async {
|
||||
static Future<Map<String, dynamic>> addTrack(
|
||||
String userId, double lat, double lon) async {
|
||||
Map<String, dynamic> cats = {};
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = '$serverUrl/api/track/';
|
||||
print('++++++++$url');
|
||||
//print('++++++++$url');
|
||||
final geom = '{"type": "MULTIPOINT", "coordinates": [[$lon, $lat]]}';
|
||||
final http.Response response = await http.post(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
body: jsonEncode(<String, String>{
|
||||
'user_id': userId,
|
||||
'geom': geom
|
||||
}),
|
||||
body: jsonEncode(<String, String>{'user_id': userId, 'geom': geom}),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
@ -29,5 +24,4 @@ class TrackingService {
|
||||
}
|
||||
return cats;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/spa/spa_controller.dart';
|
||||
import 'package:gifunavi/spa/spa_controller.dart';
|
||||
|
||||
class SpaBinding extends Bindings {
|
||||
@override
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/spa/spa_controller.dart';
|
||||
import 'package:gifunavi/spa/spa_controller.dart';
|
||||
|
||||
class SpaPage extends GetView<SpaController> {
|
||||
const SpaPage({Key? key}) : super(key: key);
|
||||
const SpaPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@ -2,7 +2,9 @@
|
||||
|
||||
|
||||
class ConstValues{
|
||||
static const container_svr = "http://container.intranet.sumasen.net:8100";
|
||||
//static const container_svr = "http://container.intranet.sumasen.net:8100";
|
||||
//static const server_uri = "https://rogaining.intranet.sumasen.net";
|
||||
static const container_svr = "http://container.sumasen.net:8100";
|
||||
static const server_uri = "https://rogaining.sumasen.net";
|
||||
static const dev_server = "http://localhost:8100";
|
||||
static const dev_ip_server = "http://192.168.8.100:8100";
|
||||
@ -10,6 +12,7 @@ class ConstValues{
|
||||
static const dev_home_ip_mserver = "http://192.168.1.10:8100";
|
||||
|
||||
static String currentServer(){
|
||||
//return dev_ip_server;
|
||||
return server_uri;
|
||||
}
|
||||
}
|
||||
|
||||
102
lib/utils/database_gps.dart
Normal file
102
lib/utils/database_gps.dart
Normal file
@ -0,0 +1,102 @@
|
||||
import 'dart:io';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:gifunavi/model/gps_data.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
|
||||
class GpsDatabaseHelper {
|
||||
GpsDatabaseHelper._privateConstructor();
|
||||
static final GpsDatabaseHelper instance =
|
||||
GpsDatabaseHelper._privateConstructor();
|
||||
static Database? _database;
|
||||
Future<Database> get database async => _database ??= await _initDatabase();
|
||||
|
||||
Future<Database> _initDatabase() async {
|
||||
Directory documentDirectory = await getApplicationDocumentsDirectory();
|
||||
String path = join(documentDirectory.path, 'rog.db');
|
||||
// return await openDatabase(
|
||||
// path,
|
||||
// version: 1,
|
||||
// onCreate: _onCreate,
|
||||
// );
|
||||
return openDatabase(
|
||||
join(
|
||||
await getDatabasesPath(),
|
||||
'gps.db',
|
||||
),
|
||||
version: 1,
|
||||
onCreate: _onCreate);
|
||||
}
|
||||
|
||||
Future _onCreate(Database db, int version) async {
|
||||
await db.execute('''
|
||||
CREATE TABLE gps(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
team_name TEXT,
|
||||
event_code TEXT,
|
||||
lat REAL,
|
||||
lon REAL,
|
||||
is_checkin int,
|
||||
created_at INTEGER,
|
||||
is_synced INTEGER DEFAULT 0
|
||||
)
|
||||
''');
|
||||
}
|
||||
|
||||
Future<int> insertGps(GpsData gps) async {
|
||||
try {
|
||||
//print("---- try insering ${gps.toMap()}");
|
||||
Database db = await instance.database;
|
||||
int? nextOrder =
|
||||
Sqflite.firstIntValue(await db.rawQuery('SELECT MAX(id) FROM gps'));
|
||||
nextOrder = nextOrder ?? 0;
|
||||
nextOrder = nextOrder + 1;
|
||||
gps.id = nextOrder;
|
||||
//print("---- insering ${gps.toMap()}");
|
||||
int res = await db.insert(
|
||||
'gps',
|
||||
gps.toMap(),
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
//print("------ database helper insert $res-----------::::::::");
|
||||
return res;
|
||||
} catch (err) {
|
||||
print("------ error $err-----------::::::::");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<GpsData>> getGPSData(String teamName, String eventCode) async {
|
||||
Database db = await instance.database;
|
||||
var gpss = await db.query('gps',
|
||||
where: "team_name = ? and event_code = ?",
|
||||
whereArgs: [teamName, eventCode],
|
||||
orderBy: 'created_at');
|
||||
List<GpsData> gpsDatas =
|
||||
gpss.isNotEmpty ? gpss.map((e) => GpsData.fromMap(e)).toList() : [];
|
||||
//print("--------- db list $gpsDatas");
|
||||
return gpsDatas;
|
||||
}
|
||||
|
||||
Future<List<GpsData>> getUnsyncedGPSData(
|
||||
String teamName, String eventCode) async {
|
||||
Database db = await instance.database;
|
||||
var gpss = await db.query('gps',
|
||||
where: 'team_name = ? and event_code = ? and is_synced = 0',
|
||||
whereArgs: [teamName, eventCode],
|
||||
orderBy: 'created_at');
|
||||
return gpss.isNotEmpty ? gpss.map((e) => GpsData.fromMap(e)).toList() : [];
|
||||
}
|
||||
|
||||
Future<void> setSyncData(List<GpsData> data) async {
|
||||
Database db = await instance.database;
|
||||
for (var record in data) {
|
||||
await db.update(
|
||||
'gps',
|
||||
{'is_synced': 1},
|
||||
where: 'id = ?',
|
||||
whereArgs: [record.id],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,12 @@
|
||||
import 'dart:io';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:rogapp/model/destination.dart';
|
||||
import 'package:gifunavi/model/destination.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
import '../model/rog.dart';
|
||||
|
||||
class DatabaseHelper{
|
||||
class DatabaseHelper {
|
||||
DatabaseHelper._privateConstructor();
|
||||
static final DatabaseHelper instance = DatabaseHelper._privateConstructor();
|
||||
|
||||
@ -21,7 +21,13 @@ class DatabaseHelper{
|
||||
// version: 1,
|
||||
// onCreate: _onCreate,
|
||||
// );
|
||||
return openDatabase(join(await getDatabasesPath(), 'rog.db',), version: 1, onCreate: _onCreate);
|
||||
return openDatabase(
|
||||
join(
|
||||
await getDatabasesPath(),
|
||||
'rog.db',
|
||||
),
|
||||
version: 1,
|
||||
onCreate: _onCreate);
|
||||
}
|
||||
|
||||
Future _onCreate(Database db, int version) async {
|
||||
@ -48,11 +54,16 @@ class DatabaseHelper{
|
||||
cp REAL,
|
||||
checkin_point REAL,
|
||||
buy_point REAL,
|
||||
hidden_location INTEGER
|
||||
hidden_location INTEGER,
|
||||
checkin_image TEXT,
|
||||
buypoint_image TEXT,
|
||||
forced_checkin INTEGER,
|
||||
recipt_times INTEGER,
|
||||
tags TEXT
|
||||
)
|
||||
''');
|
||||
|
||||
await db.execute('''
|
||||
await db.execute('''
|
||||
CREATE TABLE rogaining(
|
||||
rog_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
course_id INTEGER,
|
||||
@ -65,7 +76,7 @@ class DatabaseHelper{
|
||||
)
|
||||
''');
|
||||
|
||||
await db.execute('''
|
||||
await db.execute('''
|
||||
CREATE TABLE rog(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
team_name TEXT,
|
||||
@ -77,86 +88,72 @@ class DatabaseHelper{
|
||||
rog_action_type INTEGER
|
||||
)
|
||||
''');
|
||||
|
||||
}
|
||||
|
||||
Future<List<Rog>> allRogianing() async {
|
||||
Database db = await instance.database;
|
||||
var rog = await db.query('rog');
|
||||
List<Rog> roglist = rog.isNotEmpty ?
|
||||
rog.map((e) => Rog.fromMap(e)).toList() : [];
|
||||
print("--------- $rog");
|
||||
List<Rog> roglist =
|
||||
rog.isNotEmpty ? rog.map((e) => Rog.fromMap(e)).toList() : [];
|
||||
//print("--------- $rog");
|
||||
return roglist;
|
||||
}
|
||||
|
||||
|
||||
Future<List<Rog>> getRogainingByLatLon(double lat, double lon) async {
|
||||
Database db = await instance.database;
|
||||
var rog = await db.query('rog', where: "lat = $lat and lon= $lon");
|
||||
List<Rog> roglist = rog.isNotEmpty
|
||||
? rog.map((e) => Rog.fromMap(e)).toList() : [];
|
||||
List<Rog> roglist =
|
||||
rog.isNotEmpty ? rog.map((e) => Rog.fromMap(e)).toList() : [];
|
||||
return roglist;
|
||||
}
|
||||
|
||||
Future clearSelection() async {
|
||||
Database db = await instance.database;
|
||||
Map<String, dynamic> rowClear = {
|
||||
"selected": false
|
||||
};
|
||||
return await db.update(
|
||||
"destination",
|
||||
rowClear
|
||||
);
|
||||
Map<String, dynamic> rowClear = {"selected": false};
|
||||
return await db.update("destination", rowClear);
|
||||
}
|
||||
|
||||
Future<int> toggleSelecttion(Destination dest) async {
|
||||
Database db = await instance.database;
|
||||
|
||||
|
||||
bool val = !dest.selected!;
|
||||
Map<String, dynamic> rowTarget = {
|
||||
"selected": val
|
||||
};
|
||||
Map<String, dynamic> rowTarget = {"selected": val};
|
||||
|
||||
await clearSelection();
|
||||
|
||||
return await db.update(
|
||||
"destination",
|
||||
rowTarget,
|
||||
where: 'location_id = ?',
|
||||
whereArgs: [dest.location_id!]
|
||||
);
|
||||
return await db.update("destination", rowTarget,
|
||||
where: 'location_id = ?', whereArgs: [dest.location_id!]);
|
||||
}
|
||||
|
||||
Future<int> deleteRogaining(int id) async {
|
||||
Database db = await instance.database;
|
||||
var rog = await db.delete('rog', where: "id = $id");
|
||||
int ret = rog > 0 ? rog : -1;
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
Future<void> deleteAllRogaining() async {
|
||||
Database db = await instance.database;
|
||||
await db.delete('rog');
|
||||
}
|
||||
|
||||
|
||||
Future<bool>isRogAlreadyAvailable(int id) async{
|
||||
Future<bool> isRogAlreadyAvailable(int id) async {
|
||||
Database db = await instance.database;
|
||||
var rog = await db.query('rog', where: "id = $id");
|
||||
return rog.isNotEmpty ? true : false;
|
||||
}
|
||||
|
||||
Future<int?>latestGoal() async{
|
||||
Future<int?> latestGoal() async {
|
||||
Database db = await instance.database;
|
||||
return Sqflite.firstIntValue(await db.rawQuery('SELECT MAX(checkintime) FROM rog'));
|
||||
|
||||
return Sqflite.firstIntValue(
|
||||
await db.rawQuery('SELECT MAX(checkintime) FROM rog'));
|
||||
}
|
||||
|
||||
Future<int> insertRogaining(Rog rog) async {
|
||||
Database db = await instance.database;
|
||||
int? nextOrder = Sqflite.firstIntValue(await db.rawQuery('SELECT MAX(id) FROM rog'));
|
||||
int? nextOrder =
|
||||
Sqflite.firstIntValue(await db.rawQuery('SELECT MAX(id) FROM rog'));
|
||||
nextOrder = nextOrder ?? 0;
|
||||
nextOrder = nextOrder + 1;
|
||||
rog.id = nextOrder;
|
||||
@ -165,68 +162,63 @@ class DatabaseHelper{
|
||||
rog.toMap(),
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
print("------ database helper insert $res-----------::::::::");
|
||||
//print("------ database helper insert $res-----------::::::::");
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
Future<List<Destination>> getDestinations() async {
|
||||
Database db = await instance.database;
|
||||
var dest = await db.query('destination', orderBy: 'list_order');
|
||||
List<Destination> destList = dest.isNotEmpty ?
|
||||
dest.map((e) => Destination.fromMap(e)).toList() : [];
|
||||
print("--------- $destList");
|
||||
List<Destination> destList =
|
||||
dest.isNotEmpty ? dest.map((e) => Destination.fromMap(e)).toList() : [];
|
||||
//print("--------- $destList");
|
||||
return destList;
|
||||
}
|
||||
|
||||
Future<List<Destination>> getDestinationById(int id) async {
|
||||
Database db = await instance.database;
|
||||
var rog = await db.query('destination', where: "location_id = $id");
|
||||
List<Destination> deslist = rog.isNotEmpty
|
||||
? rog.map((e) => Destination.fromMap(e)).toList() : [];
|
||||
List<Destination> deslist =
|
||||
rog.isNotEmpty ? rog.map((e) => Destination.fromMap(e)).toList() : [];
|
||||
return deslist;
|
||||
}
|
||||
|
||||
|
||||
Future<List<Destination>> getDestinationByLatLon(double lat, double lon) async {
|
||||
Future<List<Destination>> getDestinationByLatLon(
|
||||
double lat, double lon) async {
|
||||
Database db = await instance.database;
|
||||
var dest = await db.query('destination', where: "lat = $lat and lon= $lon", orderBy: 'list_order');
|
||||
List<Destination> destList = dest.isNotEmpty
|
||||
? dest.map((e) => Destination.fromMap(e)).toList() : [];
|
||||
var dest = await db.query('destination',
|
||||
where: "lat = $lat and lon= $lon", orderBy: 'list_order');
|
||||
List<Destination> destList =
|
||||
dest.isNotEmpty ? dest.map((e) => Destination.fromMap(e)).toList() : [];
|
||||
return destList;
|
||||
}
|
||||
|
||||
Future<int> deleteDestination(int locationId) async {
|
||||
Database db = await instance.database;
|
||||
var dest = await db.delete('destination', where: "location_id = $locationId");
|
||||
var dest =
|
||||
await db.delete('destination', where: "location_id = $locationId");
|
||||
int ret = dest > 0 ? dest : -1;
|
||||
|
||||
//after deleting set correct order
|
||||
await setOrder();
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Future<void>setOrder() async {
|
||||
Future<void> setOrder() async {
|
||||
Database db = await instance.database;
|
||||
var byOrder = await db.query('destination', orderBy: 'list_order');
|
||||
|
||||
List<Destination> desDb = byOrder.isNotEmpty
|
||||
? byOrder.map((e) => Destination.fromMap(e)).toList() : [];
|
||||
? byOrder.map((e) => Destination.fromMap(e)).toList()
|
||||
: [];
|
||||
|
||||
int order = 1;
|
||||
for( Destination d in desDb){
|
||||
for (Destination d in desDb) {
|
||||
Map<String, dynamic> rowTarget = {"list_order": order};
|
||||
|
||||
Map<String, dynamic> rowTarget = {
|
||||
"list_order": order
|
||||
};
|
||||
|
||||
await db.update(
|
||||
"destination",
|
||||
rowTarget,
|
||||
where: 'location_id = ?',
|
||||
whereArgs: [d.location_id]
|
||||
);
|
||||
await db.update("destination", rowTarget,
|
||||
where: 'location_id = ?', whereArgs: [d.location_id]);
|
||||
|
||||
order += 1;
|
||||
}
|
||||
@ -237,15 +229,18 @@ class DatabaseHelper{
|
||||
await db.delete('destination');
|
||||
}
|
||||
|
||||
Future<bool>isAlreadyAvailable(int locationId) async{
|
||||
Future<bool> isAlreadyAvailable(int locationId) async {
|
||||
Database db = await instance.database;
|
||||
var dest = await db.query('destination', where: "location_id = $locationId");
|
||||
var dest =
|
||||
await db.query('destination', where: "location_id = $locationId");
|
||||
return dest.isNotEmpty ? true : false;
|
||||
}
|
||||
|
||||
Future<int> insertDestination(Destination dest) async {
|
||||
await deleteDestination(dest.location_id!);
|
||||
Database db = await instance.database;
|
||||
int? nextOrder = Sqflite.firstIntValue(await db.rawQuery('SELECT MAX(list_order) FROM destination'));
|
||||
int? nextOrder = Sqflite.firstIntValue(
|
||||
await db.rawQuery('SELECT MAX(list_order) FROM destination'));
|
||||
nextOrder = nextOrder ?? 0;
|
||||
nextOrder = nextOrder + 1;
|
||||
dest.list_order = nextOrder;
|
||||
@ -258,39 +253,50 @@ class DatabaseHelper{
|
||||
return res;
|
||||
}
|
||||
|
||||
Future<int> updateAction(Destination destination, bool checkin)async {
|
||||
Future<int> updateCancelBuyPoint(Destination destination) async {
|
||||
//print("---- updating puypint image in db -----");
|
||||
Database db = await instance.database;
|
||||
int act = checkin == false ? 0 : 1;
|
||||
Map<String, dynamic> row = {
|
||||
"checkedin": act
|
||||
};
|
||||
return await db.update(
|
||||
"destination",
|
||||
row,
|
||||
where: 'location_id = ?',
|
||||
whereArgs: [destination.location_id!]
|
||||
);
|
||||
Map<String, dynamic> row = {"buy_point": 0};
|
||||
return await db.update("destination", row,
|
||||
where: 'location_id = ?', whereArgs: [destination.location_id!]);
|
||||
}
|
||||
|
||||
Future<void> updateOrder(Destination d, int dir)async {
|
||||
Future<int> updateBuyPoint(Destination destination, String imageUrl) async {
|
||||
//print("---- updating puypint image in db -----");
|
||||
Database db = await instance.database;
|
||||
var target = await db.query('destination', where: "list_order = ${d.list_order! + dir}");
|
||||
var dest = await db.query('destination', where: "location_id = ${d.location_id}");
|
||||
Map<String, dynamic> row = {"buypoint_image": imageUrl};
|
||||
return await db.update("destination", row,
|
||||
where: 'location_id = ?', whereArgs: [destination.location_id!]);
|
||||
}
|
||||
|
||||
print("--- target in db is $target");
|
||||
print("--- destine in db is $dest");
|
||||
Future<int> updateAction(Destination destination, bool checkin) async {
|
||||
Database db = await instance.database;
|
||||
int act = checkin == false ? 0 : 1;
|
||||
Map<String, dynamic> row = {"checkedin": act};
|
||||
return await db.update("destination", row,
|
||||
where: 'location_id = ?', whereArgs: [destination.location_id!]);
|
||||
}
|
||||
|
||||
if(target.isNotEmpty){
|
||||
Future<void> updateOrder(Destination d, int dir) async {
|
||||
Database db = await instance.database;
|
||||
var target = await db.query('destination',
|
||||
where: "list_order = ${d.list_order! + dir}");
|
||||
var dest =
|
||||
await db.query('destination', where: "location_id = ${d.location_id}");
|
||||
|
||||
// print("--- target in db is $target");
|
||||
// print("--- destine in db is $dest");
|
||||
|
||||
if (target.isNotEmpty) {
|
||||
List<Destination> targetIndb = target.isNotEmpty
|
||||
? target.map((e) => Destination.fromMap(e)).toList() : [];
|
||||
? target.map((e) => Destination.fromMap(e)).toList()
|
||||
: [];
|
||||
|
||||
List<Destination> destIndb = dest.isNotEmpty
|
||||
? dest.map((e) => Destination.fromMap(e)).toList() : [];
|
||||
? dest.map((e) => Destination.fromMap(e)).toList()
|
||||
: [];
|
||||
|
||||
Map<String, dynamic> rowTarget = {
|
||||
"list_order": d.list_order
|
||||
};
|
||||
Map<String, dynamic> rowTarget = {"list_order": d.list_order};
|
||||
|
||||
Map<String, dynamic> rowDes = {
|
||||
"list_order": destIndb[0].list_order! + dir
|
||||
@ -299,19 +305,11 @@ class DatabaseHelper{
|
||||
// print("--- target destination is ${target_indb[0].location_id}");
|
||||
// print("--- destine destination is is ${dest_indb[0].location_id}");
|
||||
|
||||
await db.update(
|
||||
"destination",
|
||||
rowTarget,
|
||||
where: 'location_id = ?',
|
||||
whereArgs: [targetIndb[0].location_id]
|
||||
);
|
||||
await db.update("destination", rowTarget,
|
||||
where: 'location_id = ?', whereArgs: [targetIndb[0].location_id]);
|
||||
|
||||
await db.update(
|
||||
"destination",
|
||||
rowDes,
|
||||
where: 'location_id = ?',
|
||||
whereArgs: [destIndb[0].location_id]
|
||||
);
|
||||
await db.update("destination", rowDes,
|
||||
where: 'location_id = ?', whereArgs: [destIndb[0].location_id]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -319,6 +317,4 @@ class DatabaseHelper{
|
||||
// Database db = await instance.database;
|
||||
// return await Sqflite.firstIntValue(await db.rawQuery("SELECT COUNT(*) FROM incidents"));
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
||||
372
lib/utils/location_controller.dart
Normal file
372
lib/utils/location_controller.dart
Normal file
@ -0,0 +1,372 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
//import 'package:gifunavi/widgets/debug_widget.dart';
|
||||
import 'package:flutter_map_location_marker/flutter_map_location_marker.dart';
|
||||
import 'package:gifunavi/pages/destination/destination_controller.dart';
|
||||
import 'package:gifunavi/pages/permission/permission.dart';
|
||||
|
||||
// LocationControllerクラスは、GetxControllerを継承したクラスであり、位置情報の管理を担当しています。
|
||||
// LocationControllerは以下の機能を提供しています。
|
||||
// LocationControllerは、アプリ全体で位置情報を一元管理するための重要なコンポーネントです。
|
||||
// 他のコンポーネントは、LocationControllerから位置情報を取得し、位置情報に関連する機能を実装することができます。
|
||||
//
|
||||
// Features:
|
||||
// * 現在の位置情報を保持し、他のコンポーネントからアクセスできるようにします。
|
||||
// * 位置情報のストリームを管理し、位置情報の更新を監視します。
|
||||
// * 位置情報サービスの有効性と権限の確認を行い、適切な処理を行います。
|
||||
// * 位置情報のストリームを開始、停止、再開する機能を提供します。
|
||||
// * 位置マーカーの位置情報をStreamControllerを通じて他のコンポーネントに通知します。
|
||||
//
|
||||
// Logic:
|
||||
// 1. startPositionStreamメソッドで、Geolocator.getPositionStreamを使用して位置情報のストリームを開始します。
|
||||
// 2. ストリームから位置情報を受信すると、LocationMarkerPositionオブジェクトを作成し、locationMarkerPositionStreamControllerに追加します。
|
||||
// 3. 位置情報が取得できなかった場合や、エラーが発生した場合は、適切な処理を行います。
|
||||
// 4. stopPositionStreamメソッドで、位置情報のストリームを一時停止することができます。
|
||||
// 5. resumePositionStreamメソッドで、一時停止中の位置情報のストリームを再開することができます。
|
||||
// 6. onCloseメソッドで、コントローラーのクローズ時に位置情報のストリームをキャンセルします。
|
||||
//
|
||||
class LocationController extends GetxController {
|
||||
// Reactive variable to hold the current position
|
||||
Rx<Position?> currentPosition = Rx<Position?>(null);
|
||||
// 現在の位置情報を保持するReactive変数です。Rx<Position?>型で宣言されています。
|
||||
|
||||
// Subscription to the position stream
|
||||
StreamSubscription<Position>? positionStream;
|
||||
// 位置情報のストリームを保持する変数です。StreamSubscription<Position>型で宣言されています。
|
||||
|
||||
LatLng? lastValidLocation;
|
||||
DateTime lastGPSDataReceivedTime = DateTime.now(); // 最後にGPSデータを受け取った時刻
|
||||
|
||||
bool gpsDebugMode = true;
|
||||
/*
|
||||
// GPSシミュレーション用のメソッドを追加
|
||||
void setSimulationMode(bool value) {
|
||||
isSimulationMode = value;
|
||||
}
|
||||
|
||||
// ====== Akira , GPS信号強度をシミュレート ==== ここから
|
||||
//
|
||||
|
||||
//===== Akira Added 2024-4-9 start
|
||||
// GPSシミュレーション用の変数を追加 ===> 本番では false にする。
|
||||
bool isSimulationMode = false;
|
||||
|
||||
// GPS信号強度をシミュレートするための変数
|
||||
final Rx<String> _simulatedSignalStrength = Rx<String>('high');
|
||||
|
||||
// GPS信号強度をシミュレートするための関数
|
||||
void setSimulatedSignalStrength(String strength) {
|
||||
if( strength!='real') {
|
||||
isSimulationMode = true;
|
||||
_simulatedSignalStrength.value = strength;
|
||||
latestSignalStrength.value = strength;
|
||||
}else{
|
||||
isSimulationMode = false;
|
||||
_simulatedSignalStrength.value = strength;
|
||||
}
|
||||
}
|
||||
|
||||
// シミュレートされた信号強度を取得するための関数
|
||||
String getSimulatedSignalStrength() {
|
||||
//debugPrint("strength : ${_simulatedSignalStrength.value}");
|
||||
return _simulatedSignalStrength.value;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
//
|
||||
// ====== Akira , GPS信号強度をシミュレート ==== ここまで
|
||||
|
||||
|
||||
// GPS信号が弱い場合のフラグ. 本番では、false,high にする。
|
||||
bool isGpsSignalWeak = false;
|
||||
RxString latestSignalStrength = 'high'.obs;
|
||||
//final _latestSignalStrength = 'low'.obs; // 初期値を設定
|
||||
//String get latestSignalStrength => _latestSignalStrength.value;
|
||||
Stream<String> get gpsSignalStrengthStream => latestSignalStrength.stream;
|
||||
|
||||
bool isRunningBackgroundGPS=false;
|
||||
int activeEngineCount = 0;
|
||||
|
||||
// GPS信号の強弱を判断するメソッドを追加. 10m 以内:強、30m以内:中、それ以上:弱
|
||||
//
|
||||
String getGpsSignalStrength(Position? position) {
|
||||
if (isSimulationMode.value) {
|
||||
return getSimulatedSignalStrength();
|
||||
}
|
||||
|
||||
if (position == null) {
|
||||
//gpsDebugMode ? debugPrint("getGpsSignalStrength position is null.") : null;
|
||||
latestSignalStrength.value = "low";
|
||||
isGpsSignalWeak = true;
|
||||
return 'low';
|
||||
}
|
||||
final accuracy = position.accuracy;
|
||||
//gpsDebugMode ? debugPrint("getGpsSignalStrength : ${accuracy}") : null;
|
||||
/*
|
||||
if(isSimulationMode){
|
||||
return _simulatedSignalStrength.value; // GPS信号強度シミュレーション
|
||||
}else {
|
||||
*/
|
||||
if (accuracy <= 10) {
|
||||
latestSignalStrength.value = "high";
|
||||
isGpsSignalWeak = false;
|
||||
return 'high';
|
||||
} else if (accuracy <= 50) {
|
||||
latestSignalStrength.value = "medium";
|
||||
isGpsSignalWeak = false;
|
||||
return 'medium';
|
||||
} else {
|
||||
latestSignalStrength.value = "low";
|
||||
isGpsSignalWeak = true;
|
||||
return 'low';
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
// 現在位置を調整するメソッドを追加
|
||||
LatLng? adjustCurrentLocation(Position? position) {
|
||||
if (position == null) {
|
||||
if( lastValidLocation!=null ) {
|
||||
//debugPrint("=== adjustCurrentLocation (Position:Null and using LastValidLocation ${lastValidLocation})===");
|
||||
return LatLng(lastValidLocation!.latitude, lastValidLocation!.longitude);
|
||||
}else {
|
||||
print("=== adjustCurrentLocation (Position:Null and No LastValidLocation ... )===");
|
||||
return null;
|
||||
}
|
||||
//return lastValidLocation ?? LatLng(0, 0);
|
||||
}
|
||||
final signalStrength = getGpsSignalStrength(position);
|
||||
if (signalStrength == 'high' || signalStrength == 'medium') {
|
||||
//debugPrint("=== adjustCurrentLocation (Position:Get and return Valid location:${position} ... )===");
|
||||
lastValidLocation = LatLng(position.latitude, position.longitude);
|
||||
}
|
||||
return lastValidLocation ?? LatLng(lastValidLocation!.latitude, lastValidLocation!.longitude);
|
||||
}
|
||||
|
||||
//===== Akira Added 2024-4-9 end
|
||||
|
||||
final locationMarkerPositionStreamController =
|
||||
StreamController<LocationMarkerPosition?>.broadcast();
|
||||
// 位置マーカーの位置情報を送信するためのStreamControllerです。
|
||||
// StreamController<LocationMarkerPosition?>型で宣言されています。
|
||||
|
||||
bool isStreamPaused = false; // 位置情報のストリームが一時停止中かどうかを示すフラグです。bool型で宣言されています。
|
||||
|
||||
// 位置マーカーの位置情報のストリームを取得するゲッター関数です。
|
||||
// locationMarkerPositionStreamController.streamを返します。
|
||||
//
|
||||
Stream<LocationMarkerPosition?> get locationMarkerPositionStream =>
|
||||
locationMarkerPositionStreamController.stream;
|
||||
|
||||
// コントローラーの初期化時に呼び出されるライフサイクルメソッドです。
|
||||
// startPositionStreamメソッドを呼び出して、位置情報のストリームを開始します。
|
||||
//
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
// Start listening to location updates when the controller is initialized
|
||||
startPositionStream();
|
||||
|
||||
}
|
||||
|
||||
// 位置情報のストリームを開始するメソッドです。
|
||||
// 位置情報サービスが有効か確認し、無効な場合はダイアログを表示します。
|
||||
// 位置情報の権限を確認し、必要な権限がない場合は権限をリクエストします。
|
||||
// 既存の位置情報のストリームをキャンセルします。
|
||||
// Geolocator.getPositionStreamを使用して、新しい位置情報のストリームを開始します。
|
||||
// ストリームから受信した位置情報をlocationMarkerPositionStreamControllerに追加します。
|
||||
// エラーが発生した場合は、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();
|
||||
if (!serviceEnabled) {
|
||||
Get.snackbar('位置情報サービスが無効です', '設定から位置情報サービスを有効にしてください');
|
||||
return;
|
||||
}
|
||||
|
||||
await PermissionController.checkAndRequestPermissions();
|
||||
|
||||
// 位置情報の設定を行います。z11
|
||||
// Set up the location options
|
||||
const locationOptions =
|
||||
LocationSettings(accuracy: LocationAccuracy.medium, distanceFilter: 0);
|
||||
|
||||
// 既存の位置情報のストリームをキャンセルします。
|
||||
await positionStream?.cancel();
|
||||
|
||||
// 新しい位置情報のストリームを開始します。
|
||||
//
|
||||
positionStream = Geolocator.getPositionStream(locationSettings: locationOptions).listen(
|
||||
(Position? position) {
|
||||
//gpsDebugMode ? debugPrint("Position = ${position}"):null;
|
||||
final signalStrength = getGpsSignalStrength(position);
|
||||
if (signalStrength == 'low') {
|
||||
isGpsSignalWeak = true;
|
||||
//gpsDebugMode ? debugPrint("LocationController.getPositionStream : isGpsSignalWeak = ${isGpsSignalWeak}"):null;
|
||||
} else {
|
||||
isGpsSignalWeak = false;
|
||||
//gpsDebugMode ? debugPrint("LocationController.getPositionStream : isGpsSignalWeak = ${isGpsSignalWeak}"):null;
|
||||
}
|
||||
|
||||
final DestinationController destinationController = Get.find<DestinationController>();
|
||||
|
||||
// ロゲ開始前、終了後、GPS=low の場合は更新しない。
|
||||
//
|
||||
if (isGpsSignalWeak == false) {
|
||||
//if (destinationController.isInRog.value && isGpsSignalWeak == false) {
|
||||
final adjustedLocation = adjustCurrentLocation(position);
|
||||
if (adjustedLocation != null) {
|
||||
final locationMarkerPosition = LocationMarkerPosition(
|
||||
latitude: adjustedLocation.latitude,
|
||||
longitude: adjustedLocation.longitude,
|
||||
accuracy: position?.accuracy ?? 0,
|
||||
);
|
||||
handleLocationUpdate(locationMarkerPosition);
|
||||
//locationMarkerPositionStreamController.add(locationMarkerPosition); // 位置データ送信
|
||||
} else {
|
||||
// 位置情報が取得できなかった場合、
|
||||
// locationMarkerPositionStreamControllerにnullを追加します。
|
||||
locationMarkerPositionStreamController.add(null); // null 送信?
|
||||
//forceUpdateLocation(Position? position);
|
||||
|
||||
}
|
||||
//}else{
|
||||
// debugPrint("GPS処理対象外");
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
onError: (e) {
|
||||
// エラーが発生した場合、locationMarkerPositionStreamControllerにエラーを追加します。
|
||||
locationMarkerPositionStreamController.addError(e);
|
||||
},
|
||||
onDone: () {
|
||||
positionStream = null; // ストリームが完了したらnullに設定
|
||||
},
|
||||
cancelOnError: true // エラー発生時にストリームをキャンセル
|
||||
);
|
||||
|
||||
// Resume stream if it was paused previously
|
||||
// ストリームが一時停止中の場合、ストリームを再開します。
|
||||
//
|
||||
if (isStreamPaused) {
|
||||
isStreamPaused = false;
|
||||
positionStream!.resume();
|
||||
}
|
||||
}
|
||||
|
||||
// Method to stop the position stream
|
||||
// 位置情報のストリームを停止するメソッドです。
|
||||
// positionStreamが存在する場合、ストリームを一時停止します。
|
||||
// isStreamPausedフラグをtrueに設定します。
|
||||
//
|
||||
void stopPositionStream() async {
|
||||
if (positionStream != null) {
|
||||
// updated Akira 2024-4-8
|
||||
await positionStream!.cancel();
|
||||
positionStream = null;
|
||||
|
||||
//positionStream!.pause();
|
||||
//isStreamPaused = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Method to resume the position stream
|
||||
// 位置情報のストリームを再開するメソッドです。
|
||||
// positionStreamが存在し、ストリームが一時停止中の場合、ストリームを再開します。
|
||||
// isStreamPausedフラグをfalseに設定します。
|
||||
//
|
||||
void resumePositionStream() {
|
||||
if (positionStream != null && isStreamPaused) {
|
||||
positionStream!.resume();
|
||||
isStreamPaused = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void handleLocationUpdate(LocationMarkerPosition? position) async {
|
||||
//debugPrint("locationController.handleLocationUpdate");
|
||||
try {
|
||||
if (position != null) {
|
||||
double currentLat = position.latitude;
|
||||
double currentLon = position.longitude;
|
||||
//debugPrint("Flutter: Received GPS signal. Latitude: $currentLat, Longitude: $currentLon");
|
||||
|
||||
//debugPrint("position = ${position}");
|
||||
/*
|
||||
currentPosition.value = position;
|
||||
final locationMarkerPosition = LocationMarkerPosition(
|
||||
latitude: position.latitude,
|
||||
longitude: position.longitude,
|
||||
accuracy: position.accuracy,
|
||||
);
|
||||
*/
|
||||
lastGPSDataReceivedTime = DateTime.now(); // 最後にGPS信号を受け取った時刻
|
||||
locationMarkerPositionStreamController.add(position);
|
||||
}else{
|
||||
gpsDebugMode ? debugPrint("Flutter: No GPS signal received."):null;
|
||||
}
|
||||
} catch( e ) {
|
||||
debugPrint("Flutter: Error in handleLocationUpdate: $e");
|
||||
}
|
||||
}
|
||||
|
||||
// このメソッドは、現在の位置情報を locationMarkerPositionStreamController に送信します。
|
||||
//
|
||||
void forceUpdateLocation(Position? position) {
|
||||
if (position != null) {
|
||||
final adjustedLocation = adjustCurrentLocation(position);
|
||||
if (adjustedLocation != null) {
|
||||
final locationMarkerPosition = LocationMarkerPosition(
|
||||
latitude: adjustedLocation.latitude,
|
||||
longitude: adjustedLocation.longitude,
|
||||
accuracy: position.accuracy,
|
||||
);
|
||||
locationMarkerPositionStreamController.add(locationMarkerPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// コントローラーのクローズ時に呼び出されるライフサイクルメソッドです。
|
||||
// positionStreamをキャンセルします。
|
||||
//
|
||||
@override
|
||||
void onClose() {
|
||||
// Cancel the position stream subscription when the controller is closed
|
||||
positionStream?.cancel();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
// シミュレーションモードのフラグ
|
||||
RxBool isSimulationMode = RxBool(false);
|
||||
|
||||
// シミュレーションモードを切り替えるための関数
|
||||
void setSimulationMode(bool value) {
|
||||
isSimulationMode.value = value;
|
||||
}
|
||||
|
||||
// GPS信号強度をシミュレートするための変数
|
||||
final Rx<String> _simulatedSignalStrength = Rx<String>('high');
|
||||
|
||||
// GPS信号強度をシミュレートするための関数
|
||||
void setSimulatedSignalStrength(String strength) {
|
||||
_simulatedSignalStrength.value = strength;
|
||||
}
|
||||
|
||||
// シミュレートされた信号強度を取得するための関数
|
||||
String getSimulatedSignalStrength() {
|
||||
return _simulatedSignalStrength.value;
|
||||
}
|
||||
|
||||
}
|
||||
@ -7,7 +7,8 @@ class StringValues extends Translations{
|
||||
'drawer_title':'Rogaining participants can view checkpoints by logging in',
|
||||
'app_title': '- Rogaining -',
|
||||
'address':'address',
|
||||
'email':'Email',
|
||||
'bib':'Bib number',
|
||||
'email':'Email address',
|
||||
'password':'Password',
|
||||
'web':'Web',
|
||||
'wikipedia':'Wikipedia',
|
||||
@ -29,9 +30,9 @@ class StringValues extends Translations{
|
||||
'visit_history': 'Visit History',
|
||||
'rog_web': 'rog website',
|
||||
'no_values': 'No Values',
|
||||
'email_and_password_required': 'Email and password required',
|
||||
'email_and_password_required': 'Email and password are required to register user',
|
||||
'rogaining_user_need_tosign_up': "Rogaining participants do need to sign up.",
|
||||
'add_location': 'Add Location',
|
||||
'add_location': 'Gifu',
|
||||
'select_travel_mode':'Select your travel mode',
|
||||
'walking':'Walking',
|
||||
'driving': 'Driving',
|
||||
@ -70,16 +71,158 @@ class StringValues extends Translations{
|
||||
"Not reached the goal yet": "Not reached the goal yet",
|
||||
"You have not reached the goal yet.":"You have not reached the goal yet.",
|
||||
"delete_account": "Delete account",
|
||||
"delete_account_title": "Are you ok to delete your account?",
|
||||
"delete_account_middle": "All your account information and data history will be removed from local device and server side.",
|
||||
"accounted_deleted": "Account deleted",
|
||||
"account_deleted_message": "your account has beed successfully deleted",
|
||||
"privacy": "Privacy policy",
|
||||
"app_developed_by_gifu_dx": "This app was developed by the Gifu Prefecture DX subsidy project."
|
||||
"app_developed_by_gifu_dx": "This app was developed by the Gifu Prefecture DX subsidy project.",
|
||||
|
||||
'location_permission_title': 'Location Permission',
|
||||
'location_permission_content': 'Gifu Navi app collects location data to provide better services.\nLocation data is used for automatic check-in at checkpoints and delivery of notifications.\nLocation data may be collected even when the app is closed or not in use.\nCollected location data is only used as statistical information that cannot identify individuals and is never linked to personal information.\nIf you do not allow the use of location data, please select "Do not allow" on the next screen.',
|
||||
'location_disabled_title': 'Location Service Disabled',
|
||||
'location_disabled_content': 'Location information is disabled.\nTo continue, please enable location services for Gifu Navi in Settings > Privacy and Security > Location Services.',
|
||||
'drawer_title': 'Rogaining participants can view checkpoints by logging in',
|
||||
'app_title': 'Travel Itinerary',
|
||||
'want_to_go': 'Want to Go',
|
||||
'schedule_point': 'Schedule Point',
|
||||
'rog_web': 'Rogaining Website',
|
||||
'rogaining_user_need_tosign_up': "Rogaining participants do not need to sign up.",
|
||||
'add_location': 'Gifu',
|
||||
'finish': 'Finish',
|
||||
'my_route': 'My Route',
|
||||
'visit_history': 'Visit History',
|
||||
'search': 'Search',
|
||||
'login': 'Login',
|
||||
'password': 'Password',
|
||||
'already_have_account': 'Already have an account?',
|
||||
'sign_up': 'Sign Up',
|
||||
'create_account': 'Create an account, it\'s free',
|
||||
'confirm_password': 'Confirm Password',
|
||||
'cancel_checkin': 'Cancel Check-in',
|
||||
'go_here': 'Show route',
|
||||
'cancel_route':'Clear route',
|
||||
'start_rogaining': 'Start Rogaining',
|
||||
'in_game': 'In Game',
|
||||
'finish_rogaining': 'Finish Rogaining',
|
||||
'checkin': 'Check-in',
|
||||
'rogaining_not_started': 'Rogaining not started',
|
||||
'confirm': 'Confirm',
|
||||
'clear_rog_data_message': 'Starting rogaining will clear all previous rogaining data. Are you sure you want to start?',
|
||||
'no': 'No',
|
||||
'yes': 'Yes',
|
||||
'retake': 'Retake',
|
||||
'take_photo': 'Take Photo',
|
||||
'take_receipt_photo': 'Please take a photo of the receipt',
|
||||
'buypoint_added': 'Buypoint added.',
|
||||
'no_purchase': 'No Purchase',
|
||||
'complete': 'Complete',
|
||||
'movement_history': 'Movement History',
|
||||
'pass_history': 'Pass History',
|
||||
'no_checkin_yet': 'No check-in yet',
|
||||
'game_status': 'Game Status',
|
||||
'take_cp_photo': 'This is a CP. Please take a photo.',
|
||||
'save_goal_success': 'Goal saved successfully',
|
||||
'save_goal_failed': 'Goal not added',
|
||||
'please_try_again': 'Please try again',
|
||||
'click_start_to_start_rogaining': 'Click start to start rogaining',
|
||||
'at_rogaining_point_start': 'You are at a rogaining point, start rogaining',
|
||||
'start': 'Start',
|
||||
'rogaining_started': 'Rogaining Started',
|
||||
'rogaining_session_started': 'Rogaining session started',
|
||||
'not_started_yet': 'Not started yet',
|
||||
'not_started_rogaining_yet': 'You have not started rogaining yet.',
|
||||
'not_reached_goal_yet': 'Not reached the goal yet',
|
||||
'not_reached_goal_yet_message': 'You have not reached the goal yet.',
|
||||
'reload_qr': 'Reload QR',
|
||||
'read_qr': 'Read QR',
|
||||
'read_qr_code': 'Please read the QR code',
|
||||
'canceled': 'Canceled',
|
||||
'checkin_failed_try_again': 'Check-in failed. Please tap the checkpoint again if necessary.',
|
||||
'rogaining_not_started': 'Rogaining not started',
|
||||
'need_to_start_rogaining': 'You need to tap the start button to begin rogaining',
|
||||
'no_destination': 'No destination',
|
||||
'near_cp_not_checkin': 'Near a CP or distance-ignored CP, in-game but not checked in yet.',
|
||||
'auto_checkin_case': 'Auto check-in case',
|
||||
'non_auto_checkin_case': 'Non-auto check-in case',
|
||||
'normal_cp_case': 'Normal CP case',
|
||||
'non_normal_cp_case': 'Non-normal CP case... what case?',
|
||||
'goal_clock_photo_case': 'Goal clock photo case',
|
||||
'start_case_24_hours_passed': 'Start case and 24 hours have passed since the last goal',
|
||||
'start_cp_24_hours_passed': 'At the start CP, and 24 hours have passed since the last goal,',
|
||||
'standard_cp_not_checkin': 'Standard CP not checked in yet.',
|
||||
'after_checkin_buypoint_case': 'After check-in, buypoint case.',
|
||||
'goal_case': 'Goal case',
|
||||
'start_case': 'Start case',
|
||||
'no_match_skip_process': 'Does not match any conditions, skipping process',
|
||||
'server_error_occurred': 'A server error occurred',
|
||||
'could_not_communicate_with_server': 'Could not communicate with the server',
|
||||
'communication_error_occurred': 'A communication error occurred',
|
||||
'checked_in': 'Checked in.',
|
||||
'cancel_checkin': 'Cancel Check-in',
|
||||
'checkin_canceled_for': 'Check-in canceled for',
|
||||
'error': 'Error',
|
||||
'failed_to_cancel_checkin': 'Failed to cancel check-in.',
|
||||
'buypoint_added': 'Buypoint added',
|
||||
'error_occurred': 'An error occurred',
|
||||
'failed_to_process_checkpoint': 'Failed to process the checkpoint.',
|
||||
'start_rogaining': 'Start Rogaining',
|
||||
'in_competition': 'In Competition',
|
||||
'map_auto_return_message': 'If there is no map operation, it will automatically return to the current location. Please enter the timer seconds. If you check the checkbox, auto-return will not be performed.',
|
||||
'no_auto_return': 'No Auto Return',
|
||||
'failed_to_load_markers': 'Failed to load markers',
|
||||
'screen_switching_error': 'Screen switching error',
|
||||
'failed_to_switch_screen': 'Failed to switch the screen',
|
||||
'timer_duration': 'Timer Duration',
|
||||
'user_data_deletion': 'User Data Deletion',
|
||||
'user_consent_set_for_data_deletion': 'User consent is set for data deletion. User data has been deleted from the app and server',
|
||||
'go_to_gps_signal_area': 'Please go to an area with GPS signal.',
|
||||
'location_service_disabled': 'Location service is disabled. Please enable location service from the settings screen. If you are unsure, please contact the engineering staff.',
|
||||
'location_permission_not_granted': 'Location permission is not granted. Please allow location service for Gifu Navi from the settings screen. If you are unsure, please contact the engineering staff.',
|
||||
'location_service_issue_occurred': 'An issue occurred with the location service. The location service is being restarted, please wait a moment.',
|
||||
'login_failed': 'Login Failed',
|
||||
'check_login_id_or_password': 'Please check your login ID or password.',
|
||||
'communication_error_occurred': 'A communication error occurred',
|
||||
'could_not_communicate_with_server': 'Could not communicate with the server',
|
||||
'before_game': 'Before Game',
|
||||
'location_permission_denied_title': 'Location Permission Denied',
|
||||
'location_permission_denied_message': 'This app requires location permission to function properly. Please grant location permission to continue.',
|
||||
'location_permission_permanently_denied_title': 'Location Permission Permanently Denied',
|
||||
'location_permission_permanently_denied_message': 'Location permission has been permanently denied. Please open app settings to grant location permission.',
|
||||
'open_settings': 'Open Settings',
|
||||
'location_permission_needed_title': 'Location Permission Needed',
|
||||
'location_permission_needed_main': 'Location permissions have been permanently denied. Please open app settings to enable location permissions.',
|
||||
'open_settings': 'Open Settings',
|
||||
'location_services_disabled_title': 'Location Services Disabled',
|
||||
'location_service_disabled_main': 'Please enable location services to continue using the app.',
|
||||
'location_permission_denied_title': 'Location Permission Denied',
|
||||
'location_permission_denied_main': 'This app requires location permissions to function properly. Please enable location permissions in your device settings.',
|
||||
'home': 'Home',
|
||||
'welcome': 'Welcome to Gifu Navi',
|
||||
'location_disabled_message': 'Location services are disabled. Some features may not work properly.',
|
||||
'enable_location_service': 'Enable Location Service',
|
||||
'start_app': 'Start App',
|
||||
'location_permission_required_title': 'Location Permission Required',
|
||||
'location_permission_required_message': 'This app requires access to your location. Please grant permission to continue.',
|
||||
'cancel': 'Cancel',
|
||||
'checkins': 'Check-ins',
|
||||
'reset_button': 'Reset data',
|
||||
'reset_title': 'Reset the data in this device.',
|
||||
'reset_message': 'Are you ok to reset all data in this device?',
|
||||
'reset_done': 'Reset Done.',
|
||||
'reset_explain': 'All data has been reset. You should tap start rogaining to start game.',
|
||||
'no_match': 'No match!',
|
||||
'password_does_not_match':'The passwords you entered were not match.',
|
||||
'forgot_password':'Forgot password',
|
||||
'user_registration_successful':'Sent activation mail to you. Pls click activation link on the email.',
|
||||
|
||||
},
|
||||
'ja_JP': {
|
||||
'drawer_title':'ロゲイニング参加者はログイン するとチェックポイントが参照 できます',
|
||||
'app_title': '旅行工程表',
|
||||
'address':'住所',
|
||||
'email':'Eメール',
|
||||
'bib':'ゼッケン番号',
|
||||
'email':'メールアドレス',
|
||||
'password':'パスワード',
|
||||
'web':'ウェブ',
|
||||
'wikipedia':'ウィキペディア',
|
||||
@ -103,9 +246,9 @@ class StringValues extends Translations{
|
||||
'visit_history': '訪問履歴',
|
||||
'rog_web': 'ロゲイニングウェブサイト',
|
||||
'no_values': '値なし',
|
||||
'email_and_password_required': 'メールとパスワードが必要です',
|
||||
'email_and_password_required': 'メールとパスワードの入力が必要です',
|
||||
'rogaining_user_need_tosign_up': "ロゲイニング参加者はサインアップの必要はありません。",
|
||||
'add_location': '目的地選択',
|
||||
'add_location': '岐阜',
|
||||
'select_travel_mode':'移動モードを選択してください',
|
||||
'walking':'歩行',
|
||||
'driving': '自動車利用',
|
||||
@ -115,12 +258,12 @@ class StringValues extends Translations{
|
||||
'confirm': '確認',
|
||||
'cancel': 'キャンセル',
|
||||
'all_destinations_are_deleted_successfully' : 'すべての宛先が正常に削除されました',
|
||||
'deleted': "削除された",
|
||||
'deleted': "削除されました",
|
||||
'remarks' : '備考',
|
||||
'old_password' : '以前のパスワード',
|
||||
'new_password' : '新しいパスワード',
|
||||
'values_required' : '必要な値',
|
||||
'failed' : '失敗した',
|
||||
'failed' : '失敗',
|
||||
'password_change_failed_please_try_again' : 'パスワードの変更に失敗しました。もう一度お試しください',
|
||||
'user_registration_failed_please_try_again' : 'ユーザー登録に失敗しました。もう一度お試しください',
|
||||
'all': '全て',
|
||||
@ -129,13 +272,13 @@ class StringValues extends Translations{
|
||||
'finishing_rogaining' : 'ロゲイニングを終えて',
|
||||
'cp_pls_take_photo' : "CPです。撮影してください。",
|
||||
'take_photo of the clock' : '時計の写真を撮る',
|
||||
'finish_goal': 'フィニッシュゴール',
|
||||
'finish_goal': 'ゴール完了',
|
||||
'goal_saved': "目標を保存しました",
|
||||
'goal_added_successfuly' : '目標が正常に追加されました',
|
||||
'goal_not_added' : '目標が追加されていません',
|
||||
'please_try_again' : 'もう一度お試しください',
|
||||
"Click start to start rogaining":"開始をクリックして、ロゲイニングを開始します",
|
||||
"you are at roganing point, start rogaining":"あなたはロガニングポイントにいます、ロガニングを始めてください",
|
||||
"you are at roganing point, start rogaining":"あなたはロゲイニングポイントにいます、ロゲイニングを始めてください",
|
||||
"Start":"始める",
|
||||
"Rogaining Started":"ロゲイニング開始",
|
||||
"Rogaining session started":"ロゲイニングセッション開始",
|
||||
@ -143,11 +286,153 @@ class StringValues extends Translations{
|
||||
"You have not started rogaining yet.":"あなたはまだロゲイニングを始めていません。",
|
||||
"Not reached the goal yet": "まだ目標に達していない",
|
||||
"You have not reached the goal yet.":"あなたはまだゴールに達していません。",
|
||||
"delete_account": "アカウントを削除する",
|
||||
"delete_account": "アカウントを削除します",
|
||||
"delete_account_title": "アカウントを削除しますがよろしいですか?",
|
||||
"delete_account_middle": "これにより、アカウント情報とすべてのゲーム データが削除され、すべての状態が削除されます",
|
||||
"accounted_deleted": "アカウントが削除されました",
|
||||
"account_deleted_message": "あなたのアカウントは正常に削除されました",
|
||||
"privacy": "プライバシーポリシー",
|
||||
"app_developed_by_gifu_dx": "このアプリは岐阜県DX補助金事業で開発されました。"
|
||||
"app_developed_by_gifu_dx": "※このアプリは令和4、6年度岐阜県DX補助金事業で開発されました。",
|
||||
|
||||
'location_permission_title': 'ロケーション許可',
|
||||
'location_permission_content': 'このアプリでは、位置情報の収集を行います。\n岐阜ナビアプリではチェックポイントの自動チェックインの機能を可能にするために、現在地のデータが収集されます。アプリを閉じている時や、使用していないときにも収集されます。位置情報は、個人を特定できない統計的な情報として、ユーザーの個人情報とは一切結びつかない形で送信されます。お知らせの配信、位置情報の利用を許可しない場合は、この後表示されるダイアログで「許可しない」を選択してください。',
|
||||
'location_disabled_title': '位置情報サービスが無効です',
|
||||
'location_disabled_content': '位置情報が無効になっています\nこのアプリケーションへの位置情報アクセスが無効になっています。続行するには設定>プライバシーとセキュリティ>位置情報サービス>岐阜ナビ で有効にしてください。',
|
||||
'drawer_title': 'ロゲイニング参加者はログイン するとチェックポイントが参照 できます',
|
||||
'app_title': '旅行工程表',
|
||||
'want_to_go': '行きたい',
|
||||
'schedule_point': '予定地点',
|
||||
'rog_web': 'ロゲイニングウェブサイト',
|
||||
'rogaining_user_need_tosign_up': "ロゲイニング参加者はサインアップの必要はありません。",
|
||||
'add_location': '岐阜',
|
||||
'finish': '終了する',
|
||||
'my_route': 'マイルート',
|
||||
'visit_history': '訪問履歴',
|
||||
'search': '検索',
|
||||
'login': 'ログイン',
|
||||
'password': 'パスワード',
|
||||
'already_have_account': 'すでにアカウントをお持ちですか?',
|
||||
'sign_up': 'サインアップ',
|
||||
'create_account': 'アカウントを無料で作成します',
|
||||
'confirm_password': '確認用パスワード',
|
||||
'cancel_checkin': 'チェックイン取消',
|
||||
'go_here': 'ルート表示',
|
||||
'cancel_route':'ルート消去',
|
||||
'start_rogaining': 'ロゲ開始',
|
||||
'in_game': '競技中',
|
||||
'finish_rogaining': 'ロゲゴール',
|
||||
'checkin': 'チェックイン',
|
||||
'rogaining_not_started': 'ロゲは始まっていません',
|
||||
'confirm': '確認',
|
||||
'clear_rog_data_message': 'ロゲを開始すると、今までのロゲデータが全てクリアされます。本当に開始しますか?',
|
||||
'no': 'いいえ',
|
||||
'yes': 'はい',
|
||||
'retake': '再撮影',
|
||||
'take_photo': '撮影',
|
||||
'take_receipt_photo': 'レシートの写真を撮ってください',
|
||||
'buypoint_added': 'お買い物加点を行いました。',
|
||||
'no_purchase': '買い物なし',
|
||||
'complete': '完了',
|
||||
'movement_history': '移動履歴',
|
||||
'pass_history': '通過履歴',
|
||||
'no_checkin_yet': 'チェックインはまだされてません',
|
||||
'game_status': 'ゲームステータス',
|
||||
'take_cp_photo': 'CPです。撮影してください。',
|
||||
'save_goal_success': '目標が保存されました',
|
||||
'save_goal_failed': '目標が追加されていません',
|
||||
'please_try_again': 'もう一度お試しください',
|
||||
'click_start_to_start_rogaining': 'ロゲを開始するには開始をクリックしてください',
|
||||
'at_rogaining_point_start': 'あなたはロガニングポイントにいます、ロガニングを始めてください',
|
||||
'start': '開始',
|
||||
'rogaining_started': 'ロゲイニングを開始しました',
|
||||
'rogaining_session_started': 'ロゲイニングセッションを開始しました',
|
||||
'not_started_yet': 'まだ開始されていません',
|
||||
'not_started_rogaining_yet': 'あなたはまだロゲイニングを始めていません。',
|
||||
'not_reached_goal_yet': 'まだゴールに達していません',
|
||||
'not_reached_goal_yet_message': 'あなたはまだゴールに達していません。',
|
||||
'reload_qr': '再QR読込',
|
||||
'read_qr': 'QR読込',
|
||||
'read_qr_code': 'QRコードを読み取ってください',
|
||||
'canceled': 'キャンセルされました',
|
||||
'checkin_failed_try_again': 'チェックインしていません。必要ならもう一度チェックポイントをタップして下さい。',
|
||||
'rogaining_not_started': 'ロゲが始まっていません',
|
||||
'need_to_start_rogaining': 'ロゲ開始ボタンをタップして、ロゲイニングを始める必要があります',
|
||||
'no_destination': '目的地がない場合',
|
||||
'near_cp_not_checkin': '検知範囲または距離無視CPで、ゲーム中でまだチェックインしていない。',
|
||||
'auto_checkin_case': '自動チェックインの場合',
|
||||
'non_auto_checkin_case': '自動チェックイン以外の場合',
|
||||
'normal_cp_case': '通常CPの場合',
|
||||
'non_normal_cp_case': '通常CP以外の場合....どんな場合?',
|
||||
'goal_clock_photo_case': 'ゴールで時計撮影の場合',
|
||||
'start_case_24_hours_passed': 'スタートの場合で最後のゴールから24時間経過している場合',
|
||||
'start_cp_24_hours_passed': '開始CPで、最後にゴールしてから24時間経過していれば、',
|
||||
'standard_cp_not_checkin': '標準CP まだチェックインしていない。',
|
||||
'after_checkin_buypoint_case': 'チェックイン後で買い物ポイントの場合。',
|
||||
'goal_case': 'ゴールの場合',
|
||||
'start_case': 'スタートの場合',
|
||||
'no_match_skip_process': 'いずれにも当てはまらないので、処理スキップ',
|
||||
'server_error_occurred': 'サーバーエラーがおきました',
|
||||
'could_not_communicate_with_server': 'サーバーと通信できませんでした',
|
||||
'communication_error_occurred': '通信エラーがおきました',
|
||||
'checked_in': 'チェックインしました。',
|
||||
'cancel_checkin': 'チェックイン取消',
|
||||
'checkin_canceled_for': 'のチェックインは取り消されました',
|
||||
'error': 'エラー',
|
||||
'failed_to_cancel_checkin': 'チェックイン取り消しに失敗しました。',
|
||||
'buypoint_added': 'お買い物加点を行いました',
|
||||
'error_occurred': 'エラーがおきました',
|
||||
'failed_to_process_checkpoint': 'チェックポイントの処理に失敗しました。',
|
||||
'start_rogaining': 'ロゲ開始',
|
||||
'in_competition': '競技中',
|
||||
'map_auto_return_message': 'マップ操作がなければ自動的に現在地に復帰します。そのタイマー秒数を入れて下さい。チェックボックスをチェックすると、自動復帰は行われなくなります。',
|
||||
'no_auto_return': '自動復帰なし',
|
||||
'failed_to_load_markers': 'マーカーの読み込みに失敗しました',
|
||||
'screen_switching_error': '画面切り替えでエラー',
|
||||
'failed_to_switch_screen': '画面の切り替えができませんでした',
|
||||
'timer_duration': 'タイマーの長さ',
|
||||
'user_data_deletion': 'ユーザーデータを削除する',
|
||||
'user_consent_set_for_data_deletion': 'データを削除するためにユーザーの同意が設定されています アプリとサーバーでユーザーデータが削除されました',
|
||||
'go_to_gps_signal_area': 'GPSの届く場所に行って、信号を拾ってください。',
|
||||
'location_service_disabled': '位置情報サービスが無効になっています。設定画面から位置情報サービスを有効にして下さい。不明な場合にはエンジニアスタッフにお問い合わせください。',
|
||||
'location_permission_not_granted': '位置情報サービスが許可されていません。設定画面から岐阜ナビの位置情報サービスを許可して下さい。不明な場合にはエンジニアスタッフにお問い合わせください。',
|
||||
'location_service_issue_occurred': '位置情報サービスに問題が発生しました。位置情報サービスを再起動していますので少しお待ちください。',
|
||||
'login_failed': 'ログイン失敗',
|
||||
'check_login_id_or_password': 'ログインIDかパスワードを確認して下さい。',
|
||||
'communication_error_occurred': '通信エラーがおきました',
|
||||
'could_not_communicate_with_server': 'サーバーと通信できませんでした',
|
||||
'before_game': 'ゲーム前',
|
||||
'location_permission_denied_title': '位置情報の許可が拒否されました',
|
||||
'location_permission_denied_message': 'このアプリを適切に機能させるには、位置情報の許可が必要です。続行するには、位置情報の許可を付与してください。',
|
||||
'location_permission_permanently_denied_title': '位置情報の許可が永久に拒否されました',
|
||||
'location_permission_permanently_denied_message': '位置情報の許可が永久に拒否されました。位置情報の許可を付与するには、アプリ設定を開いてください。',
|
||||
'open_settings': '設定を開く',
|
||||
'storage_permission_needed_title': '写真ライブラリへの許可が必要です',
|
||||
'storage_permission_needed_main': '岐阜ロゲでは、写真ライブラリを使用してスタート・チェックイン・ゴール等の通過照明写真の記録のために、写真ライブラリへの書き込みを行なっています。このためチェックイン時に写真をライブラリに保存する権限が必要です。設定画面で、「岐阜ナビ」に対して、ライブラリに写真の保存を許可するように設定してください。',
|
||||
'location_permission_needed_title': '位置情報への許可が必要です',
|
||||
'location_permission_needed_main': '岐阜ロゲでは、位置情報を使用してスタート・チェックイン・ゴール等の通過照明及び移動手段の記録のために、位置情報のトラッキングを行なっています。このためバックグラウンドでもトラッキングができるように位置情報の権限が必要です。設定画面で、「岐阜ナビ」に対して、常に位置情報を許可するように設定してください。',
|
||||
'open_settings': '設定を開く',
|
||||
'location_permission_denied_title': '位置情報へのアクセスが拒否されています。',
|
||||
'location_permission_denied_main': 'この岐阜ナビアプリは正常に動かすには位置情報への許可が必要です。「設定」画面で位置情報の許可を指定してください。',
|
||||
'location_services_disabled_title': '位置情報サービスが拒否されています',
|
||||
'location_service_disabled_main': '岐阜ナビアプリを使用するには位置情報サービスを許可してください。',
|
||||
'home': 'ホーム',
|
||||
'welcome': '岐阜ナビへようこそ',
|
||||
'location_disabled_message': '位置情報サービスが無効になっています。一部の機能が正しく動作しない可能性があります。',
|
||||
'enable_location_service': '位置情報サービスを有効にする',
|
||||
'start_app': 'アプリを開始する',
|
||||
'location_permission_required_title': '位置情報の許可が必要です',
|
||||
'location_permission_required_message': 'このアプリを使用するには、位置情報へのアクセスが必要です。続行するには許可を付与してください。',
|
||||
'cancel': 'キャンセル',
|
||||
'checkins': 'チェックイン',
|
||||
'reset_button': 'リセット',
|
||||
'reset_title': 'リセットしますがよろしいですか?',
|
||||
'reset_message': 'これにより、すべてのゲーム データが削除され、すべての状態が削除されます',
|
||||
'reset_done': 'リセット完了',
|
||||
'reset_explain': 'すべてリセットされました。ロゲ開始から再開して下さい。',
|
||||
'no_match': '不一致',
|
||||
'password_does_not_match':'入力したパスワードが一致しません',
|
||||
'forgot_password':'パスワードを忘れた場合',
|
||||
'user_registration_successful':'ユーザー認証のメールをお届けしました。メール上のリンクをクリックして正式登録してください。',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,42 +1,43 @@
|
||||
import 'package:geojson_vi/geojson_vi.dart';
|
||||
import 'package:gifunavi/model/destination.dart';
|
||||
|
||||
import 'package:geojson/geojson.dart';
|
||||
import 'package:rogapp/model/destination.dart';
|
||||
|
||||
class TextUtils{
|
||||
|
||||
static String getDisplayTextFeture(GeoJsonFeature f){
|
||||
RegExp regex = RegExp(r'([.]*0)(?!.*\d)');
|
||||
String txt = "";
|
||||
// if(f.properties!["cp"] > 0){
|
||||
// //print("-- sub-- ${f.properties!["cp"]} ----");
|
||||
// txt = "${f.properties!["cp"].toString().replaceAll(regex, '')}";
|
||||
// }
|
||||
//if(f.properties!["buy_point"] != null && f.properties!["buy_point"] > 0){
|
||||
txt = "$txt${f.properties!["sub_loc_id"]}";
|
||||
//}
|
||||
return txt;
|
||||
class TextUtils {
|
||||
static String getDisplayTextFeture(GeoJSONFeature f) {
|
||||
RegExp regex = RegExp(r'([.]*0)(?!.*\d)');
|
||||
String txt = "";
|
||||
if (f.properties!["sub_loc_id"] != null) {
|
||||
txt = "${f.properties!["sub_loc_id"]}";
|
||||
}
|
||||
|
||||
|
||||
static String getDisplayText(Destination dp){
|
||||
RegExp regex = RegExp(r'([.]*0)(?!.*\d)');
|
||||
String txt = "";
|
||||
if(dp.cp! > 0){
|
||||
txt = dp.cp.toString().replaceAll(regex, '');
|
||||
if(dp.checkin_point != null && dp.checkin_point! > 0){
|
||||
txt = "$txt{${dp.checkin_point.toString().replaceAll(regex, '')}}";
|
||||
}
|
||||
if(dp.buy_point != null && dp.buy_point! > 0){
|
||||
print("^^^^^^^^^ ${dp.sub_loc_id}^^^^^^^^^^");
|
||||
txt = "#${dp.cp.toString().replaceAll(regex, '')}(${dp.checkin_point.toString().replaceAll(regex, '')}+${dp.buy_point.toString().replaceAll(regex, '')})";
|
||||
}
|
||||
}
|
||||
return txt;
|
||||
}
|
||||
|
||||
// static String getDisplayText(String num){
|
||||
// RegExp regex = RegExp(r'([.]*0)(?!.*\d)');
|
||||
// return "${num.replaceAll(regex, '')}";
|
||||
// if(f.properties!["cp"] > 0){
|
||||
// //print("-- sub-- ${f.properties!["cp"]} ----");
|
||||
// txt = "${f.properties!["cp"].toString().replaceAll(regex, '')}";
|
||||
// }
|
||||
//if(f.properties!["buy_point"] != null && f.properties!["buy_point"] > 0){
|
||||
//txt = "$txt${f.properties!["sub_loc_id"]}";
|
||||
//}
|
||||
//print("Text = ${txt}");
|
||||
return txt;
|
||||
}
|
||||
|
||||
}
|
||||
static String getDisplayText(Destination dp) {
|
||||
RegExp regex = RegExp(r'([.]*0)(?!.*\d)');
|
||||
String txt = "";
|
||||
if (dp.cp! > 0) {
|
||||
txt = dp.cp.toString().replaceAll(regex, '');
|
||||
if (dp.checkin_point != null && dp.checkin_point! > 0) {
|
||||
txt = "$txt{${dp.checkin_point.toString().replaceAll(regex, '')}}";
|
||||
}
|
||||
if (dp.buy_point != null && dp.buy_point! > 0) {
|
||||
//print("^^^^^^^^^ ${dp.sub_loc_id}^^^^^^^^^^");
|
||||
txt =
|
||||
"#${dp.cp.toString().replaceAll(regex, '')}(${dp.checkin_point.toString().replaceAll(regex, '')}+${dp.buy_point.toString().replaceAll(regex, '')})";
|
||||
}
|
||||
}
|
||||
return txt;
|
||||
}
|
||||
|
||||
// static String getDisplayText(String num){
|
||||
// RegExp regex = RegExp(r'([.]*0)(?!.*\d)');
|
||||
// return "${num.replaceAll(regex, '')}";
|
||||
// }
|
||||
}
|
||||
|
||||
56
lib/widgets/GameState/CheckinState.dart
Normal file
56
lib/widgets/GameState/CheckinState.dart
Normal file
@ -0,0 +1,56 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gifunavi/widgets/GameState/Colors.dart';
|
||||
|
||||
class LocationVisitedWidget extends StatelessWidget {
|
||||
final int count;
|
||||
final bool minimized;
|
||||
|
||||
const LocationVisitedWidget(
|
||||
{super.key, required this.count, this.minimized = false});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (minimized) {
|
||||
return Container(
|
||||
height: 40,
|
||||
width: 40,
|
||||
decoration: const BoxDecoration(
|
||||
color: JapaneseColors.mizu,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'$count',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: JapaneseColors.matcha,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.check_circle_outline, color: Colors.white, size: 24),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'$count チェックイン', // "X Check-ins" in Japanese
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user