Snackbar の色を変更。ロゲ開始時のエラーを回避。
This commit is contained in:
@ -243,7 +243,10 @@ class CameraPage extends StatelessWidget {
|
|||||||
destinationController.skipGps = false;
|
destinationController.skipGps = false;
|
||||||
destinationController.isPhotoShoot.value = false;
|
destinationController.isPhotoShoot.value = false;
|
||||||
Get.snackbar("チェックインしました。",
|
Get.snackbar("チェックインしました。",
|
||||||
"${destination.sub_loc_id} : ${destination.name}");
|
"${destination.sub_loc_id} : ${destination.name}",
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: const Text("チェックイン"))
|
child: const Text("チェックイン"))
|
||||||
: Container())
|
: Container())
|
||||||
@ -307,12 +310,18 @@ class CameraPage extends StatelessWidget {
|
|||||||
if (value['status'] == 'OK') {
|
if (value['status'] == 'OK') {
|
||||||
Get.back();
|
Get.back();
|
||||||
destinationController.skipGps = false;
|
destinationController.skipGps = false;
|
||||||
Get.snackbar("目標が保存されました", "目標が正常に追加されました");
|
Get.snackbar("目標が保存されました", "目標が正常に追加されました",
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white
|
||||||
|
);
|
||||||
destinationController.resetRogaining(
|
destinationController.resetRogaining(
|
||||||
isgoal: true);
|
isgoal: true);
|
||||||
} else {
|
} else {
|
||||||
//print("---- status ${value['status']} ---- ");
|
//print("---- status ${value['status']} ---- ");
|
||||||
Get.snackbar("目標が追加されていません", "please_try_again");
|
Get.snackbar("目標が追加されていません", "please_try_again",
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} on Exception catch (_) {
|
} on Exception catch (_) {
|
||||||
@ -358,7 +367,10 @@ class CameraPage extends StatelessWidget {
|
|||||||
destinationController.skipGps = false;
|
destinationController.skipGps = false;
|
||||||
destinationController.isPhotoShoot.value = false;
|
destinationController.isPhotoShoot.value = false;
|
||||||
Get.snackbar("お買い物加点を行いました。",
|
Get.snackbar("お買い物加点を行いました。",
|
||||||
"${destination.sub_loc_id} : ${destination.name}");
|
"${destination.sub_loc_id} : ${destination.name}",
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: const Text("レシートの写真を撮ってください"))
|
child: const Text("レシートの写真を撮ってください"))
|
||||||
: Container())
|
: Container())
|
||||||
@ -395,7 +407,10 @@ class CameraPage extends StatelessWidget {
|
|||||||
destinationController.skipGps = false;
|
destinationController.skipGps = false;
|
||||||
destinationController.isPhotoShoot.value = false;
|
destinationController.isPhotoShoot.value = false;
|
||||||
Get.snackbar("お買い物加点を行いました。",
|
Get.snackbar("お買い物加点を行いました。",
|
||||||
"${destination.sub_loc_id} : ${destination.name}");
|
"${destination.sub_loc_id} : ${destination.name}",
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: const Text("QRコードを読み取ってください"))
|
child: const Text("QRコードを読み取ってください"))
|
||||||
: Container())
|
: Container())
|
||||||
@ -430,7 +445,10 @@ class CameraPage extends StatelessWidget {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
"チェックインしました",
|
"チェックインしました",
|
||||||
indexController.currentDestinationFeature[0].name ??
|
indexController.currentDestinationFeature[0].name ??
|
||||||
"");
|
"",
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: const Text("チェックイン"))
|
child: const Text("チェックイン"))
|
||||||
: Container())
|
: Container())
|
||||||
@ -677,7 +695,10 @@ class BuyPointCamera extends StatelessWidget {
|
|||||||
destinationController.skipGps = false;
|
destinationController.skipGps = false;
|
||||||
destinationController.isPhotoShoot.value = false;
|
destinationController.isPhotoShoot.value = false;
|
||||||
Get.snackbar("お買い物加点を行いました",
|
Get.snackbar("お買い物加点を行いました",
|
||||||
"${destination.sub_loc_id} : ${destination.name}");
|
"${destination.sub_loc_id} : ${destination.name}",
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: const Text("完了"))
|
child: const Text("完了"))
|
||||||
],
|
],
|
||||||
|
|||||||
@ -92,6 +92,8 @@ class ChangePasswordPage extends StatelessWidget {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
"no_values".tr,
|
"no_values".tr,
|
||||||
"values_required".tr,
|
"values_required".tr,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.assistant_photo_outlined,
|
Icons.assistant_photo_outlined,
|
||||||
size: 40.0,
|
size: 40.0,
|
||||||
@ -99,7 +101,7 @@ class ChangePasswordPage extends StatelessWidget {
|
|||||||
snackPosition: SnackPosition.TOP,
|
snackPosition: SnackPosition.TOP,
|
||||||
duration: const Duration(
|
duration: const Duration(
|
||||||
milliseconds: 800),
|
milliseconds: 800),
|
||||||
backgroundColor: Colors.yellow,
|
//backgroundColor: Colors.yellow,
|
||||||
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||||||
|
|
||||||
import 'package:image_gallery_saver/image_gallery_saver.dart';
|
import 'package:image_gallery_saver/image_gallery_saver.dart';
|
||||||
import 'package:rogapp/utils/const.dart';
|
import 'package:rogapp/utils/const.dart';
|
||||||
|
import 'package:logger/logger.dart';
|
||||||
|
|
||||||
// 目的地に関連する状態管理とロジックを担当するクラスです。
|
// 目的地に関連する状態管理とロジックを担当するクラスです。
|
||||||
//
|
//
|
||||||
@ -325,6 +326,16 @@ class DestinationController extends GetxController {
|
|||||||
ds.isNotEmpty && ds[0].checkedin == true ? true : false;
|
ds.isNotEmpty && ds[0].checkedin == true ? true : false;
|
||||||
bool isuserLoggedIn = indexController.currentUser.isNotEmpty ? true : false; // ログイン済みか
|
bool isuserLoggedIn = indexController.currentUser.isNotEmpty ? true : false; // ログイン済みか
|
||||||
|
|
||||||
|
/*
|
||||||
|
// スタートとゴールは除外
|
||||||
|
debugPrint("startTimer CP=${d.cp}");
|
||||||
|
if (d.cp == -1 || d.cp == 0 || d.cp == -2) {
|
||||||
|
skipGps = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
// 初期化。GPS信号が強くても弱くても
|
// 初期化。GPS信号が強くても弱くても
|
||||||
if (checkinRadious >= distance || checkinRadious == -1) {
|
if (checkinRadious >= distance || checkinRadious == -1) {
|
||||||
//currentSelectedDestinations.add(d);
|
//currentSelectedDestinations.add(d);
|
||||||
@ -835,11 +846,13 @@ class DestinationController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
"ロゲが始まっていません",
|
"ロゲが始まっていません",
|
||||||
"ロゲ開始ボタンをタップして、ロゲイニングを始める必要があります",
|
"ロゲ開始ボタンをタップして、ロゲイニングを始める必要があります",
|
||||||
|
backgroundColor: Colors.yellow,
|
||||||
|
colorText: Colors.white,
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
|
Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
|
||||||
snackPosition: SnackPosition.TOP,
|
snackPosition: SnackPosition.TOP,
|
||||||
duration: const Duration(seconds: 3),
|
duration: const Duration(seconds: 3)
|
||||||
backgroundColor: Colors.yellow,
|
// backgroundColor: Colors.yellow,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -940,6 +953,8 @@ class DestinationController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ロゲイニングにデータを追加する関数です。
|
// ロゲイニングにデータを追加する関数です。
|
||||||
//
|
//
|
||||||
void addToRogaining(double lat, double lon, int destinationId) async {
|
void addToRogaining(double lat, double lon, int destinationId) async {
|
||||||
@ -966,6 +981,7 @@ class DestinationController extends GetxController {
|
|||||||
// 指定されたパスの画像をギャラリーに保存する関数です。
|
// 指定されたパスの画像をギャラリーに保存する関数です。
|
||||||
//
|
//
|
||||||
_saveImageFromPath(String imagePath) async {
|
_saveImageFromPath(String imagePath) async {
|
||||||
|
try {
|
||||||
// Read the image file from the given path
|
// Read the image file from the given path
|
||||||
File imageFile = File(imagePath);
|
File imageFile = File(imagePath);
|
||||||
Uint8List imageBytes = await imageFile.readAsBytes();
|
Uint8List imageBytes = await imageFile.readAsBytes();
|
||||||
@ -973,6 +989,10 @@ class DestinationController extends GetxController {
|
|||||||
// Save the image to the gallery
|
// Save the image to the gallery
|
||||||
final result = await ImageGallerySaver.saveImage(imageBytes);
|
final result = await ImageGallerySaver.saveImage(imageBytes);
|
||||||
//print("--- save result --- ${result}");
|
//print("--- save result --- ${result}");
|
||||||
|
} catch(e, stackTrace){
|
||||||
|
print('エラーが発生しました: $e');
|
||||||
|
print('スタックトレース: $stackTrace');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 買い物ポイントを作成する関数です。 指定された目的地に対して買い物ポイントの処理を行います。
|
// 買い物ポイントを作成する関数です。 指定された目的地に対して買い物ポイントの処理を行います。
|
||||||
@ -1009,12 +1029,15 @@ class DestinationController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// チェックインを行う関数です。 指定された目的地に対してチェックインの処理を行います。
|
// チェックインを行う関数です。 指定された目的地に対してチェックインの処理を行います。
|
||||||
//
|
//
|
||||||
// 要検討:チェックインのリクエストが失敗した場合のエラーハンドリングを追加することをお勧めします。
|
// 要検討:チェックインのリクエストが失敗した場合のエラーハンドリングを追加することをお勧めします。
|
||||||
//
|
//
|
||||||
Future<void> makeCheckin(
|
Future<void> makeCheckin(
|
||||||
Destination destination, bool action, String imageurl) async {
|
Destination destination, bool action, String imageurl) async {
|
||||||
|
|
||||||
|
try {
|
||||||
// print("~~~~ calling checkin function ~~~~");
|
// print("~~~~ calling checkin function ~~~~");
|
||||||
// print(
|
// print(
|
||||||
// "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ressssss ${destination.sub_loc_id}@@@@@@@@@@@");
|
// "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ressssss ${destination.sub_loc_id}@@@@@@@@@@@");
|
||||||
@ -1060,15 +1083,30 @@ class DestinationController extends GetxController {
|
|||||||
|
|
||||||
// print("------ checkin event $eventCode ------");
|
// print("------ checkin event $eventCode ------");
|
||||||
ExternalService()
|
ExternalService()
|
||||||
.makeCheckpoint(userId, token, formattedDate, team, cpNum.round(),
|
.makeCheckpoint(
|
||||||
eventCode, imageurl)
|
userId,
|
||||||
|
token,
|
||||||
|
formattedDate,
|
||||||
|
team,
|
||||||
|
cpNum.round(),
|
||||||
|
eventCode,
|
||||||
|
imageurl)
|
||||||
.then((value) {
|
.then((value) {
|
||||||
// print("------Ext service check point $value ------");
|
// print("------Ext service check point $value ------");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// dbService.updateDatabase();
|
||||||
|
|
||||||
|
}catch(e, stacktrace){
|
||||||
|
print("エラー:${e}");
|
||||||
|
//print("stack : ${stacktrace}");
|
||||||
|
}finally{
|
||||||
dbService.updateDatabase();
|
dbService.updateDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// チェックインを削除する関数です。
|
// チェックインを削除する関数です。
|
||||||
//
|
//
|
||||||
Future<void> removeCheckin(int cp) {
|
Future<void> removeCheckin(int cp) {
|
||||||
@ -1580,11 +1618,13 @@ class DestinationController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
"画面切り替えでエラー",
|
"画面切り替えでエラー",
|
||||||
"画面の切り替えができませんでした",
|
"画面の切り替えができませんでした",
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
|
Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
|
||||||
snackPosition: SnackPosition.TOP,
|
snackPosition: SnackPosition.TOP,
|
||||||
duration: const Duration(seconds: 3),
|
duration: const Duration(seconds: 3),
|
||||||
backgroundColor: Colors.yellow,
|
//backgroundColor: Colors.yellow,
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import 'package:rogapp/pages/destination/destination_controller.dart';
|
|||||||
import 'package:rogapp/pages/index/index_controller.dart';
|
import 'package:rogapp/pages/index/index_controller.dart';
|
||||||
import 'package:rogapp/routes/app_pages.dart';
|
import 'package:rogapp/routes/app_pages.dart';
|
||||||
import 'package:rogapp/services/auth_service.dart';
|
import 'package:rogapp/services/auth_service.dart';
|
||||||
|
import 'package:rogapp/utils/database_helper.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
// SafeAreaウィジェットを使用して、画面の安全領域内にメニューを表示しています。
|
// SafeAreaウィジェットを使用して、画面の安全領域内にメニューを表示しています。
|
||||||
@ -127,12 +128,26 @@ class DrawerPage extends StatelessWidget {
|
|||||||
textConfirm: "確認する",
|
textConfirm: "確認する",
|
||||||
textCancel: "キャンセルする",
|
textCancel: "キャンセルする",
|
||||||
onCancel: () => Get.back(),
|
onCancel: () => Get.back(),
|
||||||
onConfirm: () {
|
onConfirm: () async {
|
||||||
DestinationController destinationController =
|
DestinationController destinationController =
|
||||||
Get.find<DestinationController>();
|
Get.find<DestinationController>();
|
||||||
|
DatabaseHelper databaseHelper = DatabaseHelper.instance;
|
||||||
|
|
||||||
|
// ゲーム中のデータを削除
|
||||||
|
await databaseHelper.deleteAllRogaining();
|
||||||
|
await databaseHelper.deleteAllDestinations();
|
||||||
destinationController.resetRogaining();
|
destinationController.resetRogaining();
|
||||||
destinationController.deleteDBDestinations();
|
|
||||||
|
//destinationController.resetRogaining();
|
||||||
|
//destinationController.deleteDBDestinations();
|
||||||
Get.back();
|
Get.back();
|
||||||
|
Get.snackbar(
|
||||||
|
"リセット完了",
|
||||||
|
"すべてリセットされました。ロゲ開始から再開して下さい。",
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
duration: const Duration(seconds: 3),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -161,7 +176,10 @@ class DrawerPage extends StatelessWidget {
|
|||||||
indexController.logout();
|
indexController.logout();
|
||||||
Get.toNamed(AppPages.TRAVEL);
|
Get.toNamed(AppPages.TRAVEL);
|
||||||
Get.snackbar("accounted_deleted".tr,
|
Get.snackbar("accounted_deleted".tr,
|
||||||
"account_deleted_message".tr);
|
"account_deleted_message".tr,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -172,6 +190,7 @@ class DrawerPage extends StatelessWidget {
|
|||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
),
|
),
|
||||||
|
/*
|
||||||
// ユーザーデータ削除のアイテムは、ユーザーがログインしている場合にのみ表示されます。
|
// ユーザーデータ削除のアイテムは、ユーザーがログインしている場合にのみ表示されます。
|
||||||
// タップすると、AuthService.deleteUser()を呼び出してユーザーデータを削除します。
|
// タップすると、AuthService.deleteUser()を呼び出してユーザーデータを削除します。
|
||||||
indexController.currentUser.isNotEmpty
|
indexController.currentUser.isNotEmpty
|
||||||
@ -215,7 +234,7 @@ class DrawerPage extends StatelessWidget {
|
|||||||
// title: Text("point_rank".tr),
|
// title: Text("point_rank".tr),
|
||||||
// onTap: (){},
|
// onTap: (){},
|
||||||
// ),
|
// ),
|
||||||
|
*/
|
||||||
// "rog_web".trというテキストのアイテムは、ユーザーがログインしている場合にのみ表示されます。
|
// "rog_web".trというテキストのアイテムは、ユーザーがログインしている場合にのみ表示されます。
|
||||||
// タップすると、_launchURL()メソッドを呼び出して外部のウェブサイトを開きます。
|
// タップすると、_launchURL()メソッドを呼び出して外部のウェブサイトを開きます。
|
||||||
indexController.currentUser.isNotEmpty
|
indexController.currentUser.isNotEmpty
|
||||||
|
|||||||
@ -101,7 +101,7 @@ class _GpsPageState extends State<GpsPage> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text("GPS way points"),
|
title: Text("移動履歴"),
|
||||||
),
|
),
|
||||||
body: Container(
|
body: Container(
|
||||||
child: Obx(
|
child: Obx(
|
||||||
|
|||||||
@ -205,10 +205,12 @@ class IndexController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
"ログイン失敗",
|
"ログイン失敗",
|
||||||
"ログインIDかパスワードを確認して下さい。",
|
"ログインIDかパスワードを確認して下さい。",
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
icon: const Icon(Icons.error, size: 40.0, color: Colors.blue),
|
icon: const Icon(Icons.error, size: 40.0, color: Colors.blue),
|
||||||
snackPosition: SnackPosition.TOP,
|
snackPosition: SnackPosition.TOP,
|
||||||
duration: const Duration(seconds: 3),
|
duration: const Duration(seconds: 3),
|
||||||
backgroundColor: Colors.yellow,
|
//backgroundColor: Colors.yellow,
|
||||||
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -235,10 +237,12 @@ class IndexController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'failed'.tr,
|
'failed'.tr,
|
||||||
'password_change_failed_please_try_again'.tr,
|
'password_change_failed_please_try_again'.tr,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
icon: const Icon(Icons.error, size: 40.0, color: Colors.blue),
|
icon: const Icon(Icons.error, size: 40.0, color: Colors.blue),
|
||||||
snackPosition: SnackPosition.TOP,
|
snackPosition: SnackPosition.TOP,
|
||||||
duration: const Duration(milliseconds: 800),
|
duration: const Duration(milliseconds: 800),
|
||||||
backgroundColor: Colors.yellow,
|
//backgroundColor: Colors.yellow,
|
||||||
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -277,10 +281,12 @@ class IndexController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'failed'.tr,
|
'failed'.tr,
|
||||||
'user_registration_failed_please_try_again'.tr,
|
'user_registration_failed_please_try_again'.tr,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
icon: const Icon(Icons.error, size: 40.0, color: Colors.blue),
|
icon: const Icon(Icons.error, size: 40.0, color: Colors.blue),
|
||||||
snackPosition: SnackPosition.TOP,
|
snackPosition: SnackPosition.TOP,
|
||||||
duration: const Duration(milliseconds: 800),
|
duration: const Duration(milliseconds: 800),
|
||||||
backgroundColor: Colors.yellow,
|
//backgroundColor: Colors.yellow,
|
||||||
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -455,11 +461,13 @@ class IndexController extends GetxController {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
"Too many Points",
|
"Too many Points",
|
||||||
"please zoom in",
|
"please zoom in",
|
||||||
|
backgroundColor: Colors.yellow,
|
||||||
|
colorText: Colors.white,
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
|
Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
|
||||||
snackPosition: SnackPosition.TOP,
|
snackPosition: SnackPosition.TOP,
|
||||||
duration: const Duration(seconds: 3),
|
duration: const Duration(seconds: 3),
|
||||||
backgroundColor: Colors.yellow,
|
//backgroundColor: Colors.yellow,
|
||||||
);
|
);
|
||||||
showPopup = false;
|
showPopup = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -107,6 +107,8 @@ class LoginPage extends StatelessWidget {
|
|||||||
"no_values".tr,
|
"no_values".tr,
|
||||||
"email_and_password_required"
|
"email_and_password_required"
|
||||||
.tr,
|
.tr,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons
|
Icons
|
||||||
.assistant_photo_outlined,
|
.assistant_photo_outlined,
|
||||||
@ -116,7 +118,7 @@ class LoginPage extends StatelessWidget {
|
|||||||
SnackPosition.TOP,
|
SnackPosition.TOP,
|
||||||
duration: const Duration(
|
duration: const Duration(
|
||||||
seconds: 3),
|
seconds: 3),
|
||||||
backgroundColor: Colors.yellow,
|
// backgroundColor: Colors.yellow,
|
||||||
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -262,10 +264,11 @@ class LoginPage extends StatelessWidget {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(8.0),
|
padding: EdgeInsets.all(8.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
"※第8回と第9回は、岐阜県からの「清流の国ぎふ」SDGs推進ネットワーク連携促進補助金を受けています",
|
"※第8回と第9回は、岐阜県の令和5年度「清流の国ぎふ」SDGs推進ネットワーク連携促進補助金を受けています",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
//overflow: TextOverflow.ellipsis,
|
||||||
fontSize:
|
fontSize:
|
||||||
12, // Consider adjusting the font size if the text is too small.
|
10.0, // Consider adjusting the font size if the text is too small.
|
||||||
// Removed overflow: TextOverflow.ellipsis to allow text wrapping.
|
// Removed overflow: TextOverflow.ellipsis to allow text wrapping.
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -100,6 +100,8 @@ class LoginPopupPage extends StatelessWidget {
|
|||||||
"no_values".tr,
|
"no_values".tr,
|
||||||
"email_and_password_required"
|
"email_and_password_required"
|
||||||
.tr,
|
.tr,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons
|
Icons
|
||||||
.assistant_photo_outlined,
|
.assistant_photo_outlined,
|
||||||
@ -109,7 +111,7 @@ class LoginPopupPage extends StatelessWidget {
|
|||||||
SnackPosition.TOP,
|
SnackPosition.TOP,
|
||||||
duration: const Duration(
|
duration: const Duration(
|
||||||
milliseconds: 800),
|
milliseconds: 800),
|
||||||
backgroundColor: Colors.yellow,
|
//backgroundColor: Colors.yellow,
|
||||||
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -101,11 +101,13 @@ class RegisterPage extends StatelessWidget {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
"No match",
|
"No match",
|
||||||
"Passwords does not match",
|
"Passwords does not match",
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
icon: const Icon(Icons.assistant_photo_outlined,
|
icon: const Icon(Icons.assistant_photo_outlined,
|
||||||
size: 40.0, color: Colors.blue),
|
size: 40.0, color: Colors.blue),
|
||||||
snackPosition: SnackPosition.TOP,
|
snackPosition: SnackPosition.TOP,
|
||||||
duration: const Duration(milliseconds: 800),
|
duration: const Duration(milliseconds: 800),
|
||||||
backgroundColor: Colors.yellow,
|
// backgroundColor: Colors.yellow,
|
||||||
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -114,11 +116,13 @@ class RegisterPage extends StatelessWidget {
|
|||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
"no_values".tr,
|
"no_values".tr,
|
||||||
"email_and_password_required".tr,
|
"email_and_password_required".tr,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
icon: const Icon(Icons.assistant_photo_outlined,
|
icon: const Icon(Icons.assistant_photo_outlined,
|
||||||
size: 40.0, color: Colors.blue),
|
size: 40.0, color: Colors.blue),
|
||||||
snackPosition: SnackPosition.TOP,
|
snackPosition: SnackPosition.TOP,
|
||||||
duration: const Duration(milliseconds: 800),
|
duration: const Duration(milliseconds: 800),
|
||||||
backgroundColor: Colors.yellow,
|
//backgroundColor: Colors.yellow,
|
||||||
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -110,15 +110,19 @@ class AuthService {
|
|||||||
}
|
}
|
||||||
} catch( e ){
|
} catch( e ){
|
||||||
print('Error in login: $e');
|
print('Error in login: $e');
|
||||||
Get.snackbar("通信エラーがおきました", "サーバーと通信できませんでした");
|
Get.snackbar("通信エラーがおきました", "サーバーと通信できませんでした",
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white);
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
"通信エラーがおきました",
|
"通信エラーがおきました",
|
||||||
"サーバーと通信できませんでした",
|
"サーバーと通信できませんでした",
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
|
Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
|
||||||
snackPosition: SnackPosition.TOP,
|
snackPosition: SnackPosition.TOP,
|
||||||
duration: const Duration(seconds: 3),
|
duration: const Duration(seconds: 3),
|
||||||
backgroundColor: Colors.yellow,
|
//backgroundColor: Colors.yellow,
|
||||||
);
|
);
|
||||||
cats = {};
|
cats = {};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
@ -32,6 +33,8 @@ class ExternalService {
|
|||||||
Future<Map<String, dynamic>> startRogaining() async {
|
Future<Map<String, dynamic>> startRogaining() async {
|
||||||
final IndexController indexController = Get.find<IndexController>();
|
final IndexController indexController = Get.find<IndexController>();
|
||||||
|
|
||||||
|
debugPrint("== startRogaining ==");
|
||||||
|
|
||||||
Map<String, dynamic> res = {};
|
Map<String, dynamic> res = {};
|
||||||
|
|
||||||
int userId = indexController.currentUser[0]["user"]["id"];
|
int userId = indexController.currentUser[0]["user"]["id"];
|
||||||
@ -42,6 +45,7 @@ class ExternalService {
|
|||||||
|
|
||||||
if (indexController.connectionStatusName.value != "wifi" &&
|
if (indexController.connectionStatusName.value != "wifi" &&
|
||||||
indexController.connectionStatusName.value != "mobile") {
|
indexController.connectionStatusName.value != "mobile") {
|
||||||
|
debugPrint("== No network ==");
|
||||||
DatabaseHelper db = DatabaseHelper.instance;
|
DatabaseHelper db = DatabaseHelper.instance;
|
||||||
Rog rog = Rog(
|
Rog rog = Rog(
|
||||||
id: 1,
|
id: 1,
|
||||||
@ -54,6 +58,8 @@ class ExternalService {
|
|||||||
rog_action_type: 0);
|
rog_action_type: 0);
|
||||||
db.insertRogaining(rog);
|
db.insertRogaining(rog);
|
||||||
} else {
|
} else {
|
||||||
|
debugPrint("== startRogaining processing==");
|
||||||
|
|
||||||
String url = 'https://rogaining.sumasen.net/gifuroge/start_from_rogapp';
|
String url = 'https://rogaining.sumasen.net/gifuroge/start_from_rogapp';
|
||||||
//print('++++++++$url');
|
//print('++++++++$url');
|
||||||
final http.Response response = await http.post(
|
final http.Response response = await http.post(
|
||||||
@ -94,6 +100,8 @@ class ExternalService {
|
|||||||
if (imageurl != null) {
|
if (imageurl != null) {
|
||||||
if (indexController.connectionStatusName.value != "wifi" &&
|
if (indexController.connectionStatusName.value != "wifi" &&
|
||||||
indexController.connectionStatusName.value != "mobile") {
|
indexController.connectionStatusName.value != "mobile") {
|
||||||
|
debugPrint("== checkin without network ==");
|
||||||
|
|
||||||
DatabaseHelper db = DatabaseHelper.instance;
|
DatabaseHelper db = DatabaseHelper.instance;
|
||||||
Rog rog = Rog(
|
Rog rog = Rog(
|
||||||
id: 1,
|
id: 1,
|
||||||
@ -107,6 +115,7 @@ class ExternalService {
|
|||||||
);
|
);
|
||||||
db.insertRogaining(rog);
|
db.insertRogaining(rog);
|
||||||
} else {
|
} else {
|
||||||
|
debugPrint("== Normal Check in ===");
|
||||||
String serverUrl = ConstValues.currentServer();
|
String serverUrl = ConstValues.currentServer();
|
||||||
String url1 = "$serverUrl/api/checkinimage/";
|
String url1 = "$serverUrl/api/checkinimage/";
|
||||||
final im1Bytes = File(imageurl).readAsBytesSync();
|
final im1Bytes = File(imageurl).readAsBytesSync();
|
||||||
@ -167,16 +176,26 @@ class ExternalService {
|
|||||||
if (response2.statusCode == 200) {
|
if (response2.statusCode == 200) {
|
||||||
res = json.decode(utf8.decode(response2.bodyBytes));
|
res = json.decode(utf8.decode(response2.bodyBytes));
|
||||||
//print('----checkin res _res : $res ----');
|
//print('----checkin res _res : $res ----');
|
||||||
if (res["status"] == "ERROR") {
|
if (res["status"] == "ERROR" && cp>0 ) {
|
||||||
Get.snackbar("エラーがおきました", res["detail"]);
|
// スタート・ゴールはエラー除外。
|
||||||
|
Get.snackbar("エラーがおきました", res["detail"],
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Get.snackbar("サーバーエラーがおきました", "サーバーと通信できませんでした");
|
Get.snackbar("サーバーエラーがおきました", "サーバーと通信できませんでした",
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch( e ) {
|
} catch( e ) {
|
||||||
print('Error in makeCheckpoint: $e');
|
print('Error in makeCheckpoint: $e');
|
||||||
Get.snackbar("通信エラーがおきました", "サーバーと通信できませんでした");
|
Get.snackbar("通信エラーがおきました", "サーバーと通信できませんでした",
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -234,6 +253,8 @@ class ExternalService {
|
|||||||
final DestinationController destinationController =
|
final DestinationController destinationController =
|
||||||
Get.find<DestinationController>();
|
Get.find<DestinationController>();
|
||||||
|
|
||||||
|
debugPrint("== goal Rogaining ==");
|
||||||
|
|
||||||
//if(indexController.connectionStatusName != "wifi" && indexController.connectionStatusName != "mobile"){
|
//if(indexController.connectionStatusName != "wifi" && indexController.connectionStatusName != "mobile"){
|
||||||
DatabaseHelper db = DatabaseHelper.instance;
|
DatabaseHelper db = DatabaseHelper.instance;
|
||||||
Rog rog = Rog(
|
Rog rog = Rog(
|
||||||
|
|||||||
@ -79,7 +79,7 @@ class StringValues extends Translations{
|
|||||||
'drawer_title':'ロゲイニング参加者はログイン するとチェックポイントが参照 できます',
|
'drawer_title':'ロゲイニング参加者はログイン するとチェックポイントが参照 できます',
|
||||||
'app_title': '旅行工程表',
|
'app_title': '旅行工程表',
|
||||||
'address':'住所',
|
'address':'住所',
|
||||||
'email':'Eメール',
|
'email':'ゼッケン番号',
|
||||||
'password':'パスワード',
|
'password':'パスワード',
|
||||||
'web':'ウェブ',
|
'web':'ウェブ',
|
||||||
'wikipedia':'ウィキペディア',
|
'wikipedia':'ウィキペディア',
|
||||||
@ -147,7 +147,7 @@ class StringValues extends Translations{
|
|||||||
"accounted_deleted": "アカウントが削除されました",
|
"accounted_deleted": "アカウントが削除されました",
|
||||||
"account_deleted_message": "あなたのアカウントは正常に削除されました",
|
"account_deleted_message": "あなたのアカウントは正常に削除されました",
|
||||||
"privacy": "プライバシーポリシー",
|
"privacy": "プライバシーポリシー",
|
||||||
"app_developed_by_gifu_dx": "このアプリは岐阜県DX補助金事業で開発されました。"
|
"app_developed_by_gifu_dx": "※このアプリは令和4年度岐阜県DX補助金事業で開発されました。"
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -15,7 +15,7 @@ class GameStatusIndicator extends StatelessWidget {
|
|||||||
IconData iconData =
|
IconData iconData =
|
||||||
gameStarted ? Icons.stop_circle : Icons.play_circle_filled;
|
gameStarted ? Icons.stop_circle : Icons.play_circle_filled;
|
||||||
// Text to show based on the game status
|
// Text to show based on the game status
|
||||||
String text = gameStarted ? 'ゲーム開始' : 'ゲーム未開始';
|
String text = gameStarted ? 'ゲーム中' : 'ゲーム前';
|
||||||
|
|
||||||
// Layout for minimized view
|
// Layout for minimized view
|
||||||
if (minimized) {
|
if (minimized) {
|
||||||
|
|||||||
@ -198,6 +198,7 @@ class BottomSheetNew extends GetView<BottomSheetController> {
|
|||||||
// Clear data and start game logic here
|
// Clear data and start game logic here
|
||||||
destinationController.isInRog.value = true;
|
destinationController.isInRog.value = true;
|
||||||
destinationController.resetRogaining();
|
destinationController.resetRogaining();
|
||||||
|
|
||||||
destinationController.addToRogaining(
|
destinationController.addToRogaining(
|
||||||
destinationController.currentLat,
|
destinationController.currentLat,
|
||||||
destinationController.currentLon,
|
destinationController.currentLon,
|
||||||
@ -480,16 +481,37 @@ class BottomSheetNew extends GetView<BottomSheetController> {
|
|||||||
// Finish or Goal
|
// Finish or Goal
|
||||||
getActionButton(context, destination),
|
getActionButton(context, destination),
|
||||||
//remove checkin
|
//remove checkin
|
||||||
isAlreadyCheckedIn == true && destination.cp != -1
|
isAlreadyCheckedIn == true && destination.cp != 0 && destination.cp != -1 && destination.cp != -2
|
||||||
? ElevatedButton(
|
? ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.blueAccent),
|
backgroundColor: Colors.blueAccent),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
try {
|
||||||
await destinationController
|
await destinationController
|
||||||
.removeCheckin(destination.cp!.toInt());
|
.removeCheckin(destination.cp!.toInt());
|
||||||
destinationController
|
destinationController
|
||||||
.deleteDestination(destination);
|
.deleteDestination(destination);
|
||||||
Get.back();
|
Get.back();
|
||||||
|
Get.snackbar(
|
||||||
|
'チェックイン取り消し',
|
||||||
|
'${destination
|
||||||
|
.name}のチェックインは取り消されました',
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
duration: Duration(seconds: 3),
|
||||||
|
);
|
||||||
|
} catch (e ) {
|
||||||
|
// エラーハンドリング
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'An error occurred while canceling check-in.',
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
duration: Duration(seconds: 3),
|
||||||
|
);
|
||||||
|
// 必要に応じてエラーログを記録
|
||||||
|
print('Error canceling check-in: $e');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
child: const Text(
|
child: const Text(
|
||||||
"チェックイン取消",
|
"チェックイン取消",
|
||||||
|
|||||||
@ -1,325 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:geolocator/geolocator.dart';
|
|
||||||
import 'package:rogapp/model/destination.dart';
|
|
||||||
import 'package:rogapp/pages/index/index_controller.dart';
|
|
||||||
import 'package:rogapp/utils/const.dart';
|
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
|
||||||
import 'package:rogapp/pages/destination/destination_controller.dart';
|
|
||||||
|
|
||||||
|
|
||||||
class BottomSheetBase extends StatelessWidget {
|
|
||||||
|
|
||||||
BottomSheetBase({super.key, required this.destination,this.isAlreadyCheckedIn = false});
|
|
||||||
|
|
||||||
final IndexController indexController = Get.find<IndexController>();
|
|
||||||
final DestinationController destinationController = Get.find<DestinationController>();
|
|
||||||
|
|
||||||
final bool isAlreadyCheckedIn; // すでにチェックイン済みかどうかのフラグ
|
|
||||||
final Destination destination; // 目的地オブジェクト
|
|
||||||
|
|
||||||
|
|
||||||
// そのポイントの写真を取得するメソッド
|
|
||||||
//
|
|
||||||
Image getImage() {
|
|
||||||
String serverUrl = ConstValues.currentServer();
|
|
||||||
|
|
||||||
// currentDestinationFeature(destinationそのもの)のphotoデータのチェック
|
|
||||||
// Nouffer のコードでは、indexController.rogMode によって、destination(currentDestinationFeature) かGeoJSONFeature(currentFeature)の違いで処理が分かれていた。
|
|
||||||
// しかし、現在のコードでは GeoJSONFeature gf = indexController.currentFeature[0]; は使われていない。
|
|
||||||
//
|
|
||||||
if (indexController.currentDestinationFeature[0].photos! == "") {
|
|
||||||
// 定義されていなければ、empty_image.png を選択
|
|
||||||
return const Image(image: AssetImage('assets/images/empty_image.png'));
|
|
||||||
} else {
|
|
||||||
String photo = indexController.currentDestinationFeature[0].photos!;
|
|
||||||
if (photo.contains('http')) {
|
|
||||||
// photo にhttpの文字が含まれていれば、ネットから拾ってくる。エラーならempty_image.pngを表示。
|
|
||||||
return Image(
|
|
||||||
image: NetworkImage(
|
|
||||||
indexController.currentDestinationFeature[0].photos!,
|
|
||||||
),
|
|
||||||
errorBuilder:
|
|
||||||
(BuildContext context, Object exception, StackTrace? stackTrace) {
|
|
||||||
return Image.asset("assets/images/empty_image.png");
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// photo にhttpの文字が含まれていなければ、指定サーバー($serverUrl/media/compressed/*)から拾ってくる。エラーならempty_image.pngを表示。
|
|
||||||
return Image(
|
|
||||||
image: NetworkImage(
|
|
||||||
'$serverUrl/media/compressed/${indexController.currentDestinationFeature[0].photos!}',
|
|
||||||
),
|
|
||||||
errorBuilder:
|
|
||||||
(BuildContext context, Object exception, StackTrace? stackTrace) {
|
|
||||||
return Image.asset("assets/images/empty_image.png");
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// URLを開くためのメソッドです。
|
|
||||||
// url_launcherパッケージを使用して、指定されたURLを開きます。
|
|
||||||
//
|
|
||||||
void _launchURL(url) async {
|
|
||||||
if (!await launchUrl(url)) throw 'Could not launch $url';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 指定されたlocationidが目的地リストに含まれているかどうかを確認するメソッドです。
|
|
||||||
// destinationController.destinationsリストを走査し、locationidが一致する目的地があるかどうかを返します。
|
|
||||||
//
|
|
||||||
bool isInDestination(String locationid) {
|
|
||||||
int lid = int.parse(locationid);
|
|
||||||
if (destinationController.destinations
|
|
||||||
.where((element) => element.location_id == lid)
|
|
||||||
.isNotEmpty) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
children: buildWidgets(context),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// このメソッドはList<Widget>を返します。
|
|
||||||
List<Widget> buildWidgets(BuildContext context) {
|
|
||||||
return [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
MaterialButton(
|
|
||||||
onPressed: () {
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
color: Colors.blue,
|
|
||||||
textColor: Colors.white,
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
shape: const CircleBorder(),
|
|
||||||
child: const Icon(Icons.arrow_back_ios, size: 14),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Container(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Obx(() => Text("${destination.sub_loc_id} : ${destination.name}",
|
|
||||||
style: const TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold))),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [Expanded(child: SizedBox(height: 260.0, child: Obx(() => getImage())))],
|
|
||||||
),
|
|
||||||
Obx(() => Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: getDetails(context),
|
|
||||||
)),
|
|
||||||
const SizedBox(height: 60.0),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget getDetails(BuildContext context) {
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.roundabout_left),
|
|
||||||
const SizedBox(
|
|
||||||
width: 8.0,
|
|
||||||
),
|
|
||||||
destination.address != null && destination.address!.isNotEmpty
|
|
||||||
? getDetailsItem(
|
|
||||||
context,
|
|
||||||
"address".tr,
|
|
||||||
destination.address ?? '',
|
|
||||||
)
|
|
||||||
: const SizedBox(
|
|
||||||
width: 0.0,
|
|
||||||
height: 0,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.phone),
|
|
||||||
const SizedBox(
|
|
||||||
width: 8.0,
|
|
||||||
),
|
|
||||||
destination.phone != null && destination.phone!.isNotEmpty
|
|
||||||
? getDetailsItem(
|
|
||||||
context,
|
|
||||||
"telephone".tr,
|
|
||||||
destination.phone ?? '',
|
|
||||||
)
|
|
||||||
: const SizedBox(
|
|
||||||
width: 0.0,
|
|
||||||
height: 0,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.email),
|
|
||||||
const SizedBox(
|
|
||||||
width: 8.0,
|
|
||||||
),
|
|
||||||
destination.email != null && destination.email!.isNotEmpty
|
|
||||||
? getDetailsItem(
|
|
||||||
context,
|
|
||||||
"email".tr,
|
|
||||||
destination.email ?? '',
|
|
||||||
)
|
|
||||||
: const SizedBox(
|
|
||||||
width: 0.0,
|
|
||||||
height: 0,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.language),
|
|
||||||
const SizedBox(
|
|
||||||
width: 8.0,
|
|
||||||
),
|
|
||||||
destination.webcontents != null &&
|
|
||||||
destination.webcontents!.isNotEmpty
|
|
||||||
? getDetailsItem(
|
|
||||||
context,
|
|
||||||
"web".tr,
|
|
||||||
destination.webcontents ?? '',
|
|
||||||
isUrl: true,
|
|
||||||
)
|
|
||||||
: const SizedBox(
|
|
||||||
width: 0.0,
|
|
||||||
height: 0,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const SizedBox(
|
|
||||||
width: 8.0,
|
|
||||||
),
|
|
||||||
indexController.currentFeature[0]
|
|
||||||
.properties!["remark"] !=
|
|
||||||
null &&
|
|
||||||
(indexController.currentFeature[0]
|
|
||||||
.properties!["remark"] as String)
|
|
||||||
.isNotEmpty
|
|
||||||
? getDetailsItem(
|
|
||||||
context,
|
|
||||||
"remarks".tr,
|
|
||||||
indexController.currentFeature[0]
|
|
||||||
.properties!["remark"] ??
|
|
||||||
'',
|
|
||||||
isUrl: false)
|
|
||||||
: const SizedBox(
|
|
||||||
width: 0.0,
|
|
||||||
height: 0,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
ElevatedButton(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.onPrimaryContainer),
|
|
||||||
onPressed: () async {
|
|
||||||
// print(
|
|
||||||
// "dist to start ${destinationController.distanceToStart()}");
|
|
||||||
Get.back();
|
|
||||||
//print("---- go to ----");
|
|
||||||
// GeoJSONMultiPoint mp = indexController
|
|
||||||
// .currentFeature[0] as GeoJSONMultiPoint;
|
|
||||||
Position position =
|
|
||||||
await Geolocator.getCurrentPosition(
|
|
||||||
desiredAccuracy:
|
|
||||||
LocationAccuracy.bestForNavigation,
|
|
||||||
forceAndroidLocationManager: true);
|
|
||||||
//print("------- position -------- $position");
|
|
||||||
Destination ds = Destination(
|
|
||||||
lat: position.latitude,
|
|
||||||
lon: position.longitude);
|
|
||||||
|
|
||||||
Destination tp = Destination(
|
|
||||||
lat: destination.lat, lon: destination.lon);
|
|
||||||
|
|
||||||
destinationController
|
|
||||||
.destinationMatrixFromCurrentPoint([ds, tp]);
|
|
||||||
|
|
||||||
// TODO: Implement "ここへ行く" functionality
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
"ルート",
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.onPrimary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
width: 10,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Widget getDetailsItem(BuildContext context, String label, String text,
|
|
||||||
{bool isUrl = false}) {
|
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(label),
|
|
||||||
const SizedBox(
|
|
||||||
width: 10.0,
|
|
||||||
),
|
|
||||||
InkWell(
|
|
||||||
onTap: () {
|
|
||||||
if (isUrl) {
|
|
||||||
_launchURL(destination.webcontents);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: SizedBox(
|
|
||||||
width: MediaQuery.of(context).size.width -
|
|
||||||
(MediaQuery.of(context).size.width * 0.35),
|
|
||||||
child: Text(
|
|
||||||
text,
|
|
||||||
textAlign: TextAlign.justify,
|
|
||||||
style: TextStyle(
|
|
||||||
color: isUrl ? Colors.blue : Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:rogapp/pages/destination/destination_controller.dart';
|
|
||||||
import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_normal_point.dart';
|
|
||||||
|
|
||||||
class BottomSheetBuyPoint extends BottomSheetNormalPoint {
|
|
||||||
BottomSheetBuyPoint({super.key, required super.destination});
|
|
||||||
|
|
||||||
final DestinationController destinationController = Get.find<DestinationController>();
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Widget> buildWidgets(BuildContext context) {
|
|
||||||
// super.buildWidgets(context) を継承して、追加のウィジェットをリストに組み込む
|
|
||||||
return [
|
|
||||||
...super.buildWidgets(context),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () async {
|
|
||||||
// 「買い物チェックイン」ボタンがタップされた際の処理
|
|
||||||
// TODO: Implement QR code scanning and buy point processing logic
|
|
||||||
},
|
|
||||||
child: const Text('買い物チェックイン'),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
children: buildWidgets(context),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:rogapp/model/destination.dart';
|
|
||||||
import 'package:rogapp/pages/destination/destination_controller.dart';
|
|
||||||
import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_base.dart';
|
|
||||||
import 'package:latlong2/latlong.dart';
|
|
||||||
import 'package:rogapp/pages/camera/camera_page.dart';
|
|
||||||
import 'dart:ui' as ui;
|
|
||||||
|
|
||||||
class BottomSheetGoal extends BottomSheetBase {
|
|
||||||
BottomSheetGoal({super.key, required super.destination});
|
|
||||||
|
|
||||||
final DestinationController destinationController = Get.find<DestinationController>();
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Widget> buildWidgets(BuildContext context) {
|
|
||||||
// super.buildWidgets(context) を継承して、追加のウィジェットをリストに組み込む
|
|
||||||
return [
|
|
||||||
...super.buildWidgets(context),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: destinationController.distanceToStart() <= 100
|
|
||||||
? () async {
|
|
||||||
// 「ゴール時間撮影」ボタンがタップされた際の処理
|
|
||||||
Destination cdest = destinationController.festuretoDestination(indexController.currentFeature[0]);
|
|
||||||
var distance = const Distance();
|
|
||||||
double distanceToDest = distance.as(
|
|
||||||
LengthUnit.Meter,
|
|
||||||
LatLng(
|
|
||||||
destinationController.currentLat, destinationController.currentLon),
|
|
||||||
LatLng(cdest.lat!, cdest.lon!));
|
|
||||||
|
|
||||||
if (destinationController.rogainingCounted.value == true &&
|
|
||||||
// destinationController.distanceToStart() <= 500 && ... GPS信号が弱い時でもOKとする。
|
|
||||||
(destinationController.distanceToStart() <= 500 || destinationController.isGpsSignalWeak() ) &&
|
|
||||||
destination.cp == -1 &&
|
|
||||||
DestinationController.ready_for_goal == true) {
|
|
||||||
|
|
||||||
destinationController.isAtGoal.value = true;
|
|
||||||
destinationController.photos.clear();
|
|
||||||
await showModalBottomSheet(
|
|
||||||
constraints: BoxConstraints.loose(
|
|
||||||
ui.Size(Get.width, Get.height * 0.75)),
|
|
||||||
context: Get.context!,
|
|
||||||
isScrollControlled: true,
|
|
||||||
builder: ((context) => CameraPage(
|
|
||||||
destination: destination,
|
|
||||||
))).whenComplete(() {
|
|
||||||
destinationController.skipGps = false;
|
|
||||||
destinationController.chekcs = 0;
|
|
||||||
destinationController.isAtGoal.value = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
child: const Text('ゴール時間撮影'),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
children: buildWidgets(context),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:rogapp/pages/destination/destination_controller.dart';
|
|
||||||
import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_base.dart';
|
|
||||||
|
|
||||||
class BottomSheetNormalPoint extends BottomSheetBase {
|
|
||||||
BottomSheetNormalPoint({super.key, required super.destination});
|
|
||||||
|
|
||||||
final DestinationController destinationController = Get.find<DestinationController>();
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Widget> buildWidgets(BuildContext context) {
|
|
||||||
// super.buildWidgets(context) を継承して、追加のウィジェットをリストに組み込む
|
|
||||||
return [
|
|
||||||
...super.buildWidgets(context),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: destinationController.distanceToStart() <= 100
|
|
||||||
? () async {
|
|
||||||
if (destinationController.rogainingCounted.value == true &&
|
|
||||||
(destinationController.distanceToStart() <= 500 ||
|
|
||||||
destinationController.isGpsSignalWeak()) &&
|
|
||||||
destination.cp! > 0) {
|
|
||||||
// isAlreadyCheckedIn == true
|
|
||||||
// 「チェックイン撮影」ボタンがタップされた際の処理
|
|
||||||
try {
|
|
||||||
Get.back();
|
|
||||||
await destinationController.callforCheckin(destination);
|
|
||||||
// ここで、CameraPageへの遷移が行われる。
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
// エラーハンドリング
|
|
||||||
Get.snackbar(
|
|
||||||
'Error',
|
|
||||||
'An error occurred while processing check-in.',
|
|
||||||
backgroundColor: Colors.red,
|
|
||||||
colorText: Colors.white,
|
|
||||||
duration: Duration(seconds: 3),
|
|
||||||
);
|
|
||||||
// 必要に応じてエラーログを記録
|
|
||||||
print('Error processing check-in: $e');
|
|
||||||
}
|
|
||||||
} else if (destination.checkedin == true) { // 取り消しの場合?
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
|
|
||||||
child: Obx( () => Text(
|
|
||||||
destinationController.isInRog.value ? 'チェックイン取消' : 'チェックイン',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
children: buildWidgets(context),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,102 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:rogapp/pages/destination/destination_controller.dart';
|
|
||||||
import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_base.dart';
|
|
||||||
import 'package:rogapp/main.dart';
|
|
||||||
import 'package:rogapp/services/external_service.dart';
|
|
||||||
|
|
||||||
class BottomSheetStart extends BottomSheetBase {
|
|
||||||
|
|
||||||
BottomSheetStart({super.key, required super.destination});
|
|
||||||
|
|
||||||
final DestinationController destinationController = Get.find<DestinationController>();
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Widget> buildWidgets(BuildContext context) {
|
|
||||||
// super.buildWidgets(context) を継承して、追加のウィジェットをリストに組み込む
|
|
||||||
return [
|
|
||||||
...super.buildWidgets(context),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: destinationController.distanceToStart() <= 100
|
|
||||||
? () async {
|
|
||||||
// ボタンがタップされた際の処理
|
|
||||||
// Check conditions to show confirmation dialog
|
|
||||||
if (destinationController.isInRog.value == false &&
|
|
||||||
(destinationController.distanceToStart() <= 500 || destinationController.isGpsSignalWeak() ) && //追加 Akira 2024-4-5
|
|
||||||
(destination.cp == -1 || destination.cp == 0 ) &&
|
|
||||||
destinationController.rogainingCounted.value == false) {
|
|
||||||
// ロゲがまだ開始されていない: destinationController.isInRog.value == false
|
|
||||||
// かつ (開始ポイントまでの距離が 500m 以内 destinationController.distanceToStart() <= 500
|
|
||||||
// または GPS信号強度が弱い場合: destinationController.isGpsSignalWeak()
|
|
||||||
// ) かつ
|
|
||||||
// そのポイントのCP番号が -1 または 0 の場合:
|
|
||||||
// かつ ポイントがカウントされていない場合: destinationController.rogainingCounted.value=false
|
|
||||||
// にロゲイニング開始ができる。
|
|
||||||
//
|
|
||||||
print("counted ${destinationController.rogainingCounted.value}");
|
|
||||||
|
|
||||||
// Show confirmation dialog
|
|
||||||
Get.dialog(
|
|
||||||
AlertDialog(
|
|
||||||
title: const Text("確認"), //confirm
|
|
||||||
content: const Text(
|
|
||||||
"ロゲを開始すると、今までのロゲデータが全てクリアされます。本当に開始しますか?"), //are you sure
|
|
||||||
actions: <Widget>[
|
|
||||||
TextButton(
|
|
||||||
child: const Text("いいえ"), //no
|
|
||||||
onPressed: () {
|
|
||||||
Get.back(); // Close the dialog
|
|
||||||
},
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
child: const Text("はい"), //yes
|
|
||||||
onPressed: () async {
|
|
||||||
// Clear data and start game logic here
|
|
||||||
destinationController.isInRog.value = true;
|
|
||||||
destinationController.resetRogaining();
|
|
||||||
destinationController.addToRogaining(
|
|
||||||
destinationController.currentLat,
|
|
||||||
destinationController.currentLon,
|
|
||||||
destination.location_id!,
|
|
||||||
);
|
|
||||||
saveGameState(); // main.dart 参照 // ゲームの状態を保存する。
|
|
||||||
await ExternalService().startRogaining();
|
|
||||||
Get.back(); // Close the dialog and potentially navigate away
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
barrierDismissible:
|
|
||||||
false, // User must tap a button to close the dialog
|
|
||||||
);
|
|
||||||
} else if (destinationController.isInRog.value == true &&
|
|
||||||
destination.cp != -1 && destination.cp != 0 ) {
|
|
||||||
//print("counted ${destinationController.rogainingCounted.value}");
|
|
||||||
// Existing logic for other conditions
|
|
||||||
|
|
||||||
Get.back();
|
|
||||||
await destinationController.callforCheckin(destination);
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
child: Obx(
|
|
||||||
() => Text(
|
|
||||||
destinationController.isInRog.value ? '競技中' : 'ロゲ開始',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
children: buildWidgets(context),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:rogapp/model/destination.dart';
|
|
||||||
import 'package:rogapp/pages/destination/destination_controller.dart';
|
|
||||||
import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_base.dart';
|
|
||||||
|
|
||||||
class BottomSheetStartGoal extends BottomSheetBase {
|
|
||||||
BottomSheetStartGoal({Key? key, required Destination destination})
|
|
||||||
: super(key: key, destination: destination);
|
|
||||||
|
|
||||||
final DestinationController destinationController =
|
|
||||||
Get.find<DestinationController>();
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Widget> buildWidgets(BuildContext context) {
|
|
||||||
// super.buildWidgets(context) を継承して、追加のウィジェットをリストに組み込む
|
|
||||||
return [
|
|
||||||
...super.buildWidgets(context),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: destinationController.distanceToStart() <= 100
|
|
||||||
? () async {
|
|
||||||
// 「ロゲ開始」または「ゴール時間撮影」ボタンがタップされた際の処理
|
|
||||||
// TODO: Implement start game or goal time capture logic based on game state
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
child: Obx(
|
|
||||||
() => Text(
|
|
||||||
destinationController.isInRog.value ? 'ゴール時間撮影' : 'ロゲ開始',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
children: buildWidgets(context),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -127,7 +127,9 @@ class DestinationWidget extends StatelessWidget {
|
|||||||
destinationController.deleteAllDestinations();
|
destinationController.deleteAllDestinations();
|
||||||
Get.back();
|
Get.back();
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
"deleted".tr, "all_destinations_are_deleted_successfully".tr);
|
"deleted".tr, "all_destinations_are_deleted_successfully".tr,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user