2024-09-02 ほぼOK
This commit is contained in:
@ -14,8 +14,11 @@ import 'package:gifunavi/services/external_service.dart';
|
||||
import 'package:gifunavi/utils/const.dart';
|
||||
import 'package:qr_code_scanner/qr_code_scanner.dart';
|
||||
|
||||
import 'package:http/http.dart' as http; // この行を追加
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../../routes/app_pages.dart'; // この行を追加
|
||||
import 'package:timezone/data/latest.dart' as tz;
|
||||
import 'package:timezone/timezone.dart' as tz;
|
||||
|
||||
// 関数 getTagText は、特定の条件に基づいて文字列から特定の部分を抽出し、返却するためのものです。
|
||||
// 関数は2つのパラメータを受け取り、条件分岐を通じて結果を返します。
|
||||
@ -220,10 +223,67 @@ class CameraPage extends StatelessWidget {
|
||||
|
||||
Timer? timer;
|
||||
|
||||
bool isValidEventParticipation() {
|
||||
final eventCode = indexController.currentUser[0]["user"]["event_code"];
|
||||
final teamName = indexController.currentUser[0]["user"]["team_name"];
|
||||
final dateString = indexController.currentUser[0]["user"]["event_date"];
|
||||
//final parsedDate = DateTime.parse(dateString);
|
||||
|
||||
//final eventDate = tz.TZDateTime.from(parsedDate, tz.getLocation('Asia/Tokyo'));
|
||||
|
||||
//final today = DateTime.now();
|
||||
|
||||
return eventCode != null &&
|
||||
teamName != null &&
|
||||
dateString != null ;
|
||||
|
||||
// isSameDay(eventDate, today);
|
||||
}
|
||||
|
||||
bool isSameDay(DateTime date1, DateTime date2) {
|
||||
return date1.year == date2.year &&
|
||||
date1.month == date2.month &&
|
||||
date1.day == date2.day;
|
||||
}
|
||||
|
||||
void showEventParticipationWarning(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text("警告"),
|
||||
content: const Text("今日のイベントにまず参加しないと事前チェックインはできません。サブメニューからイベント参加をタップして今日のイベントに参加してください。"),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: const Text("キャンセル"),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: const Text("参加する"),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
Get.toNamed(AppPages.EVENT_ENTRY);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// 現在の状態に基づいて、適切なアクションボタンを返します。
|
||||
// 要修正:エラーハンドリングが不十分です。例外が発生した場合の処理を追加することをお勧めします。
|
||||
//
|
||||
Widget getAction(BuildContext context) {
|
||||
if (!isValidEventParticipation()) {
|
||||
return ElevatedButton(
|
||||
onPressed: () => showEventParticipationWarning(context),
|
||||
child: const Text("チェックイン"),
|
||||
);
|
||||
}
|
||||
|
||||
if (manulaCheckin == true) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
@ -253,7 +313,9 @@ class CameraPage extends StatelessWidget {
|
||||
onPressed: () async {
|
||||
await destinationController.makeCheckin(destination, true,
|
||||
destinationController.photos[0].path);
|
||||
destinationController.rogainingCounted.value = true;
|
||||
if( destinationController.isInRog.value==true ) {
|
||||
destinationController.rogainingCounted.value = true; // ロゲ開始後のみ許可
|
||||
}
|
||||
destinationController.skipGps = false;
|
||||
destinationController.isPhotoShoot.value = false;
|
||||
|
||||
@ -387,7 +449,9 @@ class CameraPage extends StatelessWidget {
|
||||
await destinationController.makeBuyPoint(
|
||||
destination, destinationController.photos[0].path);
|
||||
Get.back();
|
||||
destinationController.rogainingCounted.value = true;
|
||||
if( destinationController.isInRog.value==true ) {
|
||||
destinationController.rogainingCounted.value = true; // ロゲ開始後のみ許可
|
||||
}
|
||||
destinationController.skipGps = false;
|
||||
destinationController.isPhotoShoot.value = false;
|
||||
Get.snackbar("お買い物加点を行いました。",
|
||||
@ -428,7 +492,9 @@ class CameraPage extends StatelessWidget {
|
||||
await destinationController.makeBuyPoint(
|
||||
destination, destinationController.photos[0].path);
|
||||
Get.back();
|
||||
destinationController.rogainingCounted.value = true;
|
||||
if( destinationController.isInRog.value==true ) {
|
||||
destinationController.rogainingCounted.value = true; //ロゲ開始後のみ許可
|
||||
}
|
||||
destinationController.skipGps = false;
|
||||
destinationController.isPhotoShoot.value = false;
|
||||
Get.snackbar("お買い物加点を行いました。",
|
||||
@ -465,7 +531,9 @@ class CameraPage extends StatelessWidget {
|
||||
true,
|
||||
destinationController.photos[0].path);
|
||||
//Get.back();
|
||||
destinationController.rogainingCounted.value = true;
|
||||
if( destinationController.isInRog.value==true ) {
|
||||
destinationController.rogainingCounted.value = true; //ロゲ開始後のみ許可
|
||||
}
|
||||
destinationController.skipGps = false;
|
||||
destinationController.isPhotoShoot.value = false;
|
||||
|
||||
@ -496,6 +564,7 @@ class CameraPage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
//print("---- photos ${destination.photos} ----");
|
||||
if (buyPointPhoto == true) {
|
||||
// buyPointPhotoがtrueの場合は、BuyPointCameraウィジェットを返します。
|
||||
@ -780,7 +849,9 @@ class BuyPointCamera extends StatelessWidget {
|
||||
onPressed: () async {
|
||||
await destinationController.cancelBuyPoint(destination);
|
||||
Navigator.of(Get.context!).pop();
|
||||
destinationController.rogainingCounted.value = true;
|
||||
if( destinationController.isInRog.value==true ) {
|
||||
destinationController.rogainingCounted.value = true; // ロゲ開始後のみ許可
|
||||
}
|
||||
destinationController.skipGps = false;
|
||||
destinationController.isPhotoShoot.value = false;
|
||||
},
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:gifunavi/model/destination.dart';
|
||||
@ -8,8 +10,17 @@ import 'package:gifunavi/model/destination.dart';
|
||||
class CustomCameraView extends StatefulWidget {
|
||||
final Function(String) onImageCaptured;
|
||||
final Destination? destination;
|
||||
final Function(bool) onCameraStatusChanged;
|
||||
|
||||
//const CustomCameraView({super.key, required this.onImageCaptured, required this.destination});
|
||||
|
||||
const CustomCameraView({
|
||||
Key? key,
|
||||
required this.onImageCaptured,
|
||||
required this.destination,
|
||||
required this.onCameraStatusChanged, // 新しいコールバック
|
||||
}) : super(key: key);
|
||||
|
||||
const CustomCameraView({super.key, required this.onImageCaptured, required this.destination});
|
||||
|
||||
@override
|
||||
_CustomCameraViewState createState() => _CustomCameraViewState();
|
||||
@ -17,6 +28,8 @@ class CustomCameraView extends StatefulWidget {
|
||||
|
||||
class _CustomCameraViewState extends State<CustomCameraView> {
|
||||
CameraController? _controller;
|
||||
bool _isCameraAvailable = true;
|
||||
|
||||
late List<CameraDescription> _cameras;
|
||||
int _selectedCameraIndex = 0;
|
||||
double _currentScale = 1.0;
|
||||
@ -31,10 +44,25 @@ class _CustomCameraViewState extends State<CustomCameraView> {
|
||||
}
|
||||
|
||||
Future<void> _initializeCamera() async {
|
||||
_cameras = await availableCameras();
|
||||
_controller = CameraController(_cameras[_selectedCameraIndex], ResolutionPreset.medium);
|
||||
await _controller!.initialize();
|
||||
setState(() {});
|
||||
try {
|
||||
_cameras = await availableCameras();
|
||||
if (_cameras.isNotEmpty) {
|
||||
_controller = CameraController(
|
||||
_cameras[_selectedCameraIndex], ResolutionPreset.medium);
|
||||
await _controller!.initialize();
|
||||
setState(() {
|
||||
_isCameraAvailable = true;
|
||||
});
|
||||
} else {
|
||||
throw Exception('Camera is not available');
|
||||
}
|
||||
}catch(err){
|
||||
print("Error initializing camera: $err");
|
||||
setState(() {
|
||||
_isCameraAvailable = false;
|
||||
});
|
||||
}
|
||||
widget.onCameraStatusChanged(_isCameraAvailable);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -82,20 +110,46 @@ class _CustomCameraViewState extends State<CustomCameraView> {
|
||||
}
|
||||
|
||||
void _captureImage() async {
|
||||
if (_controller!.value.isInitialized) {
|
||||
final Directory appDirectory = await getApplicationDocumentsDirectory();
|
||||
final String imagePath = path.join(appDirectory.path, '${DateTime.now()}.jpg');
|
||||
if (_isCameraAvailable) {
|
||||
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);
|
||||
final XFile imageFile = await _controller!.takePicture();
|
||||
await imageFile.saveTo(imagePath);
|
||||
|
||||
widget.onImageCaptured(imagePath);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}else{
|
||||
// ダミー画像を使用
|
||||
final String imagePath = await _saveDummyImage();
|
||||
widget.onImageCaptured(imagePath);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> _saveDummyImage() async {
|
||||
final Directory appDirectory = await getApplicationDocumentsDirectory();
|
||||
final String imagePath = path.join(appDirectory.path, 'dummy_${DateTime.now()}.png');
|
||||
|
||||
// アセットからダミー画像を読み込む
|
||||
ByteData data = await rootBundle.load('assets/images/dummy_camera_image.png');
|
||||
List<int> bytes = data.buffer.asUint8List();
|
||||
|
||||
// ダミー画像をファイルとして保存
|
||||
await File(imagePath).writeAsBytes(bytes);
|
||||
|
||||
return imagePath;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!_isCameraAvailable) {
|
||||
return _buildDummyCameraView();
|
||||
}
|
||||
|
||||
if (_controller == null || !_controller!.value.isInitialized) {
|
||||
return Container();
|
||||
}
|
||||
@ -182,4 +236,64 @@ class _CustomCameraViewState extends State<CustomCameraView> {
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDummyCameraView() {
|
||||
return Stack(
|
||||
children: [
|
||||
Container(
|
||||
color: Colors.black,
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'カメラを利用できません',
|
||||
style: TextStyle(color: Colors.white, fontSize: 18),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 16.0,
|
||||
left: 16.0,
|
||||
right: 16.0,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.flash_off, color: Colors.white),
|
||||
iconSize: 32,
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: _captureEmulatedImage,
|
||||
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: () {},
|
||||
icon: const Icon(Icons.flip_camera_ios, color: Colors.white),
|
||||
iconSize: 32,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _captureEmulatedImage() async {
|
||||
final Directory appDirectory = await getApplicationDocumentsDirectory();
|
||||
final String imagePath = path.join(appDirectory.path, '${DateTime.now()}.jpg');
|
||||
|
||||
// ダミーの画像ファイルを作成
|
||||
await File(imagePath).writeAsBytes(Uint8List(0));
|
||||
|
||||
widget.onImageCaptured(imagePath);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
@ -29,12 +29,18 @@ import 'dart:async';
|
||||
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
import 'package:gifunavi/widgets/debug_widget.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'package:image_gallery_saver/image_gallery_saver.dart';
|
||||
|
||||
import 'package:gifunavi/pages/permission/permission.dart' ;
|
||||
|
||||
// 新しいインポート
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
|
||||
// 目的地に関連する状態管理とロジックを担当するクラスです。
|
||||
//
|
||||
class DestinationController extends GetxController {
|
||||
@ -423,11 +429,11 @@ class DestinationController extends GetxController {
|
||||
// 写真撮影モードでない場合
|
||||
|
||||
if (ds.isEmpty) {
|
||||
debugPrint("* 目的地がない場合 ==> 検知半径=-1の場合");
|
||||
//debugPrint("* 目的地がない場合 ==> ds=${ds}");
|
||||
|
||||
// print("----- in location popup cp - ${d.cp}----");
|
||||
if ((d.cp == -1 || d.cp==0 ) && DateTime.now().difference(lastGoalAt).inHours >= 10) {
|
||||
debugPrint("**1: 開始CPで、最後にゴールしてから24時間経過していれば、");
|
||||
debugPrint("**1: 目的地がない場合で、スタート地点。開始CPで、最後にゴールしてから24時間経過していれば、");
|
||||
|
||||
chekcs = 1;
|
||||
//start
|
||||
@ -461,15 +467,15 @@ class DestinationController extends GetxController {
|
||||
// 以下の条件分岐を追加
|
||||
} else if (ds.isNotEmpty && ds[0].checkedin == true) {
|
||||
// 目的地がDBに存在し、すでにチェックインしている場合は自動ポップアップを表示しない
|
||||
debugPrint("チェックイン済み");
|
||||
debugPrint("目的地がない場合で、チェックイン済み");
|
||||
return;
|
||||
|
||||
} else if (isInRog.value == true &&
|
||||
indexController.rogMode.value == 1 &&
|
||||
(locationAlreadyCheckedIn==false) &&
|
||||
d.cp != -1 && d.cp != 0 && d.cp != -2) {
|
||||
} else if (isInRog.value == true && // 常にfalse だよ。。
|
||||
indexController.rogMode.value == 1 && // マップではなくリストページだよ。
|
||||
(locationAlreadyCheckedIn==false) && // まだチェックインしてないよ。
|
||||
d.cp != -1 && d.cp != 0 && d.cp != -2) { // スタートでもゴールでもないよ。
|
||||
|
||||
debugPrint("**2: 標準CP まだチェックインしていない。");
|
||||
debugPrint("**2: 目的地がない場合で、標準CP まだチェックインしていない。");
|
||||
|
||||
// print("----- in location popup checkin cp - ${d.cp}----");
|
||||
chekcs = 2; // 標準CP
|
||||
@ -494,14 +500,20 @@ class DestinationController extends GetxController {
|
||||
});
|
||||
}
|
||||
return;
|
||||
}else{
|
||||
debugPrint("**Else: isInRog=${isInRog.value}, rogMode=${indexController.rogMode.value},locationAlreadyCheckedIn=${locationAlreadyCheckedIn},d.cp=${d.cp}");
|
||||
}
|
||||
}
|
||||
|
||||
// 以降、検知範囲にある場合。
|
||||
//debugPrint("検知範囲にある場合");
|
||||
debugPrint("検知範囲にある場合だよ...");
|
||||
|
||||
debugPrint("---- 検知範囲: ${d.checkin_radious} > 距離:${distance} ----");
|
||||
debugPrint("---- チェックイン済みか? $locationAlreadyCheckedIn ----");
|
||||
debugPrint("---- isInRog : ${isInRog.value}, checkingin = ${isCheckingIn.value}");
|
||||
debugPrint("---- buyPointImageAdded: ${buyPointImageAdded}, ds.isNotEmpty?: ${ds.isNotEmpty},buyPoint:${buyPoint},buyPointCanceled=${buyPointCanceled}");
|
||||
debugPrint(" ");
|
||||
|
||||
// print("---- location checkin radious ${d.checkin_radious} ----");
|
||||
// print("---- already checked in $locationAlreadyCheckedIn ----");
|
||||
if ((checkinRadious >= distance || checkinRadious == -1) &&
|
||||
locationAlreadyCheckedIn == false &&
|
||||
isInRog.value == true &&
|
||||
@ -515,9 +527,9 @@ class DestinationController extends GetxController {
|
||||
//print(
|
||||
// "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ make checkin ${d.sub_loc_id}@@@@@@@@@@@");
|
||||
makeCheckin(d, true, ""); // チェックインして
|
||||
if (d.cp != -1 && d.cp != -2 && d.cp != 0 ) {
|
||||
rogainingCounted.value = true; // ゴール用チェックイン済み
|
||||
}
|
||||
//if (d.cp != -1 && d.cp != -2 && d.cp != 0 ) {
|
||||
// rogainingCounted.value = true; // ゴール用チェックイン済み
|
||||
//}
|
||||
skipGps = false;
|
||||
}
|
||||
return; // 戻る
|
||||
@ -552,7 +564,9 @@ class DestinationController extends GetxController {
|
||||
))).whenComplete(() {
|
||||
shouldShowBottomSheet = true;
|
||||
skipGps = false;
|
||||
rogainingCounted.value = true;
|
||||
if( isInRog.value==true ) {
|
||||
rogainingCounted.value = true;
|
||||
}
|
||||
chekcs = 0;
|
||||
isInCheckin.value = false;
|
||||
isCheckingIn.value = false;
|
||||
@ -729,13 +743,15 @@ class DestinationController extends GetxController {
|
||||
DatabaseHelper db = DatabaseHelper.instance;
|
||||
|
||||
if (isgoal == false) {
|
||||
await db.deleteAllDestinations();
|
||||
await db.deleteAllRogaining();
|
||||
// await db.deleteAllDestinations();
|
||||
// await db.deleteAllRogaining();
|
||||
await db.deleteAllDestinationsExceptTodayCheckins();
|
||||
await db.deleteAllRogainingExceptToday();
|
||||
}
|
||||
|
||||
int? latgoal = await db.latestGoal();
|
||||
lastGoalAt = DateTime.fromMicrosecondsSinceEpoch(latgoal!);
|
||||
//print("===== last goal : $last_goal_at =====");
|
||||
debugPrint("===== last goal : $lastGoalAt =====");
|
||||
dbService.updateDatabase();
|
||||
}
|
||||
|
||||
@ -743,7 +759,7 @@ class DestinationController extends GetxController {
|
||||
//
|
||||
void deleteAllDestinations() {
|
||||
DatabaseHelper db = DatabaseHelper.instance;
|
||||
db.deleteAllDestinations().then((value) {
|
||||
db.deleteAllDestinationsExceptTodayCheckins().then((value) {
|
||||
populateDestinations();
|
||||
});
|
||||
}
|
||||
@ -760,6 +776,18 @@ class DestinationController extends GetxController {
|
||||
photos.add(File(imagePath));
|
||||
},
|
||||
destination: destination,
|
||||
onCameraStatusChanged: (isAvailable) {
|
||||
// カメラの状態が変更されたときの処理
|
||||
if (!isAvailable) {
|
||||
// カメラが利用できない場合の処理
|
||||
Get.snackbar(
|
||||
'エラー',
|
||||
'カメラを初期化できませんでした。',
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
/*
|
||||
builder: (_) => CameraCamera(
|
||||
@ -859,7 +887,9 @@ class DestinationController extends GetxController {
|
||||
dbDest: dss,
|
||||
))).whenComplete(() {
|
||||
skipGps = false;
|
||||
rogainingCounted.value = true;
|
||||
if( isInRog.value == true ) { // ロゲ開始していれば、遠くまで来たことにする。
|
||||
rogainingCounted.value = true;
|
||||
}
|
||||
chekcs = 0;
|
||||
isInCheckin.value = false;
|
||||
//Get.back();
|
||||
@ -984,6 +1014,7 @@ class DestinationController extends GetxController {
|
||||
//
|
||||
// 2024-4-8 Akira : See 2809
|
||||
// checkForCheckinメソッドの再帰呼び出しをunawaitedで囲んで、非同期処理の結果を待たずに先に進むようにしました。また、再帰呼び出しの前に一定時間待機するようにしました。
|
||||
// 2024-8-24 ... 佐伯呼び出しが必要なのか?
|
||||
//
|
||||
Future<void> checkForCheckin() async {
|
||||
//print("--- Start of checkForCheckin function ---");
|
||||
@ -1022,13 +1053,15 @@ class DestinationController extends GetxController {
|
||||
} catch (e) {
|
||||
print("An error occurred: $e");
|
||||
// await checkForCheckin();
|
||||
} finally {
|
||||
await Future.delayed(const Duration(seconds: 1)); // 一定時間待機してから再帰呼び出し
|
||||
//} finally {
|
||||
// await Future.delayed(const Duration(seconds: 1)); // 一定時間待機してから再帰呼び出し
|
||||
//print("--- End of checkForCheckin function, calling recursively ---");
|
||||
unawaited( checkForCheckin() );
|
||||
//unawaited( checkForCheckin() );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// GPSデータをサーバーにプッシュする関数です。
|
||||
//
|
||||
Future<void> pushGPStoServer() async {
|
||||
@ -1089,55 +1122,46 @@ class DestinationController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _saveImageToGallery(String imagePath) async {
|
||||
Future<String?> _saveImageToGallery(String imagePath) async {
|
||||
final status = await PermissionController.checkStoragePermission();
|
||||
if(!status){
|
||||
await PermissionController.requestStoragePermission();
|
||||
}
|
||||
|
||||
/*
|
||||
final status = await Permission.storage.status;
|
||||
if (!status.isGranted) {
|
||||
final result = await Permission.storage.request();
|
||||
if (!result.isGranted) {
|
||||
// ユーザーがストレージの権限を拒否した場合の処理
|
||||
showDialog(
|
||||
context: Get.context!,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('ストレージの権限が必要です'),
|
||||
content: Text(
|
||||
'画像をギャラリーに保存するには、ストレージの権限が必要です。アプリの設定画面で権限を許可してください。'),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('キャンセル'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text('設定'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
openAppSettings(); // アプリの設定画面を開く
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
);
|
||||
try {
|
||||
final appDir = await getApplicationDocumentsDirectory();
|
||||
//final fileName = path.basename(imagePath);
|
||||
final fileName = 'checkin_${DateTime.now().millisecondsSinceEpoch}.jpg';
|
||||
final savedImage = await File(imagePath).copy('${appDir.path}/$fileName');
|
||||
|
||||
return;
|
||||
debugPrint("fileName=${fileName}, appDir=${appDir} => ${savedImage}");
|
||||
|
||||
// ギャラリーにも保存
|
||||
//await ImageGallerySaver.saveFile(savedImage.path);
|
||||
await Future.delayed(const Duration(seconds: 3), () async {
|
||||
final result = await ImageGallerySaver.saveFile(savedImage.path);
|
||||
print("Save result: $result");
|
||||
}).timeout(const Duration(seconds: 5));
|
||||
|
||||
|
||||
debugPrint('Image saved to: ${savedImage.path}');
|
||||
return savedImage.path;
|
||||
|
||||
/*
|
||||
final result = await ImageGallerySaver.saveFile(imagePath);
|
||||
debugPrint('Image saved to gallery: $result');
|
||||
if (result['isSuccess']) {
|
||||
return result['filePath'];
|
||||
}
|
||||
*/
|
||||
} catch (e) {
|
||||
if (e is TimeoutException) {
|
||||
print("Operation timed out");
|
||||
} else {
|
||||
print('Failed to save image to gallery: $e');
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
try {
|
||||
final result = await ImageGallerySaver.saveFile(imagePath);
|
||||
print('Image saved to gallery: $result');
|
||||
} catch (e) {
|
||||
print('Failed to save image to gallery: $e');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 買い物ポイントを作成する関数です。 指定された目的地に対して買い物ポイントの処理を行います。
|
||||
@ -1145,13 +1169,10 @@ class DestinationController extends GetxController {
|
||||
// 買い物ポイントの作成に失敗した場合のエラーハンドリングを追加することを検討してください。
|
||||
//
|
||||
Future<void> makeBuyPoint(Destination destination, String imageurl) async {
|
||||
String? savedImagePath = await _saveImageToGallery(imageurl);
|
||||
DatabaseHelper db = DatabaseHelper.instance;
|
||||
await db.updateBuyPoint(destination, imageurl);
|
||||
await db.updateBuyPoint(destination, savedImagePath ?? imageurl);
|
||||
populateDestinations();
|
||||
//await _saveImageFromPath(imageurl);
|
||||
await _saveImageToGallery(imageurl);
|
||||
|
||||
|
||||
|
||||
if (indexController.currentUser.isNotEmpty) {
|
||||
double cpNum = destination.cp!;
|
||||
@ -1160,6 +1181,7 @@ class DestinationController extends GetxController {
|
||||
|
||||
int userId = indexController.currentUser[0]["user"]["id"];
|
||||
//print("--- Pressed -----");
|
||||
debugPrint("user=${indexController.currentUser[0]["user"]}");
|
||||
String team = indexController.currentUser[0]["user"]['team_name'];
|
||||
//print("--- _team : ${_team}-----");
|
||||
String eventCode = indexController.currentUser[0]["user"]["event_code"];
|
||||
@ -1172,7 +1194,7 @@ class DestinationController extends GetxController {
|
||||
//print("------ checkin event $eventCode ------");
|
||||
ExternalService()
|
||||
.makeCheckpoint(userId, token, formattedDate, team, cpNum.round(),
|
||||
eventCode, imageurl)
|
||||
eventCode, savedImagePath ?? imageurl)
|
||||
.then((value) {
|
||||
//print("------Ext service check point $value ------");
|
||||
});
|
||||
@ -1197,7 +1219,10 @@ class DestinationController extends GetxController {
|
||||
|
||||
if (ddd.isEmpty) {
|
||||
destination.checkedin = true;
|
||||
destination.checkin_image = imageurl;
|
||||
if (imageurl.isNotEmpty) {
|
||||
String? savedImagePath = await _saveImageToGallery(imageurl);
|
||||
destination.checkin_image = savedImagePath ?? imageurl;
|
||||
}
|
||||
await db.insertDestination(destination);
|
||||
// print("~~~~ inserted into db ~~~~");
|
||||
}
|
||||
@ -1212,7 +1237,8 @@ class DestinationController extends GetxController {
|
||||
//await _saveImageFromPath(imageurl!);
|
||||
}
|
||||
if (imageurl.isNotEmpty) {
|
||||
await _saveImageToGallery(imageurl);
|
||||
String? savedImagePath = await _saveImageToGallery(imageurl);
|
||||
destination.checkin_image = savedImagePath ?? imageurl;
|
||||
}
|
||||
|
||||
populateDestinations();
|
||||
@ -1299,11 +1325,11 @@ class DestinationController extends GetxController {
|
||||
void onInit() async {
|
||||
super.onInit();
|
||||
|
||||
/*
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await PermissionController.checkAndRequestPermissions();
|
||||
});
|
||||
*/
|
||||
|
||||
|
||||
startGPSCheckTimer();
|
||||
|
||||
@ -1810,7 +1836,7 @@ class DestinationController extends GetxController {
|
||||
//
|
||||
void deleteDBDestinations() {
|
||||
DatabaseHelper db = DatabaseHelper.instance;
|
||||
db.deleteAllDestinations().then((value) {
|
||||
db.deleteAllDestinationsExceptTodayCheckins().then((value) {
|
||||
populateDestinations();
|
||||
});
|
||||
dbService.updateDatabase();
|
||||
|
||||
@ -2,6 +2,8 @@ import 'package:get/get.dart';
|
||||
import 'package:gifunavi/pages/entry/entry_controller.dart';
|
||||
import 'package:gifunavi/services/api_service.dart';
|
||||
|
||||
import '../index/index_controller.dart';
|
||||
|
||||
class EntryBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
|
||||
@ -191,18 +191,21 @@ class EntryController extends GetxController {
|
||||
final updatedCategory = await _apiService.getZekkenNumber(selectedCategory.value!.id);
|
||||
final zekkenNumber = updatedCategory.categoryNumber.toString();
|
||||
|
||||
// selectedDate.value に 9時間を加えてJSTのオフセットを適用
|
||||
final jstDate = selectedDate.value!.add(const Duration(hours: 9));
|
||||
|
||||
final newEntry = await _apiService.createEntry(
|
||||
selectedTeam.value!.id,
|
||||
selectedEvent.value!.id,
|
||||
selectedCategory.value!.id,
|
||||
selectedDate.value!,
|
||||
jstDate, // JSTオフセットが適用された日付を使用
|
||||
zekkenNumber,
|
||||
);
|
||||
entries.add(newEntry);
|
||||
Get.back();
|
||||
} catch (e) {
|
||||
print('Error creating entry: $e');
|
||||
Get.snackbar('Error', 'Failed to create entry');
|
||||
Get.snackbar('Error', '$e');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
@ -4,6 +4,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:gifunavi/model/destination.dart';
|
||||
import 'package:gifunavi/utils/database_helper.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
class HistoryPage extends StatefulWidget {
|
||||
const HistoryPage({super.key});
|
||||
@ -15,6 +17,48 @@ class HistoryPage extends StatefulWidget {
|
||||
class _HistoryPageState extends State<HistoryPage> {
|
||||
DatabaseHelper db = DatabaseHelper.instance;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("pass_history".tr),
|
||||
),
|
||||
body: FutureBuilder<List<Destination>>(
|
||||
future: db.getDestinations(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (snapshot.hasError) {
|
||||
return Center(child: Text('Error: ${snapshot.error}'));
|
||||
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
||||
return Center(child: Text("no_checkin_yet".tr));
|
||||
}
|
||||
|
||||
final dests = snapshot.data!;
|
||||
return ListView.builder(
|
||||
itemCount: dests.length,
|
||||
itemBuilder: (context, index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: CustomWidget(
|
||||
title: dests[index].name ?? 'No Name',
|
||||
subtitle: "${dests[index].sub_loc_id ?? 'N/A'} : ${dests[index].name ?? 'N/A'}",
|
||||
image1Path: dests[index].checkin_image,
|
||||
image2Path: dests[index].buypoint_image,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
class _HistoryPageState_old extends State<HistoryPage> {
|
||||
DatabaseHelper db = DatabaseHelper.instance;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -86,8 +130,97 @@ class _HistoryPageState extends State<HistoryPage> {
|
||||
);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
class CustomWidget extends StatelessWidget {
|
||||
final String? image1Path;
|
||||
final String? image2Path;
|
||||
final String title;
|
||||
final String subtitle;
|
||||
|
||||
const CustomWidget({
|
||||
super.key,
|
||||
this.image1Path,
|
||||
this.image2Path,
|
||||
required this.title,
|
||||
required this.subtitle,
|
||||
});
|
||||
|
||||
Widget _buildImage(String? path) {
|
||||
if (path == null) return const SizedBox.shrink();
|
||||
|
||||
return FutureBuilder<String>(
|
||||
future: _getFullImagePath(path),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) {
|
||||
return Image.file(
|
||||
File(snapshot.data!),
|
||||
width: 50,
|
||||
height: 100,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
print('Error loading image: $error');
|
||||
return const Icon(Icons.error);
|
||||
},
|
||||
);
|
||||
} else if (snapshot.hasError) {
|
||||
print('Error loading image path: ${snapshot.error}');
|
||||
return const Icon(Icons.error);
|
||||
} else {
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<String> _getFullImagePath(String imagePath) async {
|
||||
final appDir = await getApplicationDocumentsDirectory();
|
||||
final fileName = path.basename(imagePath);
|
||||
final fullPath = path.join(appDir.path, fileName);
|
||||
debugPrint("Full image path: $fullPath");
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 104,
|
||||
child: Row(
|
||||
children: [
|
||||
_buildImage(image1Path),
|
||||
if (image1Path != null && image2Path != null) const SizedBox(width: 2),
|
||||
_buildImage(image2Path),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
maxLines: null,
|
||||
),
|
||||
Text(
|
||||
subtitle,
|
||||
style: const TextStyle(fontSize: 16),
|
||||
maxLines: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
class CustomWidget_old extends StatelessWidget {
|
||||
final Image? image1;
|
||||
final Image? image2;
|
||||
final String title;
|
||||
@ -152,3 +285,4 @@ class CustomWidget extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
*/
|
||||
@ -3,12 +3,18 @@ import 'package:gifunavi/pages/destination/destination_controller.dart';
|
||||
import 'package:gifunavi/pages/index/index_controller.dart';
|
||||
import 'package:gifunavi/utils/location_controller.dart';
|
||||
|
||||
import '../../services/api_service.dart';
|
||||
|
||||
class IndexBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<IndexController>(() => IndexController());
|
||||
//Get.put<IndexController>(IndexController());
|
||||
Get.put<LocationController>(LocationController());
|
||||
Get.put<DestinationController>(DestinationController());
|
||||
//Get.lazyPut<IndexController>(() => IndexController());
|
||||
////Get.put<IndexController>(IndexController());
|
||||
//Get.put<LocationController>(LocationController());
|
||||
//Get.put<DestinationController>(DestinationController());
|
||||
|
||||
Get.put(IndexController(apiService: Get.find<ApiService>()), permanent: true);
|
||||
Get.put(LocationController(), permanent: true);
|
||||
Get.put(DestinationController(), permanent: true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -31,6 +32,8 @@ import 'package:gifunavi/widgets/helper_dialog.dart';
|
||||
import 'package:timezone/timezone.dart' as tz;
|
||||
import 'package:timezone/data/latest.dart' as tz;
|
||||
|
||||
import '../permission/permission.dart';
|
||||
|
||||
class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
List<GeoJSONFeatureCollection> locations = <GeoJSONFeatureCollection>[].obs;
|
||||
List<GeoJSONFeature> currentFeature = <GeoJSONFeature>[].obs;
|
||||
@ -68,9 +71,14 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
String? userToken;
|
||||
|
||||
//late final ApiService _apiService;
|
||||
final ApiService _apiService = Get.find<ApiService>();
|
||||
final ApiService _apiService; // = Get.find<ApiService>();
|
||||
final DatabaseHelper _dbHelper = DatabaseHelper.instance;
|
||||
|
||||
IndexController({
|
||||
required ApiService apiService,
|
||||
}) : _apiService = apiService;
|
||||
|
||||
|
||||
// mode = 0 is map mode, mode = 1 list mode
|
||||
var mode = 0.obs;
|
||||
|
||||
@ -235,6 +243,10 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
initConnectivity();
|
||||
_connectivitySubscription = _connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await PermissionController.checkAndRequestPermissions();
|
||||
});
|
||||
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
_startLocationService(); // アプリ起動時にLocationServiceを開始する
|
||||
|
||||
@ -263,10 +275,10 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
connectionStatusName.value = "WiFi";
|
||||
break;
|
||||
case ConnectivityResult.mobile:
|
||||
connectionStatusName.value = "モバイルデータ";
|
||||
connectionStatusName.value = "mobile";
|
||||
break;
|
||||
case ConnectivityResult.none:
|
||||
connectionStatusName.value = "オフライン";
|
||||
connectionStatusName.value = "offline";
|
||||
break;
|
||||
default:
|
||||
connectionStatusName.value = "不明";
|
||||
@ -352,22 +364,53 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
}
|
||||
|
||||
void _startLocationService() async {
|
||||
const platform = MethodChannel('location');
|
||||
try {
|
||||
logManager.addOperationLog("Called start location service.");
|
||||
await platform.invokeMethod('startLocationService');
|
||||
} on PlatformException catch (e) {
|
||||
print("Failed to start location service: '${e.message}'.");
|
||||
if (Platform.isAndroid) {
|
||||
const platform = MethodChannel('location');
|
||||
try {
|
||||
logManager.addOperationLog("Called start location service.");
|
||||
await platform.invokeMethod('startLocationService');
|
||||
} on PlatformException catch (e) {
|
||||
print("Failed to start location service: '${e.message}'.");
|
||||
}
|
||||
}else if (Platform.isIOS) {
|
||||
// iOSの位置情報サービス開始ロジック
|
||||
// 例: geolocatorプラグインを使用する場合
|
||||
try {
|
||||
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||
if (!serviceEnabled) {
|
||||
// 位置情報サービスが無効の場合の処理
|
||||
return;
|
||||
}
|
||||
// 位置情報の権限確認と取得開始
|
||||
LocationPermission permission = await Geolocator.checkPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
permission = await Geolocator.requestPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
// 権限が拒否された場合の処理
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 位置情報の取得開始
|
||||
Geolocator.getPositionStream().listen((Position position) {
|
||||
// 位置情報を使用した処理
|
||||
});
|
||||
} catch (e) {
|
||||
print('Error starting iOS location service: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _stopLocationService() async {
|
||||
const platform = MethodChannel('location');
|
||||
try {
|
||||
logManager.addOperationLog("Called stop location service.");
|
||||
await platform.invokeMethod('stopLocationService');
|
||||
} on PlatformException catch (e) {
|
||||
print("Failed to stop location service: '${e.message}'.");
|
||||
if (Platform.isAndroid) {
|
||||
const platform = MethodChannel('location');
|
||||
try {
|
||||
logManager.addOperationLog("Called stop location service.");
|
||||
await platform.invokeMethod('stopLocationService');
|
||||
} on PlatformException catch (e) {
|
||||
print("Failed to stop location service: '${e.message}'.");
|
||||
}
|
||||
}else{
|
||||
debugPrint("stopLocation for iOS");
|
||||
}
|
||||
}
|
||||
|
||||
@ -433,51 +476,75 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
|
||||
// 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。
|
||||
//
|
||||
void login(String email, String password, BuildContext context) async{
|
||||
|
||||
AuthService.login(email, password).then((value) async {
|
||||
print("------- logged in user details ######## $value ###### --------");
|
||||
if (value.isNotEmpty) {
|
||||
logManager.addOperationLog("User logged in : $value.");
|
||||
|
||||
// Navigator.pop(context);
|
||||
print("--------- user details login ----- $value");
|
||||
changeUser(value);
|
||||
|
||||
// ログイン成功後、api_serviceを初期化
|
||||
await Get.putAsync(() => ApiService().init());
|
||||
|
||||
// ユーザー情報の完全性をチェック
|
||||
if (await checkUserInfoComplete()) {
|
||||
Get.offAllNamed(AppPages.INDEX);
|
||||
} else {
|
||||
Get.offAllNamed(AppPages.USER_DETAILS_EDIT);
|
||||
}
|
||||
|
||||
Future<void> login(String email, String password) async {
|
||||
try {
|
||||
final value = await AuthService.login(email, password);
|
||||
if (value.isNotEmpty && value['token'] != null) {
|
||||
await changeUser(value);
|
||||
await _initializeUserData();
|
||||
Get.offAllNamed(AppPages.INDEX);
|
||||
} else {
|
||||
logManager.addOperationLog("User failed login : $email , $password.");
|
||||
isLoading.value = false;
|
||||
Get.snackbar(
|
||||
"login_failed".tr,
|
||||
"check_login_id_or_password".tr,
|
||||
Get.snackbar('Login Failed', 'Invalid credentials');
|
||||
}
|
||||
} catch (e) {
|
||||
print('Login error: $e');
|
||||
Get.snackbar('Login Failed', 'An error occurred. Please try again.');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _initializeUserData() async {
|
||||
try {
|
||||
await fetchUserEventInfo();
|
||||
await fetchTeamData();
|
||||
// 他の必要なデータ取得処理
|
||||
} catch (e) {
|
||||
print('Error initializing user data: $e');
|
||||
Get.snackbar('Error', 'Failed to load user data. Please try again.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> login_old(String email, String password, BuildContext context) async{
|
||||
|
||||
try {
|
||||
AuthService.login(email, password).then((value) async {
|
||||
print("------- logged in user details ######## $value ###### --------");
|
||||
if (value.isNotEmpty && value['token']!=null) {
|
||||
logManager.addOperationLog("User logged in : $value.");
|
||||
|
||||
// Navigator.pop(context);
|
||||
print("--------- user details login ----- $value");
|
||||
// ログイン成功後、api_serviceを初期化
|
||||
await Get.putAsync(() => ApiService().init());
|
||||
|
||||
// ユーザー情報の完全性をチェック
|
||||
if (await checkUserInfoComplete()) {
|
||||
Get.offAllNamed(AppPages.INDEX);
|
||||
} else {
|
||||
Get.offAllNamed(AppPages.USER_DETAILS_EDIT);
|
||||
}
|
||||
} else {
|
||||
logManager.addOperationLog("User failed login : $email , $password.");
|
||||
isLoading.value = false;
|
||||
Get.snackbar(
|
||||
"login_failed".tr,
|
||||
"check_login_id_or_password".tr,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
icon: const Icon(Icons.error, 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"))
|
||||
);
|
||||
}
|
||||
});
|
||||
snackPosition: SnackPosition.TOP,
|
||||
duration: const Duration(seconds: 3),
|
||||
//backgroundColor: Colors.yellow,
|
||||
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch(e ){
|
||||
print('Login error: $e');
|
||||
Get.snackbar('Login Failed', 'An error occurred. Please try again.');
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> checkUserInfoComplete() async {
|
||||
final user = await ApiService.to.getCurrentUser();
|
||||
return user.firstname.isNotEmpty &&
|
||||
user.lastname.isNotEmpty &&
|
||||
user.dateOfBirth != null;
|
||||
}
|
||||
|
||||
// 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。
|
||||
//
|
||||
@ -532,12 +599,12 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
}
|
||||
*/
|
||||
|
||||
void logout() async {
|
||||
Future<void> logout() async {
|
||||
logManager.addOperationLog("User logout : $currentUser .");
|
||||
saveGameState();
|
||||
locations.clear();
|
||||
DatabaseHelper db = DatabaseHelper.instance;
|
||||
db.deleteAllDestinations().then((value) {
|
||||
db.deleteAllDestinationsExceptTodayCheckins().then((value) {
|
||||
DestinationController destinationController =
|
||||
Get.find<DestinationController>();
|
||||
destinationController.populateDestinations();
|
||||
@ -619,28 +686,48 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
}
|
||||
*/
|
||||
|
||||
void changeUser(Map<String, dynamic> value, {bool replace = true}) async{
|
||||
currentUser.clear();
|
||||
currentUser.add(value);
|
||||
if (replace) {
|
||||
saveToDevice(currentUser[0]["token"]);
|
||||
Future<void> changeUser(Map<String, dynamic> value, {bool replace = true}) async{
|
||||
try {
|
||||
if (value['user'] == null || value['token'] == null) {
|
||||
throw Exception('Invalid user data');
|
||||
}
|
||||
currentUser.clear();
|
||||
currentUser.add(value);
|
||||
if (replace) {
|
||||
saveToDevice(currentUser[0]["token"]);
|
||||
}
|
||||
isLoading.value = false;
|
||||
|
||||
// ユーザーのイベント情報を取得
|
||||
await fetchUserEventInfo();
|
||||
|
||||
loadLocationsBound(currentUser[0]["user"]["event_code"]);
|
||||
if (currentUser.isNotEmpty) {
|
||||
rogMode.value = 0;
|
||||
restoreGame();
|
||||
|
||||
// チームデータを取得
|
||||
await fetchTeamData();
|
||||
} else {
|
||||
rogMode.value = 1;
|
||||
}
|
||||
Get.toNamed(AppPages.INDEX);
|
||||
} catch( e ){
|
||||
print('Error in changeUser: $e');
|
||||
Get.snackbar('Error', 'Failed to update user information');
|
||||
}
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
// ユーザーのイベント情報を取得
|
||||
await fetchUserEventInfo();
|
||||
|
||||
loadLocationsBound( currentUser[0]["user"]["event_code"]);
|
||||
if (currentUser.isNotEmpty) {
|
||||
rogMode.value = 0;
|
||||
restoreGame();
|
||||
|
||||
// チームデータを取得
|
||||
await fetchTeamData();
|
||||
} else {
|
||||
rogMode.value = 1;
|
||||
Future<bool> checkUserInfoComplete() async {
|
||||
try {
|
||||
final user = await ApiService.to.getCurrentUser();
|
||||
return user.firstname.isNotEmpty &&
|
||||
user.lastname.isNotEmpty &&
|
||||
user.dateOfBirth != null;
|
||||
} catch (e) {
|
||||
print('Error checking user info: $e');
|
||||
return false;
|
||||
}
|
||||
Get.toNamed(AppPages.INDEX);
|
||||
}
|
||||
|
||||
Future<void> fetchUserEventInfo() async {
|
||||
@ -697,7 +784,7 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
|
||||
Future<void> fetchTeamData() async {
|
||||
try {
|
||||
Get.put(TeamController());
|
||||
Get.put(TeamController(apiService:Get.find<ApiService>()));
|
||||
// \"TeamController\" not found. You need to call \"Get.put(TeamController())\" or \"Get.lazyPut(()=>TeamController())\"
|
||||
final teamController = Get.find<TeamController>();
|
||||
await teamController.fetchTeams();
|
||||
@ -945,7 +1032,7 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
|
||||
Future<void> checkEntryData() async {
|
||||
// エントリーデータの有無をチェックするロジック
|
||||
final teamController = TeamController();
|
||||
final teamController = TeamController(apiService:Get.find<ApiService>());
|
||||
bool hasEntryData = teamController.checkIfUserHasEntryData();
|
||||
if (!hasEntryData) {
|
||||
await showHelperDialog(
|
||||
|
||||
@ -38,10 +38,21 @@ class _IndexPageState extends State<IndexPage> {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
//checkLoginAndShowDialog();
|
||||
//checkEventAndNavigate();
|
||||
});
|
||||
}
|
||||
|
||||
void checkLoginAndShowDialog() {
|
||||
void checkEventAndNavigate() async {
|
||||
if (indexController.currentUser.isNotEmpty &&
|
||||
indexController.currentUser[0]["user"]["event_code"] == null) {
|
||||
// イベントコードがない場合、EVENT_ENTRYページに遷移
|
||||
await Get.toNamed(AppPages.EVENT_ENTRY);
|
||||
// EVENT_ENTRYページから戻ってきた後に警告を表示
|
||||
_showEventSelectionWarning();
|
||||
}
|
||||
}
|
||||
|
||||
void checkLoginAndShowDialog() async {
|
||||
if (indexController.currentUser.isEmpty) {
|
||||
showDialog(
|
||||
context: context,
|
||||
@ -78,9 +89,30 @@ class _IndexPageState extends State<IndexPage> {
|
||||
);
|
||||
},
|
||||
);
|
||||
}else{
|
||||
if(indexController.currentUser[0]["user"]["event_code"] == null) {
|
||||
// イベントコードがない場合、EVENT_ENTRYページに遷移
|
||||
await Get.toNamed(AppPages.EVENT_ENTRY);
|
||||
// EVENT_ENTRYページから戻ってきた後に警告を表示
|
||||
_showEventSelectionWarning();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _showEventSelectionWarning() {
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
title: Text('警告'),
|
||||
content: Text('イベントを選択してください。'),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('OK'),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// class IndexPage extends GetView<IndexController> {
|
||||
// IndexPage({Key? key}) : super(key: key);
|
||||
|
||||
@ -119,8 +119,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
body: GestureDetector(
|
||||
onTap: () => FocusScope.of(context).unfocus(),
|
||||
child: indexController.currentUser.isEmpty
|
||||
? SizedBox(
|
||||
width: double.infinity,
|
||||
? SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
@ -216,8 +215,8 @@ class _LoginPageState extends State<LoginPage> {
|
||||
true;
|
||||
indexController.login(
|
||||
emailController.text,
|
||||
passwordController.text,
|
||||
context);
|
||||
passwordController.text
|
||||
);
|
||||
},
|
||||
color: Colors.indigoAccent[400],
|
||||
shape: RoundedRectangleBorder(
|
||||
@ -271,37 +270,34 @@ class _LoginPageState extends State<LoginPage> {
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"app_developed_by_gifu_dx".tr,
|
||||
style: const TextStyle(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
fontSize: 10.0),
|
||||
style: const TextStyle(fontSize: 10.0),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"※第8回と第9回は、岐阜県の令和5年度「清流の国ぎふ」SDGs推進ネットワーク連携促進補助金を受けています",
|
||||
style: TextStyle(
|
||||
fontSize: 10.0,
|
||||
),
|
||||
style: const TextStyle(fontSize: 10.0),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@ -120,8 +120,8 @@ class LoginPopupPage extends StatelessWidget {
|
||||
true;
|
||||
indexController.login(
|
||||
emailController.text,
|
||||
passwordController.text,
|
||||
context);
|
||||
passwordController.text
|
||||
);
|
||||
},
|
||||
color: Colors.indigoAccent[400],
|
||||
shape: RoundedRectangleBorder(
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
@ -10,15 +12,6 @@ class PermissionController {
|
||||
static bool _isRequestingPermission = false;
|
||||
static Completer<bool>? _permissionCompleter;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static Future<bool> checkAndRequestPermissions() async {
|
||||
if (_isRequestingPermission) {
|
||||
return _permissionCompleter!.future;
|
||||
@ -27,13 +20,119 @@ class PermissionController {
|
||||
_isRequestingPermission = true;
|
||||
_permissionCompleter = Completer<bool>();
|
||||
|
||||
bool hasPermissions = await checkLocationPermissions();
|
||||
try {
|
||||
bool hasPermissions = await _checkLocationPermissions();
|
||||
if (!hasPermissions) {
|
||||
bool userAgreed = await showLocationDisclosure();
|
||||
if (userAgreed) {
|
||||
if (Platform.isAndroid && !await _isAndroid13OrAbove()) {
|
||||
hasPermissions = await _requestAndroidPreS();
|
||||
} else {
|
||||
hasPermissions = await _requestAllLocationPermissions();
|
||||
}
|
||||
} else {
|
||||
print('User did not agree to location usage');
|
||||
hasPermissions = false;
|
||||
SystemNavigator.pop();
|
||||
}
|
||||
}
|
||||
|
||||
_isRequestingPermission = false;
|
||||
_permissionCompleter!.complete(hasPermissions);
|
||||
} catch (e) {
|
||||
print('Error in permission request: $e');
|
||||
_isRequestingPermission = false;
|
||||
_permissionCompleter!.complete(false);
|
||||
}
|
||||
|
||||
return _permissionCompleter!.future;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
static Future<bool> _requestAllLocationPermissions() async {
|
||||
await Permission.location.request();
|
||||
await Permission.locationWhenInUse.request();
|
||||
final alwaysStatus = await Permission.locationAlways.request();
|
||||
|
||||
return alwaysStatus == PermissionStatus.granted;
|
||||
}
|
||||
|
||||
static Future<bool> _requestAndroidPreS() async {
|
||||
await Permission.location.request();
|
||||
await Permission.locationWhenInUse.request();
|
||||
|
||||
// Android 13以前では、ユーザーに設定画面で権限を許可するように促す
|
||||
await showDialog(
|
||||
context: Get.context!,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('バックグラウンド位置情報の許可'),
|
||||
content: Text('アプリの設定画面で「常に許可」を選択してください。'),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('設定を開く'),
|
||||
onPressed: () {
|
||||
openAppSettings();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
// 設定画面から戻ってきた後、再度権限をチェック
|
||||
return await Permission.locationAlways.isGranted;
|
||||
}
|
||||
|
||||
static Future<bool> _isAndroid13OrAbove() async {
|
||||
if (Platform.isAndroid) {
|
||||
final androidVersion = int.tryParse(Platform.operatingSystemVersion.split('.').first) ?? 0;
|
||||
return androidVersion >= 13;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static Future<bool> checkLocationPermissions_old() async {
|
||||
final locationPermission = await Permission.location.status;
|
||||
if (locationPermission.isDenied) {
|
||||
await showLocationDisclosure();
|
||||
final result = await Permission.location.request();
|
||||
if (result.isDenied) {
|
||||
await openAppSettings();
|
||||
}
|
||||
}
|
||||
|
||||
final whenInUsePermission = await Permission.locationWhenInUse.status;
|
||||
final alwaysPermission = await Permission.locationAlways.status;
|
||||
|
||||
return locationPermission == PermissionStatus.granted &&
|
||||
(whenInUsePermission == PermissionStatus.granted || alwaysPermission == PermissionStatus.granted);
|
||||
}
|
||||
|
||||
static Future<bool> checkAndRequestPermissions_old() async {
|
||||
if (_isRequestingPermission) {
|
||||
return _permissionCompleter!.future;
|
||||
}
|
||||
|
||||
_isRequestingPermission = true;
|
||||
_permissionCompleter = Completer<bool>();
|
||||
|
||||
bool hasPermissions = await _checkLocationPermissions();
|
||||
if (!hasPermissions) {
|
||||
bool userAgreed = await showLocationDisclosure();
|
||||
if (userAgreed) {
|
||||
try {
|
||||
await requestAllLocationPermissions();
|
||||
hasPermissions = await checkLocationPermissions();
|
||||
hasPermissions = await _checkLocationPermissions();
|
||||
} catch (e) {
|
||||
print('Error requesting location permissions: $e');
|
||||
hasPermissions = false;
|
||||
@ -48,6 +147,8 @@ class PermissionController {
|
||||
|
||||
_isRequestingPermission = false;
|
||||
_permissionCompleter!.complete(hasPermissions);
|
||||
|
||||
debugPrint("Finish checkAndRequestPermissions...");
|
||||
return _permissionCompleter!.future;
|
||||
}
|
||||
|
||||
@ -67,35 +168,51 @@ class PermissionController {
|
||||
}
|
||||
|
||||
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('同意しない場合には、アプリは終了します。'),
|
||||
],
|
||||
if (Get.context == null) {
|
||||
print('Context is null, cannot show dialog');
|
||||
return false;
|
||||
}
|
||||
if (Get.isDialogOpen ?? false) {
|
||||
print('A dialog is already open');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
final result = 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: () => Get.back(result: false),
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('同意する'),
|
||||
onPressed: () => Get.back(result: true),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: const Text('同意しない'),
|
||||
onPressed: () => Get.back(result: false),
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('同意する'),
|
||||
onPressed: () => Get.back(result: true),
|
||||
),
|
||||
],
|
||||
),
|
||||
barrierDismissible: false,
|
||||
) ?? false;
|
||||
barrierDismissible: false,
|
||||
);
|
||||
return result ?? false;
|
||||
}catch(e){
|
||||
print('Dialog error: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void showPermissionDeniedDialog(String title,String message) {
|
||||
|
||||
@ -2,6 +2,8 @@ import 'package:get/get.dart';
|
||||
import 'package:gifunavi/pages/team/member_controller.dart';
|
||||
import 'package:gifunavi/services/api_service.dart';
|
||||
|
||||
import '../index/index_controller.dart';
|
||||
|
||||
class MemberBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
|
||||
@ -2,10 +2,15 @@ import 'package:get/get.dart';
|
||||
import 'package:gifunavi/pages/team/team_controller.dart';
|
||||
import 'package:gifunavi/services/api_service.dart';
|
||||
|
||||
//import '../entry/entry_controller.dart';
|
||||
import '../index/index_controller.dart';
|
||||
|
||||
class TeamBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<ApiService>(() => ApiService());
|
||||
Get.lazyPut<TeamController>(() => TeamController());
|
||||
Get.lazyPut<TeamController>(() => TeamController(
|
||||
apiService:Get.find<ApiService>())
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -15,8 +15,14 @@ import 'package:gifunavi/model/event.dart';
|
||||
|
||||
|
||||
class TeamController extends GetxController {
|
||||
late final ApiService _apiService;
|
||||
late final EntryController _entryController;
|
||||
final ApiService _apiService;
|
||||
//final EntryController _entryController;
|
||||
|
||||
TeamController({
|
||||
required ApiService apiService,
|
||||
//required EntryController entryController,
|
||||
}) : _apiService = apiService;
|
||||
//_entryController = entryController;
|
||||
|
||||
final teams = <Team>[].obs;
|
||||
final categories = <NewCategory>[].obs;
|
||||
@ -35,12 +41,12 @@ class TeamController extends GetxController {
|
||||
void onInit() async {
|
||||
super.onInit();
|
||||
try {
|
||||
_apiService = Get.find<ApiService>();
|
||||
//_apiService = Get.find<ApiService>();
|
||||
|
||||
if (!Get.isRegistered<EntryController>()) {
|
||||
Get.put(EntryController());
|
||||
}
|
||||
_entryController = Get.find<EntryController>();
|
||||
//if (!Get.isRegistered<EntryController>()) {
|
||||
// Get.put(EntryController());
|
||||
//}
|
||||
//_entryController = Get.find<EntryController>();
|
||||
|
||||
await loadInitialData();
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user