20240903 pre release

This commit is contained in:
2024-09-03 22:17:09 +09:00
parent fe46d46ab6
commit 2c0bb06e74
44 changed files with 610 additions and 154 deletions

View File

@ -43,6 +43,8 @@ import 'package:gifunavi/provider/cached_tile_provider.dart';
import 'package:timezone/timezone.dart' as tz;
import 'package:gifunavi/services/motion_service.dart';
Map<String, dynamic> deviceInfo = {};
/*
@ -70,7 +72,9 @@ Future<void> saveGameState() async {
if(indexController.currentUser[0]["user"]["event_date"]!=null) {
final date = indexController.currentUser[0]["user"]["event_date"];
pref.setString('eventDate', date.toIso8601String());
debugPrint("Saved date is ${date} => ${date.toIso8601String()}");
pref.setString('eventCode', indexController.currentUser[0]["user"]["event_code"]);
pref.setString('teamName', indexController.currentUser[0]["user"]["team_name"]);
pref.setString('group', indexController.currentUser[0]["user"]["group"]);
@ -197,6 +201,11 @@ void _showEventSelectionWarning() {
void main() async {
WidgetsFlutterBinding.ensureInitialized();
if (Platform.isIOS) {
// アプリの起動時にモーション更新を開始
await MotionService.startMotionUpdates();
}
final IndexController _indexController;
FlutterError.onError = (FlutterErrorDetails details) {
@ -207,20 +216,7 @@ void main() async {
};
try {
//await Get.putAsync(() => ApiService().init());
await _initApiService();
debugPrint("1: start ApiService");
// すべてのコントローラーとサービスを非同期で初期化
Get.lazyPut(() => IndexController(apiService: Get.find<ApiService>()));
debugPrint("2: start IndexController");
// その他のコントローラーを遅延初期化
Get.lazyPut(() => SettingsController());
debugPrint("2: start SettingsController");
Get.lazyPut(() => DestinationController());
debugPrint("3: start DestinationController");
await initServices();
@ -237,12 +233,37 @@ void main() async {
Future<void> initServices() async {
print('Starting services ...');
try {
//await Get.putAsync(() => ApiService().init());
await _initApiService();
debugPrint("1: start ApiService");
// コントローラーを初期化
Get.put(IndexController(apiService: Get.find<ApiService>()), permanent: true);
Get.put(SettingsController(), permanent: true);
Get.put(DestinationController(), permanent: true);
Get.put(LocationController(), permanent: true);
debugPrint("2: Controllers initialized");
/*
// すべてのコントローラーとサービスを非同期で初期化
Get.lazyPut(() => IndexController(apiService: Get.find<ApiService>()));
debugPrint("2: start IndexController");
// その他のコントローラーを遅延初期化
Get.lazyPut(() => SettingsController());
debugPrint("2: start SettingsController");
Get.lazyPut(() => DestinationController());
debugPrint("3: start DestinationController");
Get.lazyPut(() => LocationController());
debugPrint("4: start LocationController");
*/
// 非同期処理を並列実行
await Future.wait([
_initTimeZone(),
_initCacheProvider(),
]);
print('=== 5. Initialized TimeZone...');
print('=== 6. CacheProvider started...');
@ -520,6 +541,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
late final DestinationController _destinationController;
late final PermissionController _permissionController;
Timer? _memoryCheckTimer;
bool _isControllerInitialized = false;
@override
void initState() {
@ -545,24 +567,94 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
// ここに他の初期化処理を追加できます
}
void _initializeControllers() {
Future <void> _initializeControllers() async {
while (!Get.isRegistered<LocationController>() ||
!Get.isRegistered<IndexController>() ||
!Get.isRegistered<DestinationController>() ||
!Get.isRegistered<PermissionController>()) {
await Future.delayed(const Duration(milliseconds: 100));
}
if (!_isControllerInitialized) {
_locationController = Get.find<LocationController>();
_indexController = Get.find<IndexController>();
_destinationController = Get.find<DestinationController>();
_permissionController = Get.find<PermissionController>();
_isControllerInitialized = true;
}
/*
if (!Get.isRegistered<IndexController>()) {
while (true) {
try {
_locationController = Get.find<LocationController>();
break; // DestinationControllerが見つかったらループを抜ける
} catch (e) {
// DestinationControllerがまだ利用可能でない場合は少し待ってから再試行
await Future.delayed(const Duration(milliseconds: 100));
}
}
*/
/*
if (!Get.isRegistered<LocationController>()) {
_locationController = Get.put(LocationController(), permanent: true);
}
*/
/*
while (true) {
try {
_indexController = Get.find<IndexController>();
break; // DestinationControllerが見つかったらループを抜ける
} catch (e) {
// DestinationControllerがまだ利用可能でない場合は少し待ってから再試行
await Future.delayed(const Duration(milliseconds: 100));
}
}
*/
/*
if (!Get.isRegistered<IndexController>()) {
_indexController = Get.put(IndexController(apiService: Get.find<ApiService>()), permanent: true);
}
*/
/*
while (true) {
try {
_destinationController = Get.find<DestinationController>();
break; // DestinationControllerが見つかったらループを抜ける
} catch (e) {
// DestinationControllerがまだ利用可能でない場合は少し待ってから再試行
await Future.delayed(const Duration(milliseconds: 100));
}
}
*/
/*
if (!Get.isRegistered<DestinationController>()) {
_destinationController =
Get.put(DestinationController(), permanent: true);
}
*/
/*
while (true) {
try {
_permissionController = Get.find<PermissionController>();
break; // DestinationControllerが見つかったらループを抜ける
} catch (e) {
// DestinationControllerがまだ利用可能でない場合は少し待ってから再試行
await Future.delayed(const Duration(milliseconds: 100));
}
}
*/
/*
if (!Get.isRegistered<PermissionController>()) {
_permissionController = Get.put(PermissionController());
}
*/
// 他の必要なコントローラーの初期化
*/
}
void _startMemoryMonitoring() {
@ -677,6 +769,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
@override
void dispose() {
_isControllerInitialized = false;
WidgetsBinding.instance.removeObserver(this);
_memoryCheckTimer?.cancel();
super.dispose();
@ -704,6 +797,9 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
switch (state) {
case AppLifecycleState.resumed:
if (Platform.isIOS) {
MotionService.startMotionUpdates();
}
//await _onResumed();
await _onResumed();
break;
@ -714,6 +810,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
break;
case AppLifecycleState.paused:
MotionService.stopMotionUpdates();
// バックグラウンドに移行したときの処理
//locationController.resumePositionStream();
await _onPaused();
@ -740,7 +837,9 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
Future<void> _onResumed() async {
debugPrint("==(Status Changed)==> RESUMED");
try {
_initializeControllers();
if (!_isControllerInitialized) {
await _initializeControllers();
}
await stopBackgroundTracking();
_destinationController.restartGPS();

View File

@ -311,7 +311,7 @@ class CameraPage extends StatelessWidget {
? ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
onPressed: () async {
await destinationController.makeCheckin(destination, true,
await destinationController.makeCheckin(destination, true, // チェクインボタン
destinationController.photos[0].path);
if( destinationController.isInRog.value==true ) {
destinationController.rogainingCounted.value = true; // ロゲ開始後のみ許可
@ -526,7 +526,7 @@ class CameraPage extends StatelessWidget {
// print(
// "##### current destination ${indexController.currentDestinationFeature[0].sub_loc_id} #######");
await destinationController.makeCheckin(
await destinationController.makeCheckin( // チェックイン確定
indexController.currentDestinationFeature[0],
true,
destinationController.photos[0].path);
@ -551,7 +551,7 @@ class CameraPage extends StatelessWidget {
Navigator.of(context).pop(true); // ここを修正
},
child: const Text("チェックイン"))
child: const Text("チェックイン確定"))
: Container())
],
);

View File

@ -526,7 +526,7 @@ class DestinationController extends GetxController {
debugPrint("** 自動チェックインの場合");
//print(
// "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ make checkin ${d.sub_loc_id}@@@@@@@@@@@");
makeCheckin(d, true, ""); // チェックインして
makeCheckin(d, true, ""); // 自動チェックイン
//if (d.cp != -1 && d.cp != -2 && d.cp != 0 ) {
// rogainingCounted.value = true; // ゴール用チェックイン済み
//}
@ -750,9 +750,11 @@ class DestinationController extends GetxController {
}
int? latgoal = await db.latestGoal();
lastGoalAt = DateTime.fromMicrosecondsSinceEpoch(latgoal!);
debugPrint("===== last goal : $lastGoalAt =====");
if( latgoal != null ) {
lastGoalAt = DateTime.fromMicrosecondsSinceEpoch(latgoal!);
debugPrint("===== last goal : $lastGoalAt =====");
dbService.updateDatabase();
}
}
// すべての目的地を削除する関数です。
@ -835,7 +837,7 @@ class DestinationController extends GetxController {
print("---- f- checkin ${d.sub_loc_id} ----");
if (autoCheckin) {
if (!checkingIn) {
makeCheckin(d, true, "");
makeCheckin(d, true, ""); // callforCheckin
if (d.cp != -1 && d.cp != 0 && d.cp != -2) {
rogainingCounted.value = true;
}
@ -1085,12 +1087,13 @@ class DestinationController extends GetxController {
// ロゲイニングにデータを追加する関数です。
//
void addToRogaining(double lat, double lon, int destinationId) async {
debugPrint("addToRogaining .... ");
DatabaseHelper db = DatabaseHelper.instance;
List<Destination> d = await db.getDestinationById(destinationId);
if (d.isEmpty) {
Destination df = festuretoDestination(indexController.currentFeature[0]);
//print("--- made checkin ${df.location_id} ----");
makeCheckin(df, true, "");
makeCheckin(df, true, ""); // addToRogaining
}
isInRog.value = true;
@ -1138,10 +1141,10 @@ class DestinationController extends GetxController {
// ギャラリーにも保存
//await ImageGallerySaver.saveFile(savedImage.path);
await Future.delayed(const Duration(seconds: 3), () async {
await Future.delayed(const Duration(seconds: 5), () async {
final result = await ImageGallerySaver.saveFile(savedImage.path);
print("Save result: $result");
}).timeout(const Duration(seconds: 5));
}).timeout(const Duration(seconds: 7));
debugPrint('Image saved to: ${savedImage.path}');
@ -1214,14 +1217,16 @@ class DestinationController extends GetxController {
// print(
// "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ressssss ${destination.sub_loc_id}@@@@@@@@@@@");
DatabaseHelper db = DatabaseHelper.instance;
List<Destination> ddd =
await db.getDestinationByLatLon(destination.lat!, destination.lon!);
List<Destination> ddd = await db.getDestinationByLatLon(destination.lat!, destination.lon!);
if (ddd.isEmpty) {
destination.checkedin = true;
debugPrint("...近くにCPがないのにチェックイン...makeCheckin imageUrl = $imageurl");
if (imageurl.isNotEmpty) {
String? savedImagePath = await _saveImageToGallery(imageurl);
destination.checkin_image = savedImagePath ?? imageurl;
}else{
debugPrint("makeCheckin ... No image...");
}
await db.insertDestination(destination);
// print("~~~~ inserted into db ~~~~");
@ -1229,15 +1234,18 @@ class DestinationController extends GetxController {
if (imageurl.isEmpty) {
if (photos.isNotEmpty) {
debugPrint("imageurlが空の場合は、destinationのcheckin_imageプロパティを使用する");
// imageurlが空の場合は、destinationのcheckin_imageプロパティを使用する
debugPrint("photos = $photos");
imageurl = photos[0].path;
//debugPrint("photos = $photos");
//imageurl = photos[0].path;
}
debugPrint("imageurl = $imageurl");
//await _saveImageFromPath(imageurl!);
}
if (imageurl.isNotEmpty) {
debugPrint("...これでアルバム保存...makeCheckin imageUrl = $imageurl");
String? savedImagePath = await _saveImageToGallery(imageurl);
debugPrint("イメージのアルバム保存完了!!");
destination.checkin_image = savedImagePath ?? imageurl;
}

View File

@ -96,6 +96,8 @@ class IndexController extends GetxController with WidgetsBindingObserver {
final selectedEventName = 'add_location'.tr.obs;
RxBool isLoadingLocations = true.obs;
void setSelectedEventName(String eventName) {
selectedEventName.value = eventName;
}
@ -256,6 +258,9 @@ class IndexController extends GetxController with WidgetsBindingObserver {
tz.initializeTimeZones();
//teamController = Get.find<TeamController>();
loadLocations();
}catch(e,stacktrace){
print('Error in IndexController.onInit: $e');
print('Stack trace: $stacktrace');
@ -265,6 +270,20 @@ class IndexController extends GetxController with WidgetsBindingObserver {
}
}
Future<void> loadLocations() async {
isLoadingLocations.value = true;
try {
await waitForMapControllerReady();
String eventCode = currentUser.isNotEmpty ? currentUser[0]["user"]["event_code"] ?? "" : "";
await loadLocationsBound(eventCode);
} catch (e) {
print('Error loading locations: $e');
// エラーハンドリングを追加(例:スナックバーでユーザーに通知)
} finally {
isLoadingLocations.value = false;
}
}
void _updateConnectionStatus(List<ConnectivityResult> results) {
final result = results.isNotEmpty ? results.first : ConnectivityResult.none;
@ -873,7 +892,7 @@ class IndexController extends GetxController with WidgetsBindingObserver {
// 要検討Future.delayedを使用して非同期処理を待たずに先に進むようにしていますが、
// これによってメモリリークが発生する可能性があります。非同期処理の結果を適切に処理することを検討してください。
//
void loadLocationsBound(String eventCode) async {
Future<void> loadLocationsBound(String eventCode) async {
if (isCustomAreaSelected.value == true) {
return;
}
@ -881,6 +900,19 @@ class IndexController extends GetxController with WidgetsBindingObserver {
// MapControllerの初期化が完了するまで待機
await waitForMapControllerReady();
// null チェックを追加
if (mapController.bounds == null) {
print("MapController bounds are null");
return;
}
// バウンドが有効かどうかを確認する
LatLngBounds bounds = mapController.bounds!;
if (!_isValidBounds(bounds)) {
print("MapController bounds are not valid");
return;
}
locations.clear();
String cat = currentCat.isNotEmpty ? currentCat[0] : "";
if (currentCat.isNotEmpty && currentCat[0] == "-all-") {
@ -896,7 +928,7 @@ class IndexController extends GetxController with WidgetsBindingObserver {
//
*/
LatLngBounds bounds = mapController.bounds!;
//LatLngBounds bounds = mapController.bounds!;
currentBound.clear();
currentBound.add(bounds);
@ -987,6 +1019,15 @@ class IndexController extends GetxController with WidgetsBindingObserver {
}
// バウンドが有効かどうかを確認するヘルパーメソッド
bool _isValidBounds(LatLngBounds bounds) {
// 緯度と経度が有効な範囲内にあるかチェック
return bounds.southWest.latitude.abs() <= 90 &&
bounds.southWest.longitude.abs() <= 180 &&
bounds.northEast.latitude.abs() <= 90 &&
bounds.northEast.longitude.abs() <= 180 &&
bounds.southWest.latitude < bounds.northEast.latitude;
}
//===Akira 追加:2024-4-6 #2800
// 要検討MapControllerの初期化が完了するまで待機していますが、タイムアウトを設定することを検討してください。

View File

@ -8,6 +8,8 @@ import 'package:gifunavi/widgets/list_widget.dart';
import 'package:gifunavi/widgets/map_widget.dart';
import 'package:gifunavi/utils/location_controller.dart';
import '../permission/permission.dart';
// index_page.dartファイルの主な内容です。
// このファイルは、アプリのメインページのUIを構築し、各機能へのナビゲーションを提供しています。
// また、IndexControllerとDestinationControllerを使用して、状態管理と各種機能の実装を行っています。
@ -36,12 +38,20 @@ class _IndexPageState extends State<IndexPage> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
//checkLoginAndShowDialog();
//checkEventAndNavigate();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await _ensureControllersAreInitialized();
await PermissionController.checkAndRequestPermissions();
});
}
Future<void> _ensureControllersAreInitialized() async {
while (!Get.isRegistered<IndexController>() ||
!Get.isRegistered<DestinationController>() ||
!Get.isRegistered<LocationController>()) {
await Future.delayed(const Duration(milliseconds: 100));
}
}
void checkEventAndNavigate() async {
if (indexController.currentUser.isNotEmpty &&
indexController.currentUser[0]["user"]["event_code"] == null) {

View File

@ -126,28 +126,27 @@ class PermissionController {
_isRequestingPermission = true;
_permissionCompleter = Completer<bool>();
bool hasPermissions = await _checkLocationPermissions();
if (!hasPermissions) {
bool userAgreed = await showLocationDisclosure();
if (userAgreed) {
try {
await requestAllLocationPermissions();
hasPermissions = await _checkLocationPermissions();
} catch (e) {
print('Error requesting location permissions: $e');
try {
bool hasPermissions = await _checkLocationPermissions();
if (!hasPermissions) {
bool userAgreed = await showLocationDisclosure();
if (userAgreed) {
hasPermissions = await _requestAllLocationPermissions();
} else {
print('User did not agree to location usage');
hasPermissions = false;
// アプリを終了
SystemNavigator.pop();
}
} 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);
}
_isRequestingPermission = false;
_permissionCompleter!.complete(hasPermissions);
debugPrint("Finish checkAndRequestPermissions...");
return _permissionCompleter!.future;
}

View File

@ -108,7 +108,22 @@ class AuthService {
if (response.statusCode == 200) {
cats = json.decode(utf8.decode(response.bodyBytes));
} else {
debugPrint('Response body: ${response.body}');
print('Login failed with status code: ${response.statusCode}');
var errorMessage = 'ログインに失敗しました。';
if (response.statusCode == 400) {
var errorBody = json.decode(utf8.decode(response.bodyBytes));
errorMessage = errorBody['non_field_errors']?[0] ?? 'パスワードが正しくありません。';
}
Get.snackbar(
"エラー",
errorMessage,
backgroundColor: Colors.red,
colorText: Colors.white,
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
);
cats = {};
}
} catch( e ){

View File

@ -80,7 +80,7 @@ class ExternalService {
<String, String>{'team_name': team, 'event_code': eventCode}),
);
//print("---- start rogianing api status ---- ${response.statusCode}");
print("---- start rogianing api status ---- ${response.statusCode}");
if (response.statusCode == 200) {
res = json.decode(utf8.decode(response.bodyBytes));

View File

@ -0,0 +1,29 @@
import 'package:flutter/services.dart';
import 'dart:io';
class MotionService {
static const MethodChannel _channel = MethodChannel('net.sumasen.gifunavi/motion');
static Future<void> startMotionUpdates() async {
if (Platform.isIOS) {
try {
await _channel.invokeMethod('startMotionUpdates');
} on PlatformException catch (e) {
print("Failed to start motion updates: '${e.message}'.");
}
} else{
// Android の場合は何もしない、またはAndroid向けの代替実装を行う
print("Motion updates not supported on this platform");
}
}
static Future<void> stopMotionUpdates() async {
if (Platform.isIOS) {
try {
await _channel.invokeMethod('stopMotionUpdates');
} on PlatformException catch (e) {
print("Failed to stop motion updates: '${e.message}'.");
}
}
}
}

View File

@ -139,16 +139,23 @@ class BottomSheetNew extends GetView<BottomSheetController> {
}
Future<void> saveTemporaryImage(Destination destination) async {
final serverUrl = ConstValues.currentServer();
final imagePath = '$serverUrl/media/compressed/${destination.photos}';
try {
final serverUrl = ConstValues.currentServer();
final imagePath = '$serverUrl/media/compressed/${destination.photos}';
final tempDir = await getTemporaryDirectory();
final tempFile = await File('${tempDir.path}/temp_image.jpg').create(recursive: true);
final response = await http.get(Uri.parse(imagePath));
await tempFile.writeAsBytes(response.bodyBytes);
debugPrint("imagePath = $imagePath");
destinationController.photos.clear();
destinationController.photos.add(tempFile);
final tempDir = await getTemporaryDirectory();
final tempFile = await File('${tempDir.path}/temp_image.jpg').create(
recursive: true);
final response = await http.get(Uri.parse(imagePath));
await tempFile.writeAsBytes(response.bodyBytes);
destinationController.photos.clear();
destinationController.photos.add(tempFile);
} catch( e ) {
debugPrint("saveTemporaryImage error : $e");
}
}
// アクションボタン(チェックイン、ゴールなど)を表示するためのメソッドです。
@ -239,12 +246,8 @@ class BottomSheetNew extends GetView<BottomSheetController> {
return;
}
destinationController.isInRog.value = true;
// Show confirmation dialog
Get.dialog(
AlertDialog(
@ -270,6 +273,7 @@ class BottomSheetNew extends GetView<BottomSheetController> {
// Clear data and start game logic here
destinationController.resetRogaining();
// ここでチェックインもしている。
destinationController.addToRogaining(
destinationController.currentLat,
destinationController.currentLon,
@ -996,15 +1000,12 @@ class BottomSheetNew extends GetView<BottomSheetController> {
Obx((() => indexController.rogMode.value == 1
? ElevatedButton(
onPressed: () async {
Destination dest =
indexController.currentDestinationFeature[0];
//print("~~~~ before checking button ~~~~");
Destination dest = indexController.currentDestinationFeature[0];
print("~~~~ before checking button ~~~~");
//print("------ curent destination is ${dest!.checkedIn}-------");
destinationController.makeCheckin(
dest, !dest.checkedin!, "");
destinationController.makeCheckin( dest, !dest.checkedin!, "");
},
child: indexController
.currentDestinationFeature[0].checkedin ==
child: indexController.currentDestinationFeature[0].checkedin ==
false
? const Text("チェックイン")
: const Text("チェックアウト"))

View File

@ -140,12 +140,25 @@ class _MapWidgetState extends State<MapWidget> with WidgetsBindingObserver {
*/
// _centerMapOnUser を10秒間でコール
void _startIdleTimer() {
Future<void> _startIdleTimer() async {
//debugPrint("_startIdleTimer ....");
final settingsController = Get.find<SettingsController>();
SettingsController settingsController;
// SettingsControllerが利用可能になるまで待機
while (true) {
try {
settingsController = Get.find<SettingsController>();
break; // SettingsControllerが見つかったらループを抜ける
} catch (e) {
// SettingsControllerがまだ利用可能でない場合は少し待ってから再試行
await Future.delayed(const Duration(milliseconds: 100));
}
}
if (!settingsController.autoReturnDisabled.value) {
_timer = Timer(settingsController.timerDuration.value, _centerMapOnUser);
}
}
// タイマーをリセットして_startIdleTimer をコール
@ -173,7 +186,16 @@ class _MapWidgetState extends State<MapWidget> with WidgetsBindingObserver {
Future<List<Marker>> _getMarkers() async {
debugPrint('Getting markers...');
List<Marker> markers = [];
if (indexController.isLoadingLocations.value) {
await Future.doWhile(() async {
await Future.delayed(const Duration(milliseconds: 100));
return indexController.isLoadingLocations.value;
});
}
debugPrint('Getting markers...');
List<Marker> markers = [];
if (indexController.locations.isNotEmpty && indexController.locations[0].features.isNotEmpty) {
for (var feature in indexController.locations[0].features) {
GeoJSONMultiPoint point = feature!.geometry as GeoJSONMultiPoint;