ほぼ完成:QRcodeトライ
This commit is contained in:
2482
all_combined.txt
2482
all_combined.txt
File diff suppressed because it is too large
Load Diff
@ -11,9 +11,11 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- flutter_keyboard_visibility (0.0.1):
|
- flutter_keyboard_visibility (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FMDB (2.7.10):
|
- FMDB (2.7.12):
|
||||||
- FMDB/standard (= 2.7.10)
|
- FMDB/standard (= 2.7.12)
|
||||||
- FMDB/standard (2.7.10)
|
- FMDB/Core (2.7.12)
|
||||||
|
- FMDB/standard (2.7.12):
|
||||||
|
- FMDB/Core
|
||||||
- geolocator_apple (1.2.0):
|
- geolocator_apple (1.2.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- image_gallery_saver (2.0.2):
|
- image_gallery_saver (2.0.2):
|
||||||
@ -35,7 +37,7 @@ PODS:
|
|||||||
- qr_code_scanner (0.2.0):
|
- qr_code_scanner (0.2.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- MTBBarcodeScanner
|
- MTBBarcodeScanner
|
||||||
- ReachabilitySwift (5.2.1)
|
- ReachabilitySwift (5.2.3)
|
||||||
- shared_preferences_foundation (0.0.1):
|
- shared_preferences_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
@ -121,7 +123,7 @@ SPEC CHECKSUMS:
|
|||||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||||
flutter_compass: cbbd285cea1584c7ac9c4e0c3e1f17cbea55e855
|
flutter_compass: cbbd285cea1584c7ac9c4e0c3e1f17cbea55e855
|
||||||
flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069
|
flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069
|
||||||
FMDB: eae540775bf7d0c87a5af926ae37af69effe5a19
|
FMDB: 728731dd336af3936ce00f91d9d8495f5718a0e6
|
||||||
geolocator_apple: 9157311f654584b9bb72686c55fc02a97b73f461
|
geolocator_apple: 9157311f654584b9bb72686c55fc02a97b73f461
|
||||||
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
|
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
|
||||||
image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425
|
image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425
|
||||||
@ -132,7 +134,7 @@ SPEC CHECKSUMS:
|
|||||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||||
pointer_interceptor_ios: 9280618c0b2eeb80081a343924aa8ad756c21375
|
pointer_interceptor_ios: 9280618c0b2eeb80081a343924aa8ad756c21375
|
||||||
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
|
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
|
||||||
ReachabilitySwift: 5ae15e16814b5f9ef568963fb2c87aeb49158c66
|
ReachabilitySwift: 7f151ff156cea1481a8411701195ac6a984f4979
|
||||||
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
|
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
|
||||||
sqflite: 50a33e1d72bd59ee092a519a35d107502757ebed
|
sqflite: 50a33e1d72bd59ee092a519a35d107502757ebed
|
||||||
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
|
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 54;
|
objectVersion = 60;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
@ -398,11 +398,11 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 480;
|
CURRENT_PROJECT_VERSION = 486;
|
||||||
DEVELOPMENT_TEAM = UMNEWT25JR;
|
DEVELOPMENT_TEAM = UMNEWT25JR;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FLUTTER_BUILD_NAME = 4.8.0;
|
FLUTTER_BUILD_NAME = 4.8.6;
|
||||||
FLUTTER_BUILD_NUMBER = 480;
|
FLUTTER_BUILD_NUMBER = 486;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "岐阜ナビ";
|
INFOPLIST_KEY_CFBundleDisplayName = "岐阜ナビ";
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.healthcare-fitness";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.healthcare-fitness";
|
||||||
@ -411,7 +411,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 4.8.0;
|
MARKETING_VERSION = 4.8.6;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.dvox.gifunavi;
|
PRODUCT_BUNDLE_IDENTIFIER = com.dvox.gifunavi;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
@ -539,11 +539,11 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 480;
|
CURRENT_PROJECT_VERSION = 486;
|
||||||
DEVELOPMENT_TEAM = UMNEWT25JR;
|
DEVELOPMENT_TEAM = UMNEWT25JR;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FLUTTER_BUILD_NAME = 4.8.0;
|
FLUTTER_BUILD_NAME = 4.8.6;
|
||||||
FLUTTER_BUILD_NUMBER = 480;
|
FLUTTER_BUILD_NUMBER = 486;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "岐阜ナビ";
|
INFOPLIST_KEY_CFBundleDisplayName = "岐阜ナビ";
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.healthcare-fitness";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.healthcare-fitness";
|
||||||
@ -552,7 +552,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 4.8.0;
|
MARKETING_VERSION = 4.8.6;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.dvox.gifunavi;
|
PRODUCT_BUNDLE_IDENTIFIER = com.dvox.gifunavi;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
@ -571,11 +571,11 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 480;
|
CURRENT_PROJECT_VERSION = 486;
|
||||||
DEVELOPMENT_TEAM = UMNEWT25JR;
|
DEVELOPMENT_TEAM = UMNEWT25JR;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
FLUTTER_BUILD_NAME = 4.8.0;
|
FLUTTER_BUILD_NAME = 4.8.6;
|
||||||
FLUTTER_BUILD_NUMBER = 480;
|
FLUTTER_BUILD_NUMBER = 486;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "岐阜ナビ";
|
INFOPLIST_KEY_CFBundleDisplayName = "岐阜ナビ";
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.healthcare-fitness";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.healthcare-fitness";
|
||||||
@ -584,7 +584,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 4.8.0;
|
MARKETING_VERSION = 4.8.6;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.dvox.gifunavi;
|
PRODUCT_BUNDLE_IDENTIFIER = com.dvox.gifunavi;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert'; // この行を追加または確認
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@ -10,6 +11,9 @@ import 'package:rogapp/services/external_service.dart';
|
|||||||
import 'package:rogapp/utils/const.dart';
|
import 'package:rogapp/utils/const.dart';
|
||||||
import 'package:qr_code_scanner/qr_code_scanner.dart';
|
import 'package:qr_code_scanner/qr_code_scanner.dart';
|
||||||
|
|
||||||
|
import 'package:http/http.dart' as http; // この行を追加
|
||||||
|
|
||||||
|
|
||||||
// 関数 getTagText は、特定の条件に基づいて文字列から特定の部分を抽出し、返却するためのものです。
|
// 関数 getTagText は、特定の条件に基づいて文字列から特定の部分を抽出し、返却するためのものです。
|
||||||
// 関数は2つのパラメータを受け取り、条件分岐を通じて結果を返します。
|
// 関数は2つのパラメータを受け取り、条件分岐を通じて結果を返します。
|
||||||
//
|
//
|
||||||
@ -613,235 +617,108 @@ class StartRogaining extends StatelessWidget {
|
|||||||
// 完了ボタンをタップすると、購入ポイントの処理が行われます。
|
// 完了ボタンをタップすると、購入ポイントの処理が行われます。
|
||||||
// 購入なしボタンをタップすると、購入ポイントがキャンセルされます。
|
// 購入なしボタンをタップすると、購入ポイントがキャンセルされます。
|
||||||
//
|
//
|
||||||
class BuyPointCamera extends StatelessWidget {
|
class BuyPointCamera extends StatefulWidget {
|
||||||
BuyPointCamera({Key? key, required this.destination}) : super(key: key);
|
final Destination destination;
|
||||||
|
|
||||||
Destination destination;
|
const BuyPointCamera({Key? key, required this.destination}) : super(key: key);
|
||||||
|
|
||||||
DestinationController destinationController =
|
|
||||||
Get.find<DestinationController>();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
_BuyPointCameraState createState() => _BuyPointCameraState();
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
automaticallyImplyLeading: false,
|
|
||||||
title: Text(
|
|
||||||
"${destination.sub_loc_id} : ${destination.name}",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Center(
|
|
||||||
child: Obx(
|
|
||||||
() => Container(
|
|
||||||
width: MediaQuery.of(context).size.width,
|
|
||||||
height: 370,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
image: DecorationImage(
|
|
||||||
// 要修正:getReceiptImage関数の戻り値がnullの場合のエラーハンドリングが不十分です。適切なデフォルト画像を表示するなどの処理を追加してください。
|
|
||||||
//
|
|
||||||
image: getReceiptImage(), fit: BoxFit.cover)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(getTagText(true, destination.tags)),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
|
||||||
child: Wrap(
|
|
||||||
spacing: 16.0,
|
|
||||||
runSpacing: 8.0,
|
|
||||||
children: [
|
|
||||||
Obx(() => ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
destinationController.openCamera(context, destination);
|
|
||||||
},
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
shape: const CircleBorder(),
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
backgroundColor: destinationController.photos.isEmpty
|
|
||||||
? Colors.red
|
|
||||||
: Colors.grey[300],
|
|
||||||
),
|
|
||||||
child: destinationController.photos.isEmpty
|
|
||||||
? const Text("撮影",
|
|
||||||
style: TextStyle(color: Colors.white))
|
|
||||||
: const Text("再撮影",
|
|
||||||
style: TextStyle(color: Colors.black)),
|
|
||||||
)),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await destinationController.cancelBuyPoint(destination);
|
|
||||||
Navigator.of(Get.context!).pop();
|
|
||||||
destinationController.rogainingCounted.value = true;
|
|
||||||
destinationController.skipGps = false;
|
|
||||||
destinationController.isPhotoShoot.value = false;
|
|
||||||
},
|
|
||||||
child: const Text("買い物なし")),
|
|
||||||
Obx(() => destinationController.photos.isNotEmpty
|
|
||||||
? ElevatedButton(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: Colors.red),
|
|
||||||
onPressed: () async {
|
|
||||||
await destinationController.makeBuyPoint(
|
|
||||||
destination,
|
|
||||||
destinationController.photos[0].path);
|
|
||||||
Get.back();
|
|
||||||
destinationController.rogainingCounted.value = true;
|
|
||||||
destinationController.skipGps = false;
|
|
||||||
destinationController.isPhotoShoot.value = false;
|
|
||||||
Get.snackbar("お買い物加点を行いました",
|
|
||||||
"${destination.sub_loc_id} : ${destination.name}",
|
|
||||||
backgroundColor: Colors.green,
|
|
||||||
colorText: Colors.white);
|
|
||||||
},
|
|
||||||
child: const Text("完了",
|
|
||||||
style: TextStyle(color: Colors.white)))
|
|
||||||
: Container())
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class _BuyPointCameraState extends State<BuyPointCamera> {
|
||||||
|
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
|
||||||
|
QRViewController? controller;
|
||||||
|
bool isQRScanned = false;
|
||||||
|
|
||||||
/*
|
final DestinationController destinationController = Get.find<DestinationController>();
|
||||||
class BuyPointCamera extends StatelessWidget {
|
|
||||||
BuyPointCamera({Key? key, required this.destination}) : super(key: key);
|
|
||||||
|
|
||||||
Destination destination;
|
|
||||||
|
|
||||||
DestinationController destinationController =
|
|
||||||
Get.find<DestinationController>();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
//print("in camera purchase 1 ${destinationController.isInRog.value}");
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
automaticallyImplyLeading: false,
|
automaticallyImplyLeading: false,
|
||||||
title: Text(
|
title: Text("${widget.destination.sub_loc_id} : ${widget.destination.name}"),
|
||||||
"${destination.sub_loc_id} : ${destination.name}",
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Expanded(
|
||||||
padding: const EdgeInsets.all(8.0),
|
flex: 4,
|
||||||
|
child: QRView(
|
||||||
|
key: qrKey,
|
||||||
|
onQRViewCreated: _onQRViewCreated,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Obx(
|
child: Text('QRコードをスキャンしてください'),
|
||||||
() => Container(
|
|
||||||
width: MediaQuery.of(context).size.width,
|
|
||||||
height: 370,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
image: DecorationImage(
|
|
||||||
// 要修正:getReceiptImage関数の戻り値がnullの場合のエラーハンドリングが不十分です。適切なデフォルト画像を表示するなどの処理を追加してください。
|
|
||||||
//
|
|
||||||
image: getReceiptImage(), fit: BoxFit.cover)),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(getTagText(true, destination.tags)),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Obx(() => Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
// print(
|
|
||||||
// "in camera purchase 2 ${destinationController.isInRog.value}");
|
|
||||||
destinationController.openCamera(
|
|
||||||
context, destination);
|
|
||||||
},
|
|
||||||
child: destinationController.photos.isNotEmpty
|
|
||||||
? const Text("再撮影")
|
|
||||||
: const Text("撮影")),
|
|
||||||
const SizedBox(
|
|
||||||
width: 10,
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await destinationController
|
|
||||||
.cancelBuyPoint(destination);
|
|
||||||
Navigator.of(Get.context!).pop();
|
|
||||||
//Get.back();
|
|
||||||
destinationController.rogainingCounted.value = true;
|
|
||||||
destinationController.skipGps = false;
|
|
||||||
destinationController.isPhotoShoot.value = false;
|
|
||||||
},
|
|
||||||
child: const Text("買い物なし"))
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
Obx(() => destinationController.photos.isNotEmpty
|
|
||||||
? Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
// ElevatedButton(
|
|
||||||
// style: ElevatedButton.styleFrom(
|
|
||||||
// backgroundColor: Colors.red),
|
|
||||||
// onPressed: () async {},
|
|
||||||
// child: const Text("買物なし")),
|
|
||||||
// const SizedBox(
|
|
||||||
// width: 10,
|
|
||||||
// ),
|
|
||||||
ElevatedButton(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: Colors.red),
|
|
||||||
onPressed: () async {
|
|
||||||
// print(
|
|
||||||
// "in camera purchase 3 ${destinationController.isInRog.value}");
|
|
||||||
await destinationController.makeBuyPoint(
|
|
||||||
destination,
|
|
||||||
destinationController.photos[0].path);
|
|
||||||
Get.back();
|
|
||||||
// print(
|
|
||||||
// "in camera purchase 4 ${destinationController.isInRog.value}");
|
|
||||||
destinationController.rogainingCounted.value =
|
|
||||||
true;
|
|
||||||
destinationController.skipGps = false;
|
|
||||||
destinationController.isPhotoShoot.value = false;
|
|
||||||
Get.snackbar("お買い物加点を行いました",
|
|
||||||
"${destination.sub_loc_id} : ${destination.name}",
|
|
||||||
backgroundColor: Colors.green,
|
|
||||||
colorText: Colors.white
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: const Text("完了"))
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: Container())
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onQRViewCreated(QRViewController controller) {
|
||||||
|
this.controller = controller;
|
||||||
|
controller.scannedDataStream.listen((scanData) {
|
||||||
|
if (!isQRScanned && scanData.code != null && scanData.code!.startsWith('https://rogaining.sumasen.net/api/activate_buy_point/')) {
|
||||||
|
isQRScanned = true;
|
||||||
|
_activateBuyPoint(scanData.code!);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _activateBuyPoint(String qrCode) async {
|
||||||
|
final IndexController indexController = Get.find<IndexController>();
|
||||||
|
|
||||||
|
final userId = indexController.currentUser[0]["user"]["id"];
|
||||||
|
final token = indexController.currentUser[0]["token"];
|
||||||
|
final teamName = indexController.currentUser[0]["user"]['team_name'];
|
||||||
|
final eventCode = indexController.currentUser[0]["user"]["event_code"];
|
||||||
|
//final cpNumber = destinationController.currentDestinationFeature[0].cp;
|
||||||
|
final cpNumber = widget.destination.cp;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await http.post(
|
||||||
|
Uri.parse('https://rogaining.sumasen.net/api/activate_buy_point/'),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': 'Token $token',
|
||||||
|
},
|
||||||
|
body: jsonEncode({
|
||||||
|
'user_id': userId,
|
||||||
|
'team_name': teamName,
|
||||||
|
'event_code': eventCode,
|
||||||
|
'cp_number': cpNumber,
|
||||||
|
'qr_code': qrCode,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
Get.snackbar('成功', 'お買い物ポイントが有効化されました');
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
} else {
|
||||||
|
Get.snackbar('エラー', 'お買い物ポイントの有効化に失敗しました');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Get.snackbar('エラー', 'ネットワークエラーが発生しました');
|
||||||
|
} finally {
|
||||||
|
isQRScanned = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
controller?.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
class QRCodeScannerPage extends StatefulWidget {
|
class QRCodeScannerPage extends StatefulWidget {
|
||||||
|
|
||||||
|
|||||||
@ -1028,7 +1028,7 @@ class DestinationController extends GetxController {
|
|||||||
print("An error occurred: $e");
|
print("An error occurred: $e");
|
||||||
// await checkForCheckin();
|
// await checkForCheckin();
|
||||||
} finally {
|
} finally {
|
||||||
await Future.delayed(const Duration(seconds: 5)); // 一定時間待機してから再帰呼び出し
|
await Future.delayed(const Duration(seconds: 1)); // 一定時間待機してから再帰呼び出し
|
||||||
//print("--- End of checkForCheckin function, calling recursively ---");
|
//print("--- End of checkForCheckin function, calling recursively ---");
|
||||||
unawaited( checkForCheckin() );
|
unawaited( checkForCheckin() );
|
||||||
}
|
}
|
||||||
|
|||||||
@ -104,7 +104,14 @@ class DrawerPage extends StatelessWidget {
|
|||||||
Get.toNamed(AppPages.EVENT_ENTRY);
|
Get.toNamed(AppPages.EVENT_ENTRY);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.person),
|
||||||
|
title: Text("個人情報の修正"),
|
||||||
|
onTap: () {
|
||||||
|
Get.back(); // Close the drawer
|
||||||
|
Get.toNamed(AppPages.USER_DETAILS_EDIT);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
Obx(() => indexController.currentUser.isEmpty
|
Obx(() => indexController.currentUser.isEmpty
|
||||||
? ListTile(
|
? ListTile(
|
||||||
|
|||||||
@ -69,6 +69,7 @@ class EntryController extends GetxController {
|
|||||||
|
|
||||||
|
|
||||||
void initializeEditMode(Entry entry) {
|
void initializeEditMode(Entry entry) {
|
||||||
|
currentEntry.value = entry;
|
||||||
selectedEvent.value = entry.event;
|
selectedEvent.value = entry.event;
|
||||||
selectedTeam.value = entry.team;
|
selectedTeam.value = entry.team;
|
||||||
selectedCategory.value = entry.category;
|
selectedCategory.value = entry.category;
|
||||||
@ -169,10 +170,14 @@ class EntryController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateEntry() async {
|
Future<void> updateEntry() async {
|
||||||
if (currentEntry.value == null) return;
|
if (currentEntry.value == null) {
|
||||||
|
Get.snackbar('Error', 'No entry selected for update');
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
final updatedEntry = await _apiService.updateEntry(
|
final updatedEntry = await _apiService.updateEntry(
|
||||||
currentEntry.value!.id,
|
currentEntry.value!.id,
|
||||||
|
currentEntry.value!.team.id,
|
||||||
selectedEvent.value!.id,
|
selectedEvent.value!.id,
|
||||||
selectedCategory.value!.id,
|
selectedCategory.value!.id,
|
||||||
selectedDate.value!,
|
selectedDate.value!,
|
||||||
@ -189,7 +194,10 @@ class EntryController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteEntry() async {
|
Future<void> deleteEntry() async {
|
||||||
if (currentEntry.value == null) return;
|
if (currentEntry.value == null) {
|
||||||
|
Get.snackbar('Error', 'No entry selected for deletion');
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await _apiService.deleteEntry(currentEntry.value!.id);
|
await _apiService.deleteEntry(currentEntry.value!.id);
|
||||||
entries.removeWhere((entry) => entry.id == currentEntry.value!.id);
|
entries.removeWhere((entry) => entry.id == currentEntry.value!.id);
|
||||||
|
|||||||
@ -90,21 +90,40 @@ class EntryDetailPage extends GetView<EntryController> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
SizedBox(height: 32),
|
SizedBox(height: 32),
|
||||||
|
if (mode == 'new')
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
child: Text(mode == 'new' ? 'エントリーを作成' : 'エントリーを更新'),
|
child: Text('エントリーを作成'),
|
||||||
onPressed: () {
|
onPressed: () => controller.createEntry(),
|
||||||
if (mode == 'new') {
|
style: ElevatedButton.styleFrom(
|
||||||
controller.createEntry();
|
backgroundColor: Colors.blue,
|
||||||
} else {
|
minimumSize: Size(double.infinity, 50),
|
||||||
controller.updateEntry();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
if (mode == 'edit' && controller.isOwner())
|
)
|
||||||
ElevatedButton(
|
else
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton(
|
||||||
child: Text('エントリーを削除'),
|
child: Text('エントリーを削除'),
|
||||||
onPressed: () => controller.deleteEntry(),
|
onPressed: () => controller.deleteEntry(),
|
||||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
minimumSize: Size(0, 50),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton(
|
||||||
|
child: Text('エントリーを更新'),
|
||||||
|
onPressed: () => controller.updateEntry(),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.lightBlue,
|
||||||
|
minimumSize: Size(0, 50),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@ -18,18 +18,30 @@ class EntryListPage extends GetView<EntryController> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Obx(() => ListView.builder(
|
body: Obx((){
|
||||||
itemCount: controller.entries.length,
|
// エントリーを日付昇順にソート
|
||||||
|
final sortedEntries = controller.entries.toList()
|
||||||
|
..sort((a, b) => (a.date ?? DateTime(0)).compareTo(b.date ?? DateTime(0)));
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: sortedEntries.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final entry = controller.entries[index];
|
final entry = sortedEntries[index];
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(entry.event?.eventName ?? 'イベント未設定'),
|
title: Text(entry.event?.eventName ?? 'イベント未設定'),
|
||||||
subtitle: Text('${entry.team?.teamName ?? 'チーム未設定'} - ${entry.category?.categoryName ?? 'カテゴリ未設定'}'),
|
subtitle: Text(
|
||||||
trailing: Text(entry.date?.toString().substring(0, 10) ?? '日付未設定'),
|
'${entry.team?.teamName ?? 'チーム未設定'} - ${entry.category
|
||||||
onTap: () => Get.toNamed(AppPages.ENTRY_DETAIL, arguments: {'mode': 'edit', 'entry': entry}),
|
?.categoryName ?? 'カテゴリ未設定'}'),
|
||||||
|
trailing: Text(
|
||||||
|
entry.date?.toString().substring(0, 10) ?? '日付未設定'),
|
||||||
|
onTap: () =>
|
||||||
|
Get.toNamed(AppPages.ENTRY_DETAIL,
|
||||||
|
arguments: {'mode': 'edit', 'entry': entry}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)),
|
|
||||||
);
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -12,6 +12,7 @@ import 'package:latlong2/latlong.dart';
|
|||||||
import 'package:rogapp/model/destination.dart';
|
import 'package:rogapp/model/destination.dart';
|
||||||
import 'package:rogapp/model/entry.dart';
|
import 'package:rogapp/model/entry.dart';
|
||||||
import 'package:rogapp/pages/destination/destination_controller.dart';
|
import 'package:rogapp/pages/destination/destination_controller.dart';
|
||||||
|
import 'package:rogapp/pages/team/team_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/services/location_service.dart';
|
import 'package:rogapp/services/location_service.dart';
|
||||||
@ -20,9 +21,12 @@ import 'package:rogapp/widgets/debug_widget.dart';
|
|||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
import 'package:rogapp/services/api_service.dart';
|
import 'package:rogapp/services/api_service.dart';
|
||||||
|
import 'package:rogapp/model/user.dart';
|
||||||
|
|
||||||
import '../../main.dart';
|
|
||||||
|
|
||||||
|
import 'package:rogapp/main.dart';
|
||||||
|
|
||||||
|
import 'package:rogapp/widgets/helper_dialog.dart';
|
||||||
|
|
||||||
class IndexController extends GetxController with WidgetsBindingObserver {
|
class IndexController extends GetxController with WidgetsBindingObserver {
|
||||||
List<GeoJSONFeatureCollection> locations = <GeoJSONFeatureCollection>[].obs;
|
List<GeoJSONFeatureCollection> locations = <GeoJSONFeatureCollection>[].obs;
|
||||||
@ -353,6 +357,12 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
|||||||
// ログイン成功後、api_serviceを初期化
|
// ログイン成功後、api_serviceを初期化
|
||||||
await Get.putAsync(() => ApiService().init());
|
await Get.putAsync(() => ApiService().init());
|
||||||
|
|
||||||
|
// ユーザー情報の完全性をチェック
|
||||||
|
if (await checkUserInfoComplete()) {
|
||||||
|
Get.offAllNamed(AppPages.INDEX);
|
||||||
|
} else {
|
||||||
|
Get.offAllNamed(AppPages.USER_DETAILS_EDIT);
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
logManager.addOperationLog("User failed login : ${email} , ${password}.");
|
logManager.addOperationLog("User failed login : ${email} , ${password}.");
|
||||||
@ -372,6 +382,14 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> checkUserInfoComplete() async {
|
||||||
|
final user = await ApiService.to.getCurrentUser();
|
||||||
|
return user.firstname.isNotEmpty &&
|
||||||
|
user.lastname.isNotEmpty &&
|
||||||
|
user.dateOfBirth != null &&
|
||||||
|
user.female != null;
|
||||||
|
}
|
||||||
|
|
||||||
// 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。
|
// 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。
|
||||||
//
|
//
|
||||||
void changePassword(
|
void changePassword(
|
||||||
@ -507,7 +525,7 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
|||||||
saveToDevice(currentUser[0]["token"]);
|
saveToDevice(currentUser[0]["token"]);
|
||||||
}
|
}
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
loadLocationsBound( currentUser[0]["user"]["even_code"]);
|
loadLocationsBound( currentUser[0]["user"]["event_code"]);
|
||||||
if (currentUser.isNotEmpty) {
|
if (currentUser.isNotEmpty) {
|
||||||
rogMode.value = 0;
|
rogMode.value = 0;
|
||||||
restoreGame();
|
restoreGame();
|
||||||
@ -752,4 +770,23 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
|||||||
// 例: 現在の位置情報を再取得し、マップを更新する
|
// 例: 現在の位置情報を再取得し、マップを更新する
|
||||||
loadLocationsBound( eventCode );
|
loadLocationsBound( eventCode );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> checkEntryData() async {
|
||||||
|
// エントリーデータの有無をチェックするロジック
|
||||||
|
final teamController = TeamController();
|
||||||
|
bool hasEntryData = teamController.checkIfUserHasEntryData();
|
||||||
|
if (!hasEntryData) {
|
||||||
|
await showHelperDialog(
|
||||||
|
'イベントに参加するには、チーム登録・メンバー登録及びエントリーが必要になります。',
|
||||||
|
'entry_check'
|
||||||
|
);
|
||||||
|
// ドロワーを表示するロジック
|
||||||
|
Get.toNamed('/drawer');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateCurrentUser(User updatedUser) {
|
||||||
|
currentUser[0]['user'] = updatedUser.toJson();
|
||||||
|
update();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,8 +23,40 @@ import 'package:rogapp/utils/location_controller.dart';
|
|||||||
|
|
||||||
// IndexPageクラスは、GetView<IndexController>を継承したStatelessWidgetです。このクラスは、アプリのメインページを表すウィジェットです。
|
// IndexPageクラスは、GetView<IndexController>を継承したStatelessWidgetです。このクラスは、アプリのメインページを表すウィジェットです。
|
||||||
//
|
//
|
||||||
class IndexPage extends GetView<IndexController> {
|
import 'package:rogapp/widgets/helper_dialog.dart';
|
||||||
IndexPage({Key? key}) : super(key: key);
|
|
||||||
|
class IndexPage extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_IndexPageState createState() => _IndexPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _IndexPageState extends State<IndexPage> {
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
//checkEntryAndShowHelper();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
void checkEntryAndShowHelper() async {
|
||||||
|
final hasEntry = await checkIfUserHasEntry(); // この関数は実装する必要があります
|
||||||
|
if (!hasEntry) {
|
||||||
|
showHelperDialog(
|
||||||
|
context,
|
||||||
|
'イベントに参加するには、チーム登録・メンバー登録及びエントリーが必要になります。',
|
||||||
|
'entry_helper',
|
||||||
|
showDoNotShowAgain: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// class IndexPage extends GetView<IndexController> {
|
||||||
|
// IndexPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
// IndexControllerとDestinationControllerのインスタンスを取得しています。
|
// IndexControllerとDestinationControllerのインスタンスを取得しています。
|
||||||
//
|
//
|
||||||
|
|||||||
@ -2,18 +2,36 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.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/widgets/helper_dialog.dart';
|
||||||
|
|
||||||
// 要検討:ログインボタンとサインアップボタンの配色を見直すことを検討してください。現在の配色では、ボタンの役割がわかりにくい可能性があります。
|
// 要検討:ログインボタンとサインアップボタンの配色を見直すことを検討してください。現在の配色では、ボタンの役割がわかりにくい可能性があります。
|
||||||
// エラーメッセージをローカライズすることを検討してください。
|
// エラーメッセージをローカライズすることを検討してください。
|
||||||
// ログイン処理中にエラーが発生した場合のエラーハンドリングを追加することをお勧めします。
|
// ログイン処理中にエラーが発生した場合のエラーハンドリングを追加することをお勧めします。
|
||||||
//
|
//
|
||||||
class LoginPage extends StatelessWidget {
|
class LoginPage extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_LoginPageState createState() => _LoginPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoginPageState extends State<LoginPage> {
|
||||||
|
//class LoginPage extends StatelessWidget {
|
||||||
final IndexController indexController = Get.find<IndexController>();
|
final IndexController indexController = Get.find<IndexController>();
|
||||||
|
|
||||||
TextEditingController emailController = TextEditingController();
|
TextEditingController emailController = TextEditingController();
|
||||||
TextEditingController passwordController = TextEditingController();
|
TextEditingController passwordController = TextEditingController();
|
||||||
|
|
||||||
LoginPage({Key? key}) : super(key: key);
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
showHelperDialog(
|
||||||
|
'参加するにはユーザー登録が必要です。サインアップからユーザー登録してください。',
|
||||||
|
'login_page'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//LoginPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@ -2,15 +2,30 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.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/widgets/helper_dialog.dart';
|
||||||
|
|
||||||
class RegisterPage extends StatelessWidget {
|
class RegisterPage extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_RegisterPageState createState() => _RegisterPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RegisterPageState extends State<RegisterPage> {
|
||||||
final IndexController indexController = Get.find<IndexController>();
|
final IndexController indexController = Get.find<IndexController>();
|
||||||
|
|
||||||
TextEditingController emailController = TextEditingController();
|
final TextEditingController emailController = TextEditingController();
|
||||||
TextEditingController passwordController = TextEditingController();
|
final TextEditingController passwordController = TextEditingController();
|
||||||
TextEditingController confirmPasswordController = TextEditingController();
|
final TextEditingController confirmPasswordController = TextEditingController();
|
||||||
|
|
||||||
RegisterPage({Key? key}) : super(key: key);
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
showHelperDialog(
|
||||||
|
'登録メールにアクティベーションメールが送信されます。メールにあるリンクをタップすると正式登録になります。',
|
||||||
|
'register_page'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -21,191 +36,117 @@ class RegisterPage extends StatelessWidget {
|
|||||||
elevation: 0,
|
elevation: 0,
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
onPressed: () {
|
onPressed: () => Navigator.pop(context),
|
||||||
Navigator.pop(context);
|
icon: const Icon(Icons.arrow_back_ios, size: 20, color: Colors.black),
|
||||||
},
|
),
|
||||||
icon: const Icon(
|
|
||||||
Icons.arrow_back_ios,
|
|
||||||
size: 20,
|
|
||||||
color: Colors.black,
|
|
||||||
)),
|
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: SizedBox(
|
child: Container(
|
||||||
height: MediaQuery.of(context).size.height,
|
height: MediaQuery.of(context).size.height,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"sign_up".tr, // "サインアップ"
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 30,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 20,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"create_account".tr, // アカウントを無料で作成します。
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 15,
|
|
||||||
color: Colors.grey[700],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 30,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
makeInput(label: "email".tr, controller: emailController), // メールアドレス
|
Text(
|
||||||
makeInput(
|
|
||||||
label: "password".tr,
|
|
||||||
controller: passwordController,
|
|
||||||
obsureText: true), // パスワード
|
|
||||||
makeInput(
|
|
||||||
label: "confirm_password".tr,
|
|
||||||
controller: confirmPasswordController,
|
|
||||||
obsureText: true) // パスワード再確認
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.only(top: 3, left: 3),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(40),
|
|
||||||
border: const Border(
|
|
||||||
bottom: BorderSide(color: Colors.black),
|
|
||||||
top: BorderSide(color: Colors.black),
|
|
||||||
right: BorderSide(color: Colors.black),
|
|
||||||
left: BorderSide(color: Colors.black))),
|
|
||||||
child: MaterialButton(
|
|
||||||
minWidth: double.infinity,
|
|
||||||
height: 60,
|
|
||||||
onPressed: () {
|
|
||||||
if (passwordController.text !=
|
|
||||||
confirmPasswordController.text) {
|
|
||||||
Get.snackbar(
|
|
||||||
"no_match".tr,
|
|
||||||
"password_does_not_match".tr,
|
|
||||||
backgroundColor: Colors.red,
|
|
||||||
colorText: Colors.white,
|
|
||||||
icon: const Icon(Icons.assistant_photo_outlined,
|
|
||||||
size: 40.0, color: Colors.blue),
|
|
||||||
snackPosition: SnackPosition.TOP,
|
|
||||||
duration: const Duration(seconds: 3),
|
|
||||||
// backgroundColor: Colors.yellow,
|
|
||||||
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (emailController.text.isEmpty ||
|
|
||||||
passwordController.text.isEmpty) {
|
|
||||||
Get.snackbar(
|
|
||||||
"no_values".tr,
|
|
||||||
"email_and_password_required".tr,
|
|
||||||
backgroundColor: Colors.red,
|
|
||||||
colorText: Colors.white,
|
|
||||||
icon: const Icon(Icons.assistant_photo_outlined,
|
|
||||||
size: 40.0, color: Colors.blue),
|
|
||||||
snackPosition: SnackPosition.TOP,
|
|
||||||
duration: const Duration(seconds: 3),
|
|
||||||
//backgroundColor: Colors.yellow,
|
|
||||||
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
indexController.isLoading.value = true;
|
|
||||||
indexController.register(emailController.text,
|
|
||||||
passwordController.text, context);
|
|
||||||
},
|
|
||||||
color: Colors.redAccent,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(40)),
|
|
||||||
child: Text(
|
|
||||||
"sign_up".tr,
|
"sign_up".tr,
|
||||||
style: const TextStyle(
|
style: const TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Text(
|
||||||
|
"create_account".tr,
|
||||||
|
style: TextStyle(fontSize: 15, color: Colors.grey[700]),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
makeInput(label: "email".tr, controller: emailController),
|
||||||
|
makeInput(label: "password".tr, controller: passwordController, obsureText: true),
|
||||||
|
makeInput(label: "confirm_password".tr, controller: confirmPasswordController, obsureText: true),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: _handleRegister,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.redAccent,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(40)),
|
||||||
|
minimumSize: const Size(double.infinity, 60),
|
||||||
),
|
),
|
||||||
|
child: Text("sign_up".tr, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 16)),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 20),
|
||||||
const SizedBox(
|
|
||||||
height: 20,
|
|
||||||
),
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Flexible(child: Text("already_have_account".tr)),
|
Flexible(child: Text("already_have_account".tr)),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () => Get.toNamed(AppPages.LOGIN),
|
||||||
Get.toNamed(AppPages.LOGIN);
|
child: Text("login".tr, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 18)),
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
"login".tr,
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.w600, fontSize: 18),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handleRegister() {
|
||||||
|
if (passwordController.text != confirmPasswordController.text) {
|
||||||
|
_showErrorSnackbar("no_match".tr, "password_does_not_match".tr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (emailController.text.isEmpty || passwordController.text.isEmpty) {
|
||||||
|
_showErrorSnackbar("no_values".tr, "email_and_password_required".tr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
indexController.isLoading.value = true;
|
||||||
|
try {
|
||||||
|
indexController.register(
|
||||||
|
emailController.text,
|
||||||
|
passwordController.text,
|
||||||
|
context
|
||||||
|
);
|
||||||
|
// 登録が成功したと仮定し、ログインページに遷移
|
||||||
|
Get.offNamed(AppPages.LOGIN);
|
||||||
|
} catch (error) {
|
||||||
|
_showErrorSnackbar("registration_error".tr, error.toString());
|
||||||
|
} finally {
|
||||||
|
indexController.isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showErrorSnackbar(String title, String message) {
|
||||||
|
Get.snackbar(
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
icon: const Icon(Icons.error_outline, size: 40.0, color: Colors.white),
|
||||||
|
snackPosition: SnackPosition.TOP,
|
||||||
|
duration: const Duration(seconds: 3),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget makeInput(
|
Widget makeInput({required String label, required TextEditingController controller, bool obsureText = false}) {
|
||||||
{label, required TextEditingController controller, obsureText = false}) {
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(label, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w400, color: Colors.black87)),
|
||||||
label,
|
const SizedBox(height: 5),
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 15, fontWeight: FontWeight.w400, color: Colors.black87),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 5,
|
|
||||||
),
|
|
||||||
TextField(
|
TextField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
obscureText: obsureText,
|
obscureText: obsureText,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
contentPadding:
|
contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 10),
|
||||||
const EdgeInsets.symmetric(vertical: 0, horizontal: 10),
|
enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey[400]!)),
|
||||||
enabledBorder: OutlineInputBorder(
|
border: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey[400]!)),
|
||||||
borderSide: BorderSide(
|
|
||||||
color: (Colors.grey[400])!,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
border: OutlineInputBorder(
|
const SizedBox(height: 20),
|
||||||
borderSide: BorderSide(color: (Colors.grey[400])!),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 30,
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
182
lib/pages/register/user_detail_page.dart
Normal file
182
lib/pages/register/user_detail_page.dart
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:rogapp/model/user.dart';
|
||||||
|
import 'package:rogapp/routes/app_pages.dart';
|
||||||
|
import 'package:rogapp/services/api_service.dart';
|
||||||
|
import 'package:rogapp/pages/index/index_controller.dart';
|
||||||
|
|
||||||
|
class UserDetailsEditPage extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_UserDetailsEditPageState createState() => _UserDetailsEditPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UserDetailsEditPageState extends State<UserDetailsEditPage> {
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
final IndexController indexController = Get.find<IndexController>();
|
||||||
|
late User _user;
|
||||||
|
final TextEditingController _firstnameController = TextEditingController();
|
||||||
|
final TextEditingController _lastnameController = TextEditingController();
|
||||||
|
final TextEditingController _dateOfBirthController = TextEditingController();
|
||||||
|
late bool _female;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_user = User.fromJson(indexController.currentUser[0]['user']);
|
||||||
|
_firstnameController.text = _user.firstname;
|
||||||
|
_lastnameController.text = _user.lastname;
|
||||||
|
_dateOfBirthController.text = _user.dateOfBirth != null
|
||||||
|
? '${_user.dateOfBirth!.year}/${_user.dateOfBirth!.month.toString().padLeft(2, '0')}/${_user.dateOfBirth!.day.toString().padLeft(2, '0')}'
|
||||||
|
: '';
|
||||||
|
_female = _user.female;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text('個人情報の修正'),
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
|
),
|
||||||
|
body: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: ListView(
|
||||||
|
padding: EdgeInsets.all(16.0),
|
||||||
|
children: [
|
||||||
|
TextFormField(
|
||||||
|
controller: _lastnameController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: '姓',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return '姓を入力してください';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _firstnameController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: '名',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return '名を入力してください';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _dateOfBirthController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: '生年月日 (YYYY/MM/DD)',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
hintText: 'YYYY/MM/DD',
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.datetime,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return '生年月日を入力してください';
|
||||||
|
}
|
||||||
|
if (!RegExp(r'^\d{4}/\d{2}/\d{2}$').hasMatch(value)) {
|
||||||
|
return '正しい形式で入力してください (YYYY/MM/DD)';
|
||||||
|
}
|
||||||
|
final date = DateTime.tryParse(value.replaceAll('/', '-'));
|
||||||
|
if (date == null) {
|
||||||
|
return '有効な日付を入力してください';
|
||||||
|
}
|
||||||
|
if (date.isAfter(DateTime.now())) {
|
||||||
|
return '未来の日付は入力できません';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text('性別'),
|
||||||
|
subtitle: Text(_female ? '女性' : '男性'),
|
||||||
|
value: _female,
|
||||||
|
onChanged: (bool value) {
|
||||||
|
setState(() {
|
||||||
|
_female = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
initialValue: _user.email,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'メールアドレス',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text('アクティブ状態'),
|
||||||
|
value: _user.isActive,
|
||||||
|
onChanged: null,
|
||||||
|
),
|
||||||
|
SizedBox(height: 32),
|
||||||
|
ElevatedButton(
|
||||||
|
child: Text('更新'),
|
||||||
|
onPressed: _updateUserDetails,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateUserDetails() async {
|
||||||
|
if (_formKey.currentState!.validate()) {
|
||||||
|
final dateOfBirth = DateTime.tryParse(_dateOfBirthController.text.replaceAll('/', '-'));
|
||||||
|
if (dateOfBirth == null || dateOfBirth.isAfter(DateTime.now())) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('生年月日が無効です', style: TextStyle(color: Colors.red))),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
User updatedUser = User(
|
||||||
|
id: _user.id,
|
||||||
|
email: _user.email,
|
||||||
|
firstname: _firstnameController.text,
|
||||||
|
lastname: _lastnameController.text,
|
||||||
|
dateOfBirth: dateOfBirth,
|
||||||
|
female: _female,
|
||||||
|
isActive: _user.isActive,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
bool success = await ApiService.updateUserDetail(updatedUser, indexController.currentUser[0]['token']);
|
||||||
|
if (success) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('個人情報が更新されました')),
|
||||||
|
);
|
||||||
|
indexController.updateCurrentUser(updatedUser);
|
||||||
|
Get.offAllNamed(AppPages.INDEX);
|
||||||
|
//Get.back();
|
||||||
|
} else {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('更新に失敗しました', style: TextStyle(color: Colors.red))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('エラーが発生しました: $e', style: TextStyle(color: Colors.red))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -87,31 +87,42 @@ class _MemberDetailPageState extends State<MemberDetailPage> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(mode == 'new' ? 'メンバー追加' : 'メンバー詳細'),
|
title: Text(mode == 'new' ? 'メンバー追加' : 'メンバー詳細'),
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.save),
|
|
||||||
onPressed: () async {
|
|
||||||
await controller.saveMember();
|
|
||||||
Get.back(result: true);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
body: Obx(() {
|
body: Obx(() {
|
||||||
if (controller.isLoading.value) {
|
if (controller.isLoading.value) {
|
||||||
return Center(child: CircularProgressIndicator());
|
return Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
|
// TextEditingControllerをObxの中で作成
|
||||||
|
final emailController = TextEditingController(text: controller.email.value);
|
||||||
|
// カーソル位置を保持
|
||||||
|
emailController.selection = TextSelection.fromPosition(
|
||||||
|
TextPosition(offset: controller.email.value.length),
|
||||||
|
);
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
padding: EdgeInsets.all(16.0),
|
padding: EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (mode == 'new')
|
if (mode == 'new')
|
||||||
TextField(
|
TextFormField(
|
||||||
decoration: InputDecoration(labelText: 'メールアドレス'),
|
decoration: InputDecoration(labelText: 'メールアドレス'),
|
||||||
onChanged: (value) => controller.email.value = value,
|
//onChanged: (value) => controller.email.value = value,
|
||||||
controller: TextEditingController(text: controller.email.value),
|
onChanged: (value) {
|
||||||
|
controller.email.value = value;
|
||||||
|
// カーソル位置を更新
|
||||||
|
emailController.selection = TextSelection.fromPosition(
|
||||||
|
TextPosition(offset: value.length),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
controller: emailController,
|
||||||
|
//controller: TextEditingController(text: controller.email.value),
|
||||||
|
keyboardType: TextInputType.emailAddress, // メールアドレス用のキーボードを表示
|
||||||
|
autocorrect: false, // 自動修正を無効化
|
||||||
|
enableSuggestions: false,
|
||||||
)
|
)
|
||||||
else if (controller.isDummyEmail)
|
else if (controller.isDummyEmail)
|
||||||
Text('メールアドレス: (メアド無し)')
|
Text('メールアドレス: (メアド無し)')
|
||||||
@ -119,12 +130,12 @@ class _MemberDetailPageState extends State<MemberDetailPage> {
|
|||||||
Text('メールアドレス: ${controller.email.value}'),
|
Text('メールアドレス: ${controller.email.value}'),
|
||||||
|
|
||||||
if (controller.email.value.isEmpty || mode == 'edit') ...[
|
if (controller.email.value.isEmpty || mode == 'edit') ...[
|
||||||
TextField(
|
TextFormField(
|
||||||
decoration: InputDecoration(labelText: '姓'),
|
decoration: InputDecoration(labelText: '姓'),
|
||||||
onChanged: (value) => controller.lastname.value = value,
|
onChanged: (value) => controller.lastname.value = value,
|
||||||
controller: TextEditingController(text: controller.lastname.value),
|
controller: TextEditingController(text: controller.lastname.value),
|
||||||
),
|
),
|
||||||
TextField(
|
TextFormField(
|
||||||
decoration: InputDecoration(labelText: '名'),
|
decoration: InputDecoration(labelText: '名'),
|
||||||
onChanged: (value) => controller.firstname.value = value,
|
onChanged: (value) => controller.firstname.value = value,
|
||||||
controller: TextEditingController(text: controller.firstname.value),
|
controller: TextEditingController(text: controller.firstname.value),
|
||||||
@ -157,16 +168,16 @@ class _MemberDetailPageState extends State<MemberDetailPage> {
|
|||||||
onChanged: (value) => controller.female.value = value,
|
onChanged: (value) => controller.female.value = value,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
// 招待メール再送信ボタン(通常のEmailで未承認の場合のみ)
|
]),
|
||||||
if (!controller.isDummyEmail && !controller.isApproved)
|
|
||||||
ElevatedButton(
|
|
||||||
child: Text('招待メールを再送信'),
|
|
||||||
onPressed: () => controller.resendInvitation(),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
// メンバー削除ボタン
|
Padding(
|
||||||
|
padding: EdgeInsets.all(16.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
child: Text('メンバーから削除'),
|
child: Text('削除'),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final confirmed = await Get.dialog<bool>(
|
final confirmed = await Get.dialog<bool>(
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
@ -189,17 +200,38 @@ class _MemberDetailPageState extends State<MemberDetailPage> {
|
|||||||
Get.back(result: true);
|
Get.back(result: true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (!controller.isDummyEmail && !controller.isApproved)
|
||||||
|
ElevatedButton(
|
||||||
|
child: Text('招待再送信'),
|
||||||
|
onPressed: () => controller.resendInvitation(),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
foregroundColor: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
child: Text('保存・招待'),
|
||||||
|
onPressed: () async {
|
||||||
|
await controller.saveMember();
|
||||||
|
Get.back(result: true);
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
// lib/controllers/team_controller.dart
|
// lib/controllers/team_controller.dart
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:rogapp/model/team.dart';
|
import 'package:rogapp/model/team.dart';
|
||||||
import 'package:rogapp/model/category.dart';
|
import 'package:rogapp/model/category.dart';
|
||||||
@ -27,6 +28,7 @@ class TeamController extends GetxController {
|
|||||||
super.onInit();
|
super.onInit();
|
||||||
try {
|
try {
|
||||||
_apiService = Get.find<ApiService>();
|
_apiService = Get.find<ApiService>();
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
await fetchCategories();
|
await fetchCategories();
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
@ -67,8 +69,13 @@ class TeamController extends GetxController {
|
|||||||
void resetForm() {
|
void resetForm() {
|
||||||
selectedTeam.value = null;
|
selectedTeam.value = null;
|
||||||
teamName.value = '';
|
teamName.value = '';
|
||||||
selectedCategory.value = categories.isNotEmpty ? categories.first : null;
|
if (categories.isNotEmpty) {
|
||||||
teamMembers.clear();
|
selectedCategory.value = categories.first;
|
||||||
|
} else {
|
||||||
|
selectedCategory.value = null;
|
||||||
|
// カテゴリが空の場合、エラーメッセージをセット
|
||||||
|
error.value = 'カテゴリデータが取得できませんでした。';
|
||||||
|
} teamMembers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void cleanupForNavigation() {
|
void cleanupForNavigation() {
|
||||||
@ -93,6 +100,14 @@ class TeamController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool checkIfUserHasEntryData(){
|
||||||
|
if (teams.isEmpty) {
|
||||||
|
return false;
|
||||||
|
}else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> fetchCategories() async {
|
Future<void> fetchCategories() async {
|
||||||
try {
|
try {
|
||||||
final fetchedCategories = await _apiService.getCategories();
|
final fetchedCategories = await _apiService.getCategories();
|
||||||
@ -115,6 +130,17 @@ class TeamController extends GetxController {
|
|||||||
|
|
||||||
Future<Team> createTeam(String teamName, int categoryId) async {
|
Future<Team> createTeam(String teamName, int categoryId) async {
|
||||||
final newTeam = await _apiService.createTeam(teamName, categoryId);
|
final newTeam = await _apiService.createTeam(teamName, categoryId);
|
||||||
|
|
||||||
|
// 自分自身をメンバーとして自動登録
|
||||||
|
await _apiService.createTeamMember(
|
||||||
|
newTeam.id,
|
||||||
|
currentUser.value?.email,
|
||||||
|
currentUser.value!.firstname,
|
||||||
|
currentUser.value!.lastname,
|
||||||
|
currentUser.value?.dateOfBirth,
|
||||||
|
currentUser.value?.female,
|
||||||
|
);
|
||||||
|
|
||||||
return newTeam;
|
return newTeam;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,11 +151,51 @@ class TeamController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteTeam(int teamId) async {
|
Future<void> deleteTeam(int teamId) async {
|
||||||
|
bool confirmDelete = await Get.dialog(
|
||||||
|
AlertDialog(
|
||||||
|
title: Text('チーム削除の確認'),
|
||||||
|
content: Text('このチームとそのすべてのメンバーを削除しますか?この操作は取り消せません。'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
child: Text('キャンセル'),
|
||||||
|
onPressed: () => Get.back(result: false),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
child: Text('削除'),
|
||||||
|
onPressed: () => Get.back(result: true),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
) ?? false;
|
||||||
|
|
||||||
|
if (confirmDelete) {
|
||||||
try {
|
try {
|
||||||
|
// まず、チームのメンバーを全て削除
|
||||||
|
await _apiService.deleteAllTeamMembers(teamId);
|
||||||
|
|
||||||
|
// その後、チームを削除
|
||||||
await _apiService.deleteTeam(teamId);
|
await _apiService.deleteTeam(teamId);
|
||||||
|
|
||||||
|
// ローカルのチームリストを更新
|
||||||
teams.removeWhere((team) => team.id == teamId);
|
teams.removeWhere((team) => team.id == teamId);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get.snackbar(
|
||||||
|
'成功',
|
||||||
|
'チームとそのメンバーが削除されました',
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
);
|
||||||
|
*/
|
||||||
|
|
||||||
|
// チームリスト画面に戻る
|
||||||
|
Get.back();
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error deleting team: $e');
|
print('Error deleting team and members: $e');
|
||||||
|
Get.snackbar('エラー', 'チームとメンバーの削除に失敗しました');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,4 +303,30 @@ class TeamController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<NewCategory> getFilteredCategories() {
|
||||||
|
//List<User> teamMembers = getCurrentTeamMembers();
|
||||||
|
return categories.where((category) {
|
||||||
|
return isCategoryValid(category, teamMembers);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isCategoryValid(NewCategory category, List<User> teamMembers) {
|
||||||
|
int maleCount = teamMembers.where((member) => !member.female).length;
|
||||||
|
int femaleCount = teamMembers.where((member) => member.female).length;
|
||||||
|
int totalCount = teamMembers.length;
|
||||||
|
|
||||||
|
bool isValidGender = category.female ? (femaleCount == totalCount) : true;
|
||||||
|
bool isValidMemberCount = totalCount == category.numOfMember;
|
||||||
|
bool isValidFamily = category.family ? areAllMembersFamily(teamMembers) : true;
|
||||||
|
|
||||||
|
return isValidGender && isValidMemberCount && isValidFamily;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool areAllMembersFamily(List<User> teamMembers) {
|
||||||
|
// 家族かどうかを判断するロジック(例: 同じ姓を持つメンバーが2人以上いる場合は家族とみなす)
|
||||||
|
Set<String> familyNames = teamMembers.map((member) => member.lastname).toSet();
|
||||||
|
return familyNames.length < teamMembers.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -126,15 +126,26 @@ class _TeamDetailPageState extends State<TeamDetailPage> {
|
|||||||
),
|
),
|
||||||
|
|
||||||
SizedBox(height: 16),
|
SizedBox(height: 16),
|
||||||
DropdownButtonFormField<NewCategory>(
|
Obx(() {
|
||||||
|
if (controller.categories.isEmpty) {
|
||||||
|
return Text('カテゴリデータを読み込めませんでした。', style: TextStyle(color: Colors.red));
|
||||||
|
}
|
||||||
|
return DropdownButtonFormField<NewCategory>(
|
||||||
decoration: InputDecoration(labelText: 'カテゴリ'),
|
decoration: InputDecoration(labelText: 'カテゴリ'),
|
||||||
value: controller.selectedCategory.value,
|
value: controller.selectedCategory.value,
|
||||||
items: controller.categories.map((category) => DropdownMenuItem(
|
items: controller.categories.map((category) => DropdownMenuItem(
|
||||||
value: category,
|
value: category,
|
||||||
child: Text(category.categoryName),
|
child: Text(category.categoryName),
|
||||||
)).toList(),
|
)).toList(),
|
||||||
|
/*
|
||||||
|
items: controller.getFilteredCategories().map((category) => DropdownMenuItem(
|
||||||
|
value: category,
|
||||||
|
child: Text(category.categoryName),
|
||||||
|
)).toList(),
|
||||||
|
*/
|
||||||
onChanged: (value) => controller.updateCategory(value),
|
onChanged: (value) => controller.updateCategory(value),
|
||||||
),
|
);
|
||||||
|
}),
|
||||||
if (mode.value == 'edit')
|
if (mode.value == 'edit')
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(vertical: 16),
|
padding: EdgeInsets.symmetric(vertical: 16),
|
||||||
@ -160,7 +171,7 @@ class _TeamDetailPageState extends State<TeamDetailPage> {
|
|||||||
? '${member.lastname} ${member.firstname}'
|
? '${member.lastname} ${member.firstname}'
|
||||||
: member.isActive
|
: member.isActive
|
||||||
? '${member.lastname} ${member.firstname}'
|
? '${member.lastname} ${member.firstname}'
|
||||||
: '${member.email?.split('@')[0] ?? ''}(未承認)';
|
: '${member.email?.split('@')[0] ?? ''}'; //(未承認)';
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(displayName),
|
title: Text(displayName),
|
||||||
subtitle: isDummyEmail ? Text('Email未設定') : null,
|
subtitle: isDummyEmail ? Text('Email未設定') : null,
|
||||||
|
|||||||
@ -39,7 +39,7 @@ import 'package:rogapp/pages/entry/entry_binding.dart';
|
|||||||
|
|
||||||
import 'package:rogapp/pages/entry/event_entries_page.dart';
|
import 'package:rogapp/pages/entry/event_entries_page.dart';
|
||||||
import 'package:rogapp/pages/entry/event_entries_binding.dart';
|
import 'package:rogapp/pages/entry/event_entries_binding.dart';
|
||||||
|
import 'package:rogapp/pages/register/user_detail_page.dart';
|
||||||
|
|
||||||
part 'app_routes.dart';
|
part 'app_routes.dart';
|
||||||
|
|
||||||
@ -75,6 +75,8 @@ class AppPages {
|
|||||||
static const ENTRY_LIST = Routes.ENTRY_LIST;
|
static const ENTRY_LIST = Routes.ENTRY_LIST;
|
||||||
static const ENTRY_DETAIL = Routes.ENTRY_DETAIL;
|
static const ENTRY_DETAIL = Routes.ENTRY_DETAIL;
|
||||||
static const EVENT_ENTRY = Routes.EVENT_ENTRIES;
|
static const EVENT_ENTRY = Routes.EVENT_ENTRIES;
|
||||||
|
static const USER_DETAILS_EDIT = Routes.USER_DETAILS_EDIT;
|
||||||
|
|
||||||
|
|
||||||
static final routes = [
|
static final routes = [
|
||||||
GetPage(
|
GetPage(
|
||||||
@ -176,7 +178,10 @@ class AppPages {
|
|||||||
page: () => EventEntriesPage(),
|
page: () => EventEntriesPage(),
|
||||||
binding: EventEntriesBinding(),
|
binding: EventEntriesBinding(),
|
||||||
),
|
),
|
||||||
|
GetPage(
|
||||||
|
name: Routes.USER_DETAILS_EDIT,
|
||||||
|
page: () => UserDetailsEditPage(),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,4 +36,6 @@ abstract class Routes {
|
|||||||
static const ENTRY_DETAIL = '/entry-detail';
|
static const ENTRY_DETAIL = '/entry-detail';
|
||||||
|
|
||||||
static const EVENT_ENTRIES = '/event-entries';
|
static const EVENT_ENTRIES = '/event-entries';
|
||||||
|
static const USER_DETAILS_EDIT = '/user-details-edit';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -75,13 +75,13 @@ class ApiService extends GetxService{
|
|||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
// UTF-8でデコード
|
// UTF-8でデコード
|
||||||
final decodedResponse = utf8.decode(response.bodyBytes);
|
final decodedResponse = utf8.decode(response.bodyBytes);
|
||||||
print('User Response body: $decodedResponse');
|
//print('User Response body: $decodedResponse');
|
||||||
List<dynamic> teamsJson = json.decode(decodedResponse);
|
List<dynamic> teamsJson = json.decode(decodedResponse);
|
||||||
|
|
||||||
List<Team> teams = [];
|
List<Team> teams = [];
|
||||||
for (var teamJson in teamsJson) {
|
for (var teamJson in teamsJson) {
|
||||||
print('\nTeam Data:');
|
//print('\nTeam Data:');
|
||||||
_printDataComparison(teamJson, Team);
|
//_printDataComparison(teamJson, Team);
|
||||||
teams.add(Team.fromJson(teamJson));
|
teams.add(Team.fromJson(teamJson));
|
||||||
}
|
}
|
||||||
return teams;
|
return teams;
|
||||||
@ -114,8 +114,8 @@ class ApiService extends GetxService{
|
|||||||
|
|
||||||
List<NewCategory> categories = [];
|
List<NewCategory> categories = [];
|
||||||
for (var categoryJson in categoriesJson) {
|
for (var categoryJson in categoriesJson) {
|
||||||
print('\nCategory Data:');
|
//print('\nCategory Data:');
|
||||||
_printDataComparison(categoryJson, NewCategory);
|
//_printDataComparison(categoryJson, NewCategory);
|
||||||
categories.add(NewCategory.fromJson(categoryJson));
|
categories.add(NewCategory.fromJson(categoryJson));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,11 +143,11 @@ class ApiService extends GetxService{
|
|||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
final decodedResponse = utf8.decode(response.bodyBytes);
|
final decodedResponse = utf8.decode(response.bodyBytes);
|
||||||
print('User Response body: $decodedResponse');
|
//print('User Response body: $decodedResponse');
|
||||||
final jsonData = json.decode(decodedResponse);
|
final jsonData = json.decode(decodedResponse);
|
||||||
|
|
||||||
print('\nUser Data Comparison:');
|
//print('\nUser Data Comparison:');
|
||||||
_printDataComparison(jsonData, User);
|
//_printDataComparison(jsonData, User);
|
||||||
|
|
||||||
return User.fromJson(jsonData);
|
return User.fromJson(jsonData);
|
||||||
} else {
|
} else {
|
||||||
@ -277,7 +277,11 @@ class ApiService extends GetxService{
|
|||||||
headers: {'Authorization': 'Token $token','Content-Type': 'application/json; charset=UTF-8'},
|
headers: {'Authorization': 'Token $token','Content-Type': 'application/json; charset=UTF-8'},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode != 204) {
|
if( response.statusCode == 400) {
|
||||||
|
final decodedResponse = utf8.decode(response.bodyBytes);
|
||||||
|
print('User Response body: $decodedResponse');
|
||||||
|
throw Exception('まだメンバーが残っているので、チームを削除できません。');
|
||||||
|
}else if (response.statusCode != 204) {
|
||||||
throw Exception('Failed to delete team');
|
throw Exception('Failed to delete team');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -302,10 +306,18 @@ class ApiService extends GetxService{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<User> createTeamMember(int teamId, String? email, String firstname, String lastname, DateTime? dateOfBirth,bool? female) async {
|
Future<User> createTeamMember(int teamId, String? email, String? firstname, String? lastname, DateTime? dateOfBirth,bool? female) async {
|
||||||
init();
|
init();
|
||||||
getToken();
|
getToken();
|
||||||
|
|
||||||
|
// emailが値を持っている場合の処理
|
||||||
|
if (email != null && email.isNotEmpty) {
|
||||||
|
firstname ??= "dummy";
|
||||||
|
lastname ??= "dummy";
|
||||||
|
dateOfBirth ??= DateTime.now();
|
||||||
|
female ??= false;
|
||||||
|
}
|
||||||
|
|
||||||
String? formattedDateOfBirth;
|
String? formattedDateOfBirth;
|
||||||
if (dateOfBirth != null) {
|
if (dateOfBirth != null) {
|
||||||
formattedDateOfBirth = DateFormat('yyyy-MM-dd').format(dateOfBirth);
|
formattedDateOfBirth = DateFormat('yyyy-MM-dd').format(dateOfBirth);
|
||||||
@ -379,6 +391,17 @@ class ApiService extends GetxService{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> deleteAllTeamMembers(int teamId) async {
|
||||||
|
final response = await http.delete(
|
||||||
|
Uri.parse('$baseUrl/teams/$teamId/members/destroy_all/?confirm=true'),
|
||||||
|
headers: {'Authorization': 'Token $token'},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
throw Exception('Failed to delete team members');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> resendMemberInvitation(int memberId) async {
|
Future<void> resendMemberInvitation(int memberId) async {
|
||||||
init();
|
init();
|
||||||
getToken();
|
getToken();
|
||||||
@ -503,7 +526,7 @@ class ApiService extends GetxService{
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Future<Entry> updateEntry(int entryId, int eventId, int categoryId, DateTime date) async {
|
Future<Entry> updateEntry(int entryId, int teamId, int eventId, int categoryId, DateTime date) async {
|
||||||
init();
|
init();
|
||||||
getToken();
|
getToken();
|
||||||
|
|
||||||
@ -519,6 +542,7 @@ class ApiService extends GetxService{
|
|||||||
'Content-Type': 'application/json; charset=UTF-8',
|
'Content-Type': 'application/json; charset=UTF-8',
|
||||||
},
|
},
|
||||||
body: json.encode({
|
body: json.encode({
|
||||||
|
'team': teamId,
|
||||||
'event': eventId,
|
'event': eventId,
|
||||||
'category': categoryId,
|
'category': categoryId,
|
||||||
'date': formattedDate,
|
'date': formattedDate,
|
||||||
@ -547,4 +571,41 @@ class ApiService extends GetxService{
|
|||||||
throw Exception('Failed to delete entry');
|
throw Exception('Failed to delete entry');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<bool> updateUserDetail(User user, String token) async {
|
||||||
|
String serverUrl = ConstValues.currentServer();
|
||||||
|
int? userid = user.id;
|
||||||
|
String url = '$serverUrl/api/userdetail/$userid/';
|
||||||
|
|
||||||
|
try {
|
||||||
|
String? formattedDate;
|
||||||
|
if (user.dateOfBirth != null) {
|
||||||
|
formattedDate = DateFormat('yyyy-MM-dd').format(user.dateOfBirth!);
|
||||||
|
}
|
||||||
|
final http.Response response = await http.put(
|
||||||
|
Uri.parse(url),
|
||||||
|
headers: <String, String>{
|
||||||
|
'Content-Type': 'application/json; charset=UTF-8',
|
||||||
|
'Authorization': 'Token $token'
|
||||||
|
},
|
||||||
|
body: jsonEncode({
|
||||||
|
'firstname': user.firstname,
|
||||||
|
'lastname': user.lastname,
|
||||||
|
'date_of_birth': formattedDate,
|
||||||
|
'female': user.female,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
print('Update failed with status code: ${response.statusCode}');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error in updateUserDetail: $e');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -5,6 +5,9 @@ import 'package:get/get.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../utils/const.dart';
|
import '../utils/const.dart';
|
||||||
|
//import 'package:rogapp/services/team_service.dart';
|
||||||
|
//import 'package:rogapp/services/member_service.dart';
|
||||||
|
|
||||||
|
|
||||||
class AuthService {
|
class AuthService {
|
||||||
Future<AuthUser?> userLogin(String email, String password) async {
|
Future<AuthUser?> userLogin(String email, String password) async {
|
||||||
@ -131,6 +134,22 @@ class AuthService {
|
|||||||
|
|
||||||
// ユーザー登録
|
// ユーザー登録
|
||||||
//
|
//
|
||||||
|
/*
|
||||||
|
Future<void> registerUser(String email, String password, bool isFemale) async {
|
||||||
|
final user = await register(email, password);
|
||||||
|
if (user != null) {
|
||||||
|
final _teamController = TeamController();
|
||||||
|
_teamController.createTeam(String teamName, int categoryId) ;
|
||||||
|
final teamService = TeamService();
|
||||||
|
final memberService = MemberService();
|
||||||
|
|
||||||
|
final team = await teamService.createSoloTeam(user.id, isFemale);
|
||||||
|
await memberService.addMember(team.id, user.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
static Future<Map<String, dynamic>> register(
|
static Future<Map<String, dynamic>> register(
|
||||||
String email, String password) async {
|
String email, String password) async {
|
||||||
Map<String, dynamic> cats = {};
|
Map<String, dynamic> cats = {};
|
||||||
@ -168,6 +187,7 @@ class AuthService {
|
|||||||
return cats;
|
return cats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static Future<List<dynamic>?> userDetails(int userid) async {
|
static Future<List<dynamic>?> userDetails(int userid) async {
|
||||||
List<dynamic> cats = [];
|
List<dynamic> cats = [];
|
||||||
String serverUrl = ConstValues.currentServer();
|
String serverUrl = ConstValues.currentServer();
|
||||||
|
|||||||
@ -4,6 +4,8 @@
|
|||||||
class ConstValues{
|
class ConstValues{
|
||||||
static const container_svr = "http://container.intranet.sumasen.net:8100";
|
static const container_svr = "http://container.intranet.sumasen.net:8100";
|
||||||
static const server_uri = "https://rogaining.intranet.sumasen.net";
|
static const server_uri = "https://rogaining.intranet.sumasen.net";
|
||||||
|
//static const container_svr = "http://container.sumasen.net:8100";
|
||||||
|
//static const server_uri = "https://rogaining.sumasen.net";
|
||||||
static const dev_server = "http://localhost:8100";
|
static const dev_server = "http://localhost:8100";
|
||||||
static const dev_ip_server = "http://192.168.8.100:8100";
|
static const dev_ip_server = "http://192.168.8.100:8100";
|
||||||
static const dev_home_ip_server = "http://172.20.10.9:8100";
|
static const dev_home_ip_server = "http://172.20.10.9:8100";
|
||||||
|
|||||||
@ -311,7 +311,7 @@ class StringValues extends Translations{
|
|||||||
'already_have_account': 'すでにアカウントをお持ちですか?',
|
'already_have_account': 'すでにアカウントをお持ちですか?',
|
||||||
'sign_up': 'サインアップ',
|
'sign_up': 'サインアップ',
|
||||||
'create_account': 'アカウントを無料で作成します',
|
'create_account': 'アカウントを無料で作成します',
|
||||||
'confirm_password': 'パスワードを認証する',
|
'confirm_password': '確認用パスワード',
|
||||||
'cancel_checkin': 'チェックイン取消',
|
'cancel_checkin': 'チェックイン取消',
|
||||||
'go_here': 'ルート表示',
|
'go_here': 'ルート表示',
|
||||||
'cancel_route':'ルート消去',
|
'cancel_route':'ルート消去',
|
||||||
|
|||||||
73
lib/widgets/helper_dialog.dart
Normal file
73
lib/widgets/helper_dialog.dart
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// lib/widgets/helper_dialog.dart
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
class HelperDialog extends StatefulWidget {
|
||||||
|
final String message;
|
||||||
|
final String screenKey;
|
||||||
|
|
||||||
|
const HelperDialog({Key? key, required this.message, required this.screenKey}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_HelperDialogState createState() => _HelperDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HelperDialogState extends State<HelperDialog> {
|
||||||
|
bool _doNotShowAgain = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.help_outline, color: Colors.blue),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
Text('ヘルプ'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(widget.message),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Checkbox(
|
||||||
|
value: _doNotShowAgain,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_doNotShowAgain = value!;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Text('この画面を二度と表示しない'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
child: Text('OK'),
|
||||||
|
onPressed: () async {
|
||||||
|
if (_doNotShowAgain) {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setBool('helper_${widget.screenKey}', false);
|
||||||
|
}
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ヘルパー画面を表示する関数
|
||||||
|
Future<void> showHelperDialog(String message, String screenKey) async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final showHelper = prefs.getBool('helper_$screenKey') ?? true;
|
||||||
|
if (showHelper) {
|
||||||
|
Get.dialog(HelperDialog(message: message, screenKey: screenKey));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 4.8.2+483
|
version: 4.8.5+485
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.2.0 <4.0.0"
|
sdk: ">=3.2.0 <4.0.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user