release 4.8.9

This commit is contained in:
2024-08-05 03:08:12 +09:00
parent 1e0af0b06b
commit 66ade1fe09
30 changed files with 1765 additions and 17646 deletions

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

View File

@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 60; objectVersion = 54;
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 = 486; CURRENT_PROJECT_VERSION = 488;
DEVELOPMENT_TEAM = UMNEWT25JR; DEVELOPMENT_TEAM = UMNEWT25JR;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FLUTTER_BUILD_NAME = 4.8.6; FLUTTER_BUILD_NAME = 4.8.8;
FLUTTER_BUILD_NUMBER = 486; FLUTTER_BUILD_NUMBER = 488;
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.6; MARKETING_VERSION = 4.8.8;
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 = 486; CURRENT_PROJECT_VERSION = 488;
DEVELOPMENT_TEAM = UMNEWT25JR; DEVELOPMENT_TEAM = UMNEWT25JR;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FLUTTER_BUILD_NAME = 4.8.6; FLUTTER_BUILD_NAME = 4.8.8;
FLUTTER_BUILD_NUMBER = 486; FLUTTER_BUILD_NUMBER = 488;
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.6; MARKETING_VERSION = 4.8.8;
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 = 486; CURRENT_PROJECT_VERSION = 488;
DEVELOPMENT_TEAM = UMNEWT25JR; DEVELOPMENT_TEAM = UMNEWT25JR;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FLUTTER_BUILD_NAME = 4.8.6; FLUTTER_BUILD_NAME = 4.8.8;
FLUTTER_BUILD_NUMBER = 486; FLUTTER_BUILD_NUMBER = 488;
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.6; MARKETING_VERSION = 4.8.8;
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 = "";

View File

@ -34,13 +34,28 @@ class NewCategory {
id: json['id'] ?? 0, id: json['id'] ?? 0,
categoryName: json['category_name'] ?? 'Unknown Category', categoryName: json['category_name'] ?? 'Unknown Category',
categoryNumber: json['category_number'] ?? 0, categoryNumber: json['category_number'] ?? 0,
duration: Duration(seconds: json['duration'] ?? 0), duration: parseDuration(json['duration']),
numOfMember: json['num_of_member'] ?? 1, numOfMember: json['num_of_member'] ?? 1,
family: json['family'] ?? false, family: json['family'] ?? false,
female: json['female'] ?? false, female: json['female'] ?? false,
); );
} }
static Duration parseDuration(String s) {
int hours = 0;
int minutes = 0;
int micros;
List<String> parts = s.split(':');
if (parts.length > 2) {
hours = int.parse(parts[parts.length - 3]);
}
if (parts.length > 1) {
minutes = int.parse(parts[parts.length - 2]);
}
micros = (double.parse(parts[parts.length - 1]) * 1000000).round();
return Duration(hours: hours, minutes: minutes, microseconds: micros);
}
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
'id': id, 'id': id,

View File

@ -10,6 +10,7 @@ class Entry {
final Event event; final Event event;
final NewCategory category; final NewCategory category;
final DateTime? date; final DateTime? date;
final int zekkenNumber; // 新しく追加
final String owner; final String owner;
Entry({ Entry({
@ -18,6 +19,7 @@ class Entry {
required this.event, required this.event,
required this.category, required this.category,
required this.date, required this.date,
required this.zekkenNumber,
required this.owner, required this.owner,
}); });
@ -30,6 +32,7 @@ class Entry {
date: json['date'] != null date: json['date'] != null
? DateTime.tryParse(json['date']) ? DateTime.tryParse(json['date'])
: null, : null,
zekkenNumber: json['zekken_number'], // 新しく追加
owner: json['owner'] is Map ? json['owner']['name'] ?? '' : json['owner'] ?? '', owner: json['owner'] is Map ? json['owner']['name'] ?? '' : json['owner'] ?? '',
); );
} }
@ -41,6 +44,7 @@ class Entry {
'event': event.toJson(), 'event': event.toJson(),
'category': category.toJson(), 'category': category.toJson(),
'date': date?.toIso8601String(), 'date': date?.toIso8601String(),
'zekken_number': zekkenNumber, // 新しく追加
'owner': owner, 'owner': owner,
}; };
} }

View File

@ -6,7 +6,7 @@ import 'user.dart';
class Team { class Team {
final int id; final int id;
final String zekkenNumber; // final String zekkenNumber;
final String teamName; final String teamName;
final NewCategory category; final NewCategory category;
final User owner; final User owner;
@ -14,7 +14,7 @@ class Team {
Team({ Team({
required this.id, required this.id,
required this.zekkenNumber, // required this.zekkenNumber,
required this.teamName, required this.teamName,
required this.category, required this.category,
required this.owner, required this.owner,
@ -23,7 +23,7 @@ class Team {
factory Team.fromJson(Map<String, dynamic> json) { factory Team.fromJson(Map<String, dynamic> json) {
return Team( return Team(
id: json['id'] ?? 0, id: json['id'] ?? 0,
zekkenNumber: json['zekken_number'] ?? 'Unknown', //zekkenNumber: json['zekken_number'] ?? 'Unknown',
teamName: json['team_name'] ?? 'Unknown Team', teamName: json['team_name'] ?? 'Unknown Team',
category: json['category'] != null category: json['category'] != null
? NewCategory.fromJson(json['category']) ? NewCategory.fromJson(json['category'])
@ -37,7 +37,7 @@ class Team {
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
'id': id, 'id': id,
'zekken_number': zekkenNumber, //'zekken_number': zekkenNumber,
'team_name': teamName, 'team_name': teamName,
'category': category.toJson(), 'category': category.toJson(),
'owner': owner.toJson(), 'owner': owner.toJson(),

View File

@ -1,9 +1,12 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; // この行を追加または確認 import 'dart:convert'; // この行を追加または確認
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:path_provider/path_provider.dart';
import 'package:rogapp/model/destination.dart'; import 'package:rogapp/model/destination.dart';
import 'package:rogapp/pages/destination/destination_controller.dart'; import 'package:rogapp/pages/destination/destination_controller.dart';
import 'package:rogapp/pages/index/index_controller.dart'; import 'package:rogapp/pages/index/index_controller.dart';
@ -493,7 +496,10 @@ class CameraPage extends StatelessWidget {
if (buyPointPhoto == true) { if (buyPointPhoto == true) {
// buyPointPhotoがtrueの場合は、BuyPointCameraウィジェットを返します。 // buyPointPhotoがtrueの場合は、BuyPointCameraウィジェットを返します。
//print("--- buy point camera ${destination.toString()}"); //print("--- buy point camera ${destination.toString()}");
return BuyPointCamera(destination: destination); //return BuyPointCamera(destination: destination);
return SwitchableBuyPointCamera(destination: destination);
//}else if(destination.use_qr_code){ //}else if(destination.use_qr_code){
// return QRCodeScannerPage(destination: destination); // return QRCodeScannerPage(destination: destination);
} else if (destinationController.isInRog.value) { } else if (destinationController.isInRog.value) {
@ -617,105 +623,189 @@ class StartRogaining extends StatelessWidget {
// 完了ボタンをタップすると、購入ポイントの処理が行われます。 // 完了ボタンをタップすると、購入ポイントの処理が行われます。
// 購入なしボタンをタップすると、購入ポイントがキャンセルされます。 // 購入なしボタンをタップすると、購入ポイントがキャンセルされます。
// //
class SwitchableBuyPointCamera extends StatefulWidget {
final Destination destination;
const SwitchableBuyPointCamera({Key? key, required this.destination}) : super(key: key);
@override
_SwitchableBuyPointCameraState createState() => _SwitchableBuyPointCameraState();
}
class _SwitchableBuyPointCameraState extends State<SwitchableBuyPointCamera> {
bool isQRMode = true;
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
final screenHeight = MediaQuery.of(context).size.height;
final qrViewWidth = screenWidth * 2 / 3;
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text("${widget.destination.sub_loc_id} : ${widget.destination.name}"),
),
body: SafeArea(
child: Stack(
children: [
if (isQRMode)
Column(
children: [
SizedBox(height: screenHeight * 0.1),
Center(
child: SizedBox(
width: qrViewWidth,
height: qrViewWidth,
child: BuyPointCamera_QR(destination: widget.destination),
),
),
Expanded(
child: Align(
alignment: Alignment.topCenter,
child: Padding(
padding: const EdgeInsets.only(top: 16.0),
child: Text(
"岐阜ロゲQRコードにかざしてください。",
style: TextStyle(fontSize: 16),
),
),
),
),
],
)
else
Positioned.fill(
child: BuyPointCamera(destination: widget.destination),
),
Positioned(
right: 16,
bottom: 16,
child: Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.7),
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(isQRMode ? "カメラへ" : "QRへ"),
Switch(
value: isQRMode,
onChanged: (value) {
setState(() {
isQRMode = value;
});
},
),
],
),
),
),
],
),
),
);
}
}
class BuyPointCamera extends StatelessWidget { class BuyPointCamera extends StatelessWidget {
BuyPointCamera({Key? key, required this.destination}) : super(key: key); BuyPointCamera({Key? key, required this.destination}) : super(key: key);
Destination destination; Destination destination;
DestinationController destinationController = DestinationController destinationController =
Get.find<DestinationController>(); Get.find<DestinationController>();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return SingleChildScrollView(
appBar: AppBar( child: Column(
automaticallyImplyLeading: false, mainAxisAlignment: MainAxisAlignment.spaceAround,
title: Text( children: [
"${destination.sub_loc_id} : ${destination.name}", Padding(
), padding: const EdgeInsets.all(8.0),
), child: Center(
body: SingleChildScrollView( child: Obx(
child: Column( () =>
mainAxisAlignment: MainAxisAlignment.spaceAround, Container(
children: [ width: MediaQuery
Padding( .of(context)
padding: const EdgeInsets.all(8.0), .size
child: Center( .width,
child: Obx( height: 370,
() => Container( decoration: BoxDecoration(
width: MediaQuery.of(context).size.width, image: DecorationImage(
height: 370, // 要修正getReceiptImage関数の戻り値がnullの場合のエラーハンドリングが不十分です。適切なデフォルト画像を表示するなどの処理を追加してください。
decoration: BoxDecoration( //
image: DecorationImage( image: getReceiptImage(), fit: BoxFit.cover)),
// 要修正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())
],
), ),
), ),
], ),
),
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())
],
),
),
],
), ),
); );
} }
@ -743,28 +833,9 @@ class _BuyPointCamera_QRState extends State<BuyPointCamera_QR> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return QRView(
appBar: AppBar( key: qrKey,
automaticallyImplyLeading: false, onQRViewCreated: _onQRViewCreated,
title: Text("${widget.destination.sub_loc_id} : ${widget.destination.name}"),
),
body: Column(
children: [
Expanded(
flex: 4,
child: QRView(
key: qrKey,
onQRViewCreated: _onQRViewCreated,
),
),
Expanded(
flex: 1,
child: Center(
child: Text('QRコードをスキャンしてください'),
),
),
],
),
); );
} }
@ -773,11 +844,39 @@ class _BuyPointCamera_QRState extends State<BuyPointCamera_QR> {
controller.scannedDataStream.listen((scanData) { controller.scannedDataStream.listen((scanData) {
if (!isQRScanned && scanData.code != null && scanData.code!.startsWith('https://rogaining.sumasen.net/api/activate_buy_point/')) { if (!isQRScanned && scanData.code != null && scanData.code!.startsWith('https://rogaining.sumasen.net/api/activate_buy_point/')) {
isQRScanned = true; isQRScanned = true;
_activateBuyPoint(scanData.code!); _processBuyPoint();
//_activateBuyPoint(scanData.code!);
} }
}); });
} }
Future<String> getImageFilePathFromAssets(String assetPath) async {
final byteData = await rootBundle.load(assetPath);
final buffer = byteData.buffer;
Directory tempDir = await getTemporaryDirectory();
String tempPath = tempDir.path;
var filePath = '$tempPath/temp_qr_receipt.png';
return (await File(filePath).writeAsBytes(
buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes)
)).path;
}
void _processBuyPoint() async {
// アセットの画像をテンポラリファイルにコピー
String predefinedImagePath = await getImageFilePathFromAssets('assets/images/QR_certificate.png');
try {
await destinationController.makeBuyPoint(widget.destination, predefinedImagePath);
Get.snackbar('成功', 'お買い物ポイントが有効化されました');
Navigator.of(context).pop();
} catch (e) {
Get.snackbar('エラー', 'お買い物ポイントの有効化に失敗しました');
} finally {
isQRScanned = false;
}
}
void _activateBuyPoint(String qrCode) async { void _activateBuyPoint(String qrCode) async {
final IndexController indexController = Get.find<IndexController>(); final IndexController indexController = Get.find<IndexController>();

View File

@ -16,6 +16,7 @@ import 'package:rogapp/model/gps_data.dart';
import 'package:rogapp/pages/camera/camera_page.dart'; import 'package:rogapp/pages/camera/camera_page.dart';
import 'package:rogapp/pages/camera/custom_camera_view.dart'; import 'package:rogapp/pages/camera/custom_camera_view.dart';
import 'package:rogapp/pages/index/index_controller.dart'; import 'package:rogapp/pages/index/index_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/DatabaseService.dart'; import 'package:rogapp/services/DatabaseService.dart';
import 'package:rogapp/services/destination_service.dart'; import 'package:rogapp/services/destination_service.dart';
@ -44,6 +45,7 @@ import 'package:rogapp/pages/permission/permission.dart';
class DestinationController extends GetxController { class DestinationController extends GetxController {
late LocationSettings locationSettings; // 位置情報の設定を保持する変数です。 late LocationSettings locationSettings; // 位置情報の設定を保持する変数です。
//late TeamController teamController = TeamController();
//Timer? _GPStimer; // GPSタイマーを保持する変数です。 //Timer? _GPStimer; // GPSタイマーを保持する変数です。
var destinationCount = 0.obs; // 目的地の数を保持するReactive変数です。 var destinationCount = 0.obs; // 目的地の数を保持するReactive変数です。
@ -161,11 +163,11 @@ class DestinationController extends GetxController {
} }
} }
// //
//==== Akira .. GPS信号シミュレーション用 ======= ここまで //==== Akira .. GPS信号シミュレーション用 ======= ここまで
*/ */
// ルートをクリアする関数です。 // ルートをクリアする関数です。
void clearRoute() { void clearRoute() {
indexController.routePoints.clear(); indexController.routePoints.clear();
@ -1156,9 +1158,13 @@ class DestinationController extends GetxController {
//await _saveImageFromPath(imageurl); //await _saveImageFromPath(imageurl);
await _saveImageToGallery(imageurl); await _saveImageToGallery(imageurl);
if (indexController.currentUser.isNotEmpty) { if (indexController.currentUser.isNotEmpty) {
double cpNum = destination.cp!; double cpNum = destination.cp!;
//int teamId = indexController.teamId.value; // teamIdを使用
int userId = indexController.currentUser[0]["user"]["id"]; int userId = indexController.currentUser[0]["user"]["id"];
//print("--- Pressed -----"); //print("--- Pressed -----");
String team = indexController.currentUser[0]["user"]['team_name']; String team = indexController.currentUser[0]["user"]['team_name'];
@ -1222,6 +1228,11 @@ class DestinationController extends GetxController {
if (indexController.currentUser.isNotEmpty) { if (indexController.currentUser.isNotEmpty) {
double cpNum = destination.cp!; double cpNum = destination.cp!;
//int teamId = indexController.teamId.value; // teamIdを使用
//Team team0 = teamController.teams[0];
//print("team={team0}");
int userId = indexController.currentUser[0]["user"]["id"]; int userId = indexController.currentUser[0]["user"]["id"];
//print("--- Pressed -----"); //print("--- Pressed -----");
String team = indexController.currentUser[0]["user"]['team_name']; String team = indexController.currentUser[0]["user"]['team_name'];
@ -1238,7 +1249,7 @@ class DestinationController extends GetxController {
// print("------ checkin event $eventCode ------"); // print("------ checkin event $eventCode ------");
ExternalService() ExternalService()
.makeCheckpoint( .makeCheckpoint(
userId, userId, // teamIdを使用
token, token,
formattedDate, formattedDate,
team, team,
@ -1716,6 +1727,8 @@ class DestinationController extends GetxController {
//print('----- %%%%%%%%%%%%%%%%%%%%% ----- $val'); //print('----- %%%%%%%%%%%%%%%%%%%%% ----- $val');
Map<String, dynamic> res = {}; Map<String, dynamic> res = {};
if (val == "wifi" || val == "mobile") { if (val == "wifi" || val == "mobile") {
//int teamId = indexController.teamId.value; // teamIdを使用
String token = indexController.currentUser[0]["token"]; String token = indexController.currentUser[0]["token"];
DatabaseHelper db = DatabaseHelper.instance; DatabaseHelper db = DatabaseHelper.instance;
db.allRogianing().then((value) { db.allRogianing().then((value) {
@ -1725,7 +1738,7 @@ class DestinationController extends GetxController {
} else if (e.rog_action_type == 1) { } else if (e.rog_action_type == 1) {
var datetime = DateTime.fromMicrosecondsSinceEpoch(e.checkintime!); var datetime = DateTime.fromMicrosecondsSinceEpoch(e.checkintime!);
res = await ExternalService().makeCheckpoint( res = await ExternalService().makeCheckpoint(
e.user_id!, e.user_id!, // teamId???
token, token,
getFormatedTime(datetime), getFormatedTime(datetime),
e.team_name!, e.team_name!,
@ -1735,7 +1748,7 @@ class DestinationController extends GetxController {
} else if (e.rog_action_type == 2) { } else if (e.rog_action_type == 2) {
var datetime = DateTime.fromMicrosecondsSinceEpoch(e.checkintime!); var datetime = DateTime.fromMicrosecondsSinceEpoch(e.checkintime!);
res = await ExternalService().makeGoal( res = await ExternalService().makeGoal(
e.user_id!, e.user_id!, // // teamId???
token, token,
e.team_name!, e.team_name!,
e.image!, e.image!,

View File

@ -257,6 +257,7 @@ class DrawerPage extends StatelessWidget {
}, },
), ),
/*
ListTile( ListTile(
leading: const Icon(Icons.developer_mode), leading: const Icon(Icons.developer_mode),
title: const Text('open_settings'), title: const Text('open_settings'),
@ -265,6 +266,8 @@ class DrawerPage extends StatelessWidget {
Get.toNamed('/debug'); // デバッグ画面に遷移 Get.toNamed('/debug'); // デバッグ画面に遷移
}, },
), ),
*/
// ListTile( // ListTile(
// leading: const Icon(Icons.router), // leading: const Icon(Icons.router),
// title: Text("my_route".tr), // title: Text("my_route".tr),

View File

@ -8,6 +8,8 @@ import 'package:rogapp/model/team.dart';
import 'package:rogapp/model/category.dart'; import 'package:rogapp/model/category.dart';
import 'package:rogapp/services/api_service.dart'; import 'package:rogapp/services/api_service.dart';
import '../index/index_controller.dart';
class EntryController extends GetxController { class EntryController extends GetxController {
late ApiService _apiService; late ApiService _apiService;
@ -155,26 +157,44 @@ class EntryController extends GetxController {
return; return;
} }
try { try {
isLoading.value = true;
// Get zekken number
final updatedCategory = await _apiService.getZekkenNumber(selectedCategory.value!.id);
final zekkenNumber = updatedCategory.categoryNumber.toString();
final newEntry = await _apiService.createEntry( final newEntry = await _apiService.createEntry(
selectedTeam.value!.id, selectedTeam.value!.id,
selectedEvent.value!.id, selectedEvent.value!.id,
selectedCategory.value!.id, selectedCategory.value!.id,
selectedDate.value!, selectedDate.value!,
zekkenNumber,
); );
entries.add(newEntry); entries.add(newEntry);
Get.back(); Get.back();
} catch (e) { } catch (e) {
print('Error creating entry: $e'); print('Error creating entry: $e');
Get.snackbar('Error', 'Failed to create entry'); Get.snackbar('Error', 'Failed to create entry');
} finally {
isLoading.value = false;
} }
} }
Future<void> updateEntryAndRefreshMap() async {
await updateEntry();
// エントリーが正常に更新された後、マップをリフレッシュ
final indexController = Get.find<IndexController>();
final eventCode = currentEntry.value?.event.eventName ?? '';
indexController.reloadMap(eventCode);
}
Future<void> updateEntry() async { Future<void> updateEntry() async {
if (currentEntry.value == null) { if (currentEntry.value == null) {
Get.snackbar('Error', 'No entry selected for update'); Get.snackbar('Error', 'No entry selected for update');
return; return;
} }
try { try {
isLoading.value = true;
final updatedEntry = await _apiService.updateEntry( final updatedEntry = await _apiService.updateEntry(
currentEntry.value!.id, currentEntry.value!.id,
currentEntry.value!.team.id, currentEntry.value!.team.id,
@ -190,6 +210,8 @@ class EntryController extends GetxController {
} catch (e) { } catch (e) {
print('Error updating entry: $e'); print('Error updating entry: $e');
Get.snackbar('Error', 'Failed to update entry'); Get.snackbar('Error', 'Failed to update entry');
} finally {
isLoading.value = false;
} }
} }
@ -199,12 +221,15 @@ class EntryController extends GetxController {
return; return;
} }
try { try {
isLoading.value = true;
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);
Get.back(); Get.back();
} catch (e) { } catch (e) {
print('Error deleting entry: $e'); print('Error deleting entry: $e');
Get.snackbar('Error', 'Failed to delete entry'); Get.snackbar('Error', 'Failed to delete entry');
} finally {
isLoading.value = false;
} }
} }

View File

@ -2,10 +2,63 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:rogapp/pages/entry/entry_controller.dart'; import 'package:rogapp/pages/entry/entry_controller.dart';
import 'package:rogapp/routes/app_pages.dart'; import 'package:rogapp/routes/app_pages.dart';
class EntryListPage extends GetView<EntryController> { class EntryListPage extends GetView<EntryController> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('エントリー管理'),
),
body: Obx(() {
if (controller.entries.isEmpty) {
return Center(
child: Text('表示するエントリーがありません。'),
);
}
return ListView.builder(
itemCount: controller.entries.length,
itemBuilder: (context, index) {
final entry = controller.entries[index];
return ListTile(
title: Row(
children: [
Expanded(
child: Text('${_formatDate(entry.date)}: ${entry.event.eventName}'),
),
Text(entry.team.teamName, style: TextStyle(fontWeight: FontWeight.bold)),
],
),
subtitle: Row(
children: [
Expanded(
child: Text('カテゴリー: ${entry.category.categoryName}'),
),
Text('ゼッケン: ${entry.zekkenNumber ?? "未設定"}'),
],
),
onTap: () =>
Get.toNamed(AppPages.ENTRY_DETAIL,
arguments: {'mode': 'edit', 'entry': entry}),
);
},
);
}),
);
}
String _formatDate(DateTime? date) {
if (date == null) {
return '日時未設定';
}
return DateFormat('yyyy-MM-dd').format(date);
}
}
class EntryListPage_old extends GetView<EntryController> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -19,6 +72,10 @@ class EntryListPage extends GetView<EntryController> {
], ],
), ),
body: Obx((){ body: Obx((){
if (controller.isLoading.value) {
return Center(child: CircularProgressIndicator());
}
// エントリーを日付昇順にソート // エントリーを日付昇順にソート
final sortedEntries = controller.entries.toList() final sortedEntries = controller.entries.toList()
..sort((a, b) => (a.date ?? DateTime(0)).compareTo(b.date ?? DateTime(0))); ..sort((a, b) => (a.date ?? DateTime(0)).compareTo(b.date ?? DateTime(0)));

View File

@ -1,17 +1,26 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:rogapp/model/entry.dart'; import 'package:rogapp/model/entry.dart';
import 'package:rogapp/pages/index/index_controller.dart'; import 'package:rogapp/pages/index/index_controller.dart';
import 'package:rogapp/pages/destination/destination_controller.dart';
import 'package:rogapp/services/api_service.dart'; import 'package:rogapp/services/api_service.dart';
import 'package:flutter/material.dart';
class EventEntriesController extends GetxController { class EventEntriesController extends GetxController {
final ApiService _apiService = Get.find<ApiService>(); final ApiService _apiService = Get.find<ApiService>();
final IndexController _indexController = Get.find<IndexController>(); final IndexController _indexController = Get.find<IndexController>();
late final DestinationController _destinationController;
final entries = <Entry>[].obs; final entries = <Entry>[].obs;
final filteredEntries = <Entry>[].obs;
final showTodayEntries = true.obs;
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
// DestinationControllerが登録されていない場合に備えて、lazyPutを使用
Get.lazyPut<DestinationController>(() => DestinationController(), fenix: true);
_destinationController = Get.find<DestinationController>();
fetchEntries(); fetchEntries();
} }
@ -19,14 +28,51 @@ class EventEntriesController extends GetxController {
try { try {
final fetchedEntries = await _apiService.getEntries(); final fetchedEntries = await _apiService.getEntries();
entries.assignAll(fetchedEntries); entries.assignAll(fetchedEntries);
filterEntries();
} catch (e) { } catch (e) {
print('Error fetching entries: $e'); print('Error fetching entries: $e');
// エラー処理を追加 // エラー処理を追加
} }
} }
void filterEntries() {
if (showTodayEntries.value) {
filterEntriesForToday();
} else {
filteredEntries.assignAll(entries);
}
}
void filterEntriesForToday() {
final now = DateTime.now();
filteredEntries.assignAll(entries.where((entry) =>
entry.date?.year == now.year &&
entry.date?.month == now.month &&
entry.date?.day == now.day
));
}
void toggleShowTodayEntries() {
showTodayEntries.toggle();
filterEntries();
}
void refreshMap() {
final tk = _indexController.currentUser[0]["token"];
if (tk != null) {
_destinationController.fixMapBound(tk);
}
}
Future<void> joinEvent(Entry entry) async { Future<void> joinEvent(Entry entry) async {
try { final now = DateTime.now();
bool isToday = entry.date?.year == now.year &&
entry.date?.month == now.month &&
entry.date?.day == now.day;
_indexController.setReferenceMode(!isToday);
_indexController.setSelectedEventName(entry.event.eventName);
final userid = _indexController.currentUser[0]["user"]["id"]; final userid = _indexController.currentUser[0]["user"]["id"];
@ -35,14 +81,24 @@ class EventEntriesController extends GetxController {
_indexController.currentUser[0]["user"]["event_code"] = entry.event.eventName; _indexController.currentUser[0]["user"]["event_code"] = entry.event.eventName;
_indexController.currentUser[0]["user"]["team_name"] = entry.team.teamName; _indexController.currentUser[0]["user"]["team_name"] = entry.team.teamName;
_indexController.currentUser[0]["user"]["group"] = entry.team.category.categoryName; _indexController.currentUser[0]["user"]["group"] = entry.team.category.categoryName;
_indexController.currentUser[0]["user"]["zekken_number"] = entry.team.zekkenNumber; _indexController.currentUser[0]["user"]["zekken_number"] = entry.zekkenNumber;
Get.back(); // エントリー一覧ページを閉じる Get.back(); // エントリー一覧ページを閉じる
//_indexController.isLoading.value = true; //_indexController.isLoading.value = true;
_indexController.reloadMap(entry.event.eventName); // マップをリロード _indexController.reloadMap(entry.event.eventName);
} catch (e) {
print('Error joining event: $e'); refreshMap();
// エラー処理を追加
} if (isToday) {
Get.snackbar('成功', 'イベントに参加しました。',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.green,
colorText: Colors.white);
} else {
Get.snackbar('参照モード', '過去または未来のイベントを参照しています。ロゲの開始やチェックインはできません。',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.orange,
colorText: Colors.white);
}
} }
} }

View File

@ -1,8 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:rogapp/pages/entry/event_entries_controller.dart'; import 'package:rogapp/pages/entry/event_entries_controller.dart';
class EventEntriesPage extends GetView<EventEntriesController> { class EventEntriesPage_old extends GetView<EventEntriesController> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -21,3 +22,81 @@ class EventEntriesPage extends GetView<EventEntriesController> {
); );
} }
} }
class EventEntriesPage extends GetView<EventEntriesController> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Obx(() => Text(controller.showTodayEntries.value ? 'イベント参加' : 'イベント参照')),
),
body: Column(
children: [
Padding(
padding: EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Obx(() => Text(
controller.showTodayEntries.value ? '本日のエントリー' : 'すべてのエントリー',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
)),
Obx(() => Switch(
value: !controller.showTodayEntries.value,
onChanged: (value) {
controller.toggleShowTodayEntries();
},
activeColor: Colors.blue,
)),
],
),
),
Expanded(
child: Obx(() {
if (controller.filteredEntries.isEmpty) {
return Center(
child: Text('表示するエントリーがありません。'),
);
}
return ListView.builder(
itemCount: controller.filteredEntries.length,
itemBuilder: (context, index) {
final entry = controller.filteredEntries[index];
return ListTile(
title: Row(
children: [
Expanded(
child: Text('${_formatDate(entry.date)}: ${entry.event.eventName}'),
),
Text(entry.team.teamName, style: TextStyle(fontWeight: FontWeight.bold)),
],
),
subtitle: Row(
children: [
Expanded(
child: Text('カテゴリー: ${entry.category.categoryName}'),
),
Text('ゼッケン: ${entry.zekkenNumber ?? "未設定"}'),
],
),
onTap: () async {
await controller.joinEvent(entry);
},
);
},
);
}),
),
],
),
);
}
String _formatDate(DateTime? date) {
if (date == null) {
return '日時未設定';
}
return DateFormat('yyyy-MM-dd').format(date);
}
}

View File

@ -82,6 +82,12 @@ class IndexController extends GetxController with WidgetsBindingObserver {
String areaDropdownValue = "-1"; String areaDropdownValue = "-1";
String cateogory = "-all-"; String cateogory = "-all-";
final selectedEventName = 'add_location'.tr.obs;
void setSelectedEventName(String eventName) {
selectedEventName.value = eventName;
}
ConnectivityResult connectionStatus = ConnectivityResult.none; ConnectivityResult connectionStatus = ConnectivityResult.none;
var connectionStatusName = "".obs; var connectionStatusName = "".obs;
final Connectivity _connectivity = Connectivity(); final Connectivity _connectivity = Connectivity();
@ -89,6 +95,9 @@ class IndexController extends GetxController with WidgetsBindingObserver {
final Rx<DateTime> lastUserUpdateTime = DateTime.now().obs; final Rx<DateTime> lastUserUpdateTime = DateTime.now().obs;
RxInt teamId = RxInt(-1); // チームIDを保存するための変数
//late TeamController teamController = TeamController();
/* /*
void updateUserInfo(Map<String, dynamic> newUserInfo) { void updateUserInfo(Map<String, dynamic> newUserInfo) {
currentUser.clear(); currentUser.clear();
@ -97,6 +106,20 @@ class IndexController extends GetxController with WidgetsBindingObserver {
} }
*/ */
final isReferenceMode = false.obs;
void setReferenceMode(bool value) {
isReferenceMode.value = value;
}
bool canStartRoge() {
return !isReferenceMode.value;
}
bool canCheckin() {
return !isReferenceMode.value;
}
void toggleMode() { void toggleMode() {
if (mode.value == 0) { if (mode.value == 0) {
mode += 1; mode += 1;
@ -214,6 +237,8 @@ class IndexController extends GetxController with WidgetsBindingObserver {
print('IndexController onInit called'); // デバッグ用の出力を追加 print('IndexController onInit called'); // デバッグ用の出力を追加
//teamController = Get.find<TeamController>();
} }
Future<void> initializeApiService() async { Future<void> initializeApiService() async {
@ -462,16 +487,27 @@ class IndexController extends GetxController with WidgetsBindingObserver {
// 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。 // 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。
// //
void register(String email, String password, BuildContext context) { void register(String email, String password, String password2, BuildContext context) {
AuthService.register(email, password).then((value) { AuthService.register(email, password,password2).then((value) {
if (value.isNotEmpty) { if (value.isNotEmpty) {
debugPrint("ユーザー登録成功:${email}, ${password}"); debugPrint("ユーザー登録成功:${email}, ${password}");
logManager.addOperationLog("User tried to register new account : ${email} , ${password} ."); logManager.addOperationLog("User tried to register new account : ${email} , ${password} .");
currentUser.clear(); currentUser.clear();
currentUser.add(value); //currentUser.add(value);
isLoading.value = false; isLoading.value = false;
Navigator.pop(context);
// ユーザー登録成功メッセージを表示
Get.snackbar(
'success'.tr,
'user_registration_successful'.tr,
backgroundColor: Colors.green,
colorText: Colors.white,
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
);
//Navigator.pop(context);
Get.toNamed(AppPages.INDEX); Get.toNamed(AppPages.INDEX);
} else { } else {
debugPrint("ユーザー登録失敗:${email}, ${password}"); debugPrint("ユーザー登録失敗:${email}, ${password}");
@ -518,7 +554,7 @@ class IndexController extends GetxController with WidgetsBindingObserver {
} }
*/ */
void changeUser(Map<String, dynamic> value, {bool replace = true}) { void changeUser(Map<String, dynamic> value, {bool replace = true}) async{
currentUser.clear(); currentUser.clear();
currentUser.add(value); currentUser.add(value);
if (replace) { if (replace) {
@ -529,12 +565,29 @@ class IndexController extends GetxController with WidgetsBindingObserver {
if (currentUser.isNotEmpty) { if (currentUser.isNotEmpty) {
rogMode.value = 0; rogMode.value = 0;
restoreGame(); restoreGame();
// チームデータを取得
await fetchTeamData();
} else { } else {
rogMode.value = 1; rogMode.value = 1;
} }
Get.toNamed(AppPages.INDEX); Get.toNamed(AppPages.INDEX);
} }
Future<void> fetchTeamData() async {
try {
Get.put(TeamController());
// \"TeamController\" not found. You need to call \"Get.put(TeamController())\" or \"Get.lazyPut(()=>TeamController())\"
final teamController = Get.find<TeamController>();
await teamController.fetchTeams();
if (teamController.teams.isNotEmpty) {
teamId.value = teamController.teams.first.id;
}
} catch (e) {
print("Error fetching team data: $e");
}
}
loadUserDetailsForToken(String token) async { loadUserDetailsForToken(String token) async {
AuthService.userForToken(token).then((value) { AuthService.userForToken(token).then((value) {
print("----token val-- $value ------"); print("----token val-- $value ------");

View File

@ -78,7 +78,8 @@ class _IndexPageState extends State<IndexPage> {
// //
drawer: DrawerPage(), drawer: DrawerPage(),
appBar: AppBar( appBar: AppBar(
title: Text("add_location".tr), title: Obx(() => Text(indexController.selectedEventName.value)),
//title: Text("add_location".tr),
actions: [ actions: [
// IconButton( // IconButton(
// onPressed: () { // onPressed: () {

View File

@ -3,6 +3,7 @@ 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'; import 'package:rogapp/widgets/helper_dialog.dart';
import 'package:rogapp/services/api_service.dart';
// 要検討:ログインボタンとサインアップボタンの配色を見直すことを検討してください。現在の配色では、ボタンの役割がわかりにくい可能性があります。 // 要検討:ログインボタンとサインアップボタンの配色を見直すことを検討してください。現在の配色では、ボタンの役割がわかりにくい可能性があります。
// エラーメッセージをローカライズすることを検討してください。 // エラーメッセージをローカライズすることを検討してください。
@ -16,9 +17,11 @@ class LoginPage extends StatefulWidget {
class _LoginPageState extends State<LoginPage> { class _LoginPageState extends State<LoginPage> {
//class LoginPage extends StatelessWidget { //class LoginPage extends StatelessWidget {
final IndexController indexController = Get.find<IndexController>(); final IndexController indexController = Get.find<IndexController>();
final ApiService apiService = Get.find<ApiService>();
TextEditingController emailController = TextEditingController(); TextEditingController emailController = TextEditingController();
TextEditingController passwordController = TextEditingController(); TextEditingController passwordController = TextEditingController();
bool _obscureText = true;
@override @override
void initState() { void initState() {
@ -31,6 +34,62 @@ class _LoginPageState extends State<LoginPage> {
}); });
} }
void _showResetPasswordDialog() {
TextEditingController resetEmailController = TextEditingController();
Get.dialog(
AlertDialog(
title: Text('パスワードのリセット'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('パスワードをリセットするメールアドレスを入力してください。'),
SizedBox(height: 10),
TextField(
controller: resetEmailController,
decoration: InputDecoration(
labelText: 'メールアドレス',
border: OutlineInputBorder(),
),
),
],
),
actions: [
TextButton(
child: Text('キャンセル'),
onPressed: () => Get.back(),
),
ElevatedButton(
child: Text('リセット'),
onPressed: () async {
if (resetEmailController.text.isNotEmpty) {
bool success = await apiService.resetPassword(resetEmailController.text);
Get.back();
if (success) {
Get.dialog(
AlertDialog(
title: Text('パスワードリセット'),
content: Text('パスワードリセットメールを送信しました。メールのリンクからパスワードを設定してください。'),
actions: [
TextButton(
child: Text('OK'),
onPressed: () => Get.back(),
),
],
),
);
} else {
Get.snackbar('エラー', 'パスワードリセットに失敗しました。もう一度お試しください。',
snackPosition: SnackPosition.BOTTOM);
}
}
},
),
],
),
);
}
//LoginPage({Key? key}) : super(key: key); //LoginPage({Key? key}) : super(key: key);
@override @override
@ -43,226 +102,243 @@ class _LoginPageState extends State<LoginPage> {
backgroundColor: Colors.white, backgroundColor: Colors.white,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
), ),
body: indexController.currentUser.isEmpty body: GestureDetector(
? SizedBox( onTap: () => FocusScope.of(context).unfocus(),
width: double.infinity, child: indexController.currentUser.isEmpty
? SizedBox(
child: Column( width: double.infinity,
mainAxisAlignment: MainAxisAlignment.start, child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Column(
children: [ children: [
Column( Column(
children: [ children: [
Column( Container(
children: [ height: MediaQuery.of(context).size.height / 6,
Container( decoration: const BoxDecoration(
height: MediaQuery.of(context).size.height / 6, image: DecorationImage(
decoration: const BoxDecoration( image: AssetImage(
image: DecorationImage( 'assets/images/login_image.jpg'))),
image: AssetImage(
'assets/images/login_image.jpg'))),
),
const SizedBox(
height: 5,
),
],
), ),
Padding( const SizedBox(
padding: const EdgeInsets.symmetric(horizontal: 40), height: 5,
child: Column( ),
children: [ ],
makeInput( ),
label: "email".tr, controller: emailController), Padding(
makeInput( padding: const EdgeInsets.symmetric(horizontal: 40),
label: "password".tr, child: Column(
controller: passwordController, children: [
obsureText: true), makeInput(
], label: "email".tr, controller: emailController),
makePasswordInput(
label: "password".tr,
controller: passwordController,
obscureText: _obscureText,
onToggleVisibility: () {
setState(() {
_obscureText = !_obscureText;
});
}),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 40),
child: Container(
padding: const EdgeInsets.only(top: 3, left: 3),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(40),
),
child: Obx(
(() => indexController.isLoading.value == true
? MaterialButton(
minWidth: double.infinity,
height: 60,
onPressed: () {},
color: Colors.grey[400],
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(40)),
child: const CircularProgressIndicator(),
)
: Column(
children: [
MaterialButton(
minWidth: double.infinity,
height: 40,
onPressed: () async {
if (emailController.text.isEmpty ||
passwordController
.text.isEmpty) {
Get.snackbar(
"no_values".tr,
"email_and_password_required"
.tr,
backgroundColor: Colors.red,
colorText: Colors.white,
icon: const Icon(
Icons
.assistant_photo_outlined,
size: 40.0,
color: Colors.blue),
snackPosition:
SnackPosition.TOP,
duration: const Duration(
seconds: 3),
);
return;
}
indexController.isLoading.value =
true;
indexController.login(
emailController.text,
passwordController.text,
context);
},
color: Colors.indigoAccent[400],
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(40)),
child: Text(
"login".tr,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
color: Colors.white70),
),
),
const SizedBox(
height: 5.0,
),
MaterialButton(
minWidth: double.infinity,
height: 36,
onPressed: () {
Get.toNamed(AppPages.REGISTER);
},
color: Colors.redAccent,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(40)),
child: Text(
"sign_up".tr,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
color: Colors.white70),
),
),
],
)),
),
)),
const SizedBox(
height: 3,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
onPressed: _showResetPasswordDialog,
child: Text(
"forgot_password".tr,
style: TextStyle(color: Colors.blue),
), ),
), ),
Padding( ],
padding: const EdgeInsets.symmetric(horizontal: 40), ),
child: Container( Row(
padding: const EdgeInsets.only(top: 3, left: 3), mainAxisAlignment: MainAxisAlignment.center,
decoration: BoxDecoration( children: [
borderRadius: BorderRadius.circular(40), Flexible(
), child: Padding(
child: Obx( padding: const EdgeInsets.all(8.0),
(() => indexController.isLoading.value == true child: Text(
? MaterialButton( "app_developed_by_gifu_dx".tr,
minWidth: double.infinity, style: const TextStyle(
height: 60, overflow: TextOverflow.ellipsis,
onPressed: () {}, fontSize: 10.0),
color: Colors.grey[400], ),
shape: RoundedRectangleBorder( ),
borderRadius:
BorderRadius.circular(40)),
child: const CircularProgressIndicator(),
)
: Column(
children: [
MaterialButton(
minWidth: double.infinity,
height: 40,
onPressed: () async {
if (emailController.text.isEmpty ||
passwordController
.text.isEmpty) {
Get.snackbar(
"no_values".tr,
"email_and_password_required"
.tr,
backgroundColor: Colors.red,
colorText: Colors.white,
icon: const Icon(
Icons
.assistant_photo_outlined,
size: 40.0,
color: Colors.blue),
snackPosition:
SnackPosition.TOP,
duration: const Duration(
seconds: 3),
// backgroundColor: Colors.yellow,
//icon:Image(image:AssetImage("assets/images/dora.png"))
);
return;
}
indexController.isLoading.value =
true;
indexController.login(
emailController.text,
passwordController.text,
context);
},
color: Colors.indigoAccent[400],
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(40)),
child: Text(
"login".tr,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
color: Colors.white70),
),
),
const SizedBox(
height: 5.0,
),
MaterialButton(
minWidth: double.infinity,
height: 36,
onPressed: () {
Get.toNamed(AppPages.REGISTER);
},
color: Colors.redAccent,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(40)),
child: Text(
"sign_up".tr,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
color: Colors.white70),
),
),
const SizedBox(
height: 2.0,
),
MaterialButton(
minWidth: double.infinity,
height: 36,
onPressed: () {
Get.back();
},
color: Colors.grey,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(40)),
child: Text(
"cancel".tr,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
color: Colors.white70),
),
),
],
)),
),
)),
const SizedBox(
height: 3,
), ),
Row( ],
mainAxisAlignment: MainAxisAlignment.center, ),
children: [ const Row(
Flexible( mainAxisAlignment: MainAxisAlignment.center,
child: Padding( children: [
padding: const EdgeInsets.all(8.0), Flexible(
child: Text( child: Padding(
"rogaining_user_need_tosign_up".tr, padding: EdgeInsets.all(8.0),
style: const TextStyle( child: Text(
overflow: TextOverflow.ellipsis, "※第8回と第9回は、岐阜県の令和年度「清流の国ぎふ」SDGs推進ネットワーク連携促進補助金を受けています",
fontSize: 10.0 style: TextStyle(
), fontSize: 10.0,
),
), ),
), ),
], ),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"app_developed_by_gifu_dx".tr,
style: const TextStyle(
overflow: TextOverflow.ellipsis,
fontSize: 10.0),
),
),
),
],
),
const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text(
"※第8回と第9回は、岐阜県の令和年度「清流の国ぎふ」SDGs推進ネットワーク連携促進補助金を受けています",
style: TextStyle(
//overflow: TextOverflow.ellipsis,
fontSize:
10.0, // Consider adjusting the font size if the text is too small.
// Removed overflow: TextOverflow.ellipsis to allow text wrapping.
),
),
),
),
],
), ),
], ],
), ),
], ],
), ),
],
) ),
: TextButton( )
onPressed: () { : TextButton(
indexController.currentUser.clear(); onPressed: () {
}, indexController.logout();
child: const Text("Already Logged in, Click to logout"), Get.offAllNamed(AppPages.LOGIN);
), },
child: const Text("Already Logged in, Click to logout"),
),
),
); );
} }
} }
Widget makePasswordInput({
required String label,
required TextEditingController controller,
required bool obscureText,
required VoidCallback onToggleVisibility,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(
fontSize: 15, fontWeight: FontWeight.w400, color: Colors.black87),
),
const SizedBox(height: 5),
TextField(
controller: controller,
obscureText: obscureText,
decoration: InputDecoration(
contentPadding:
const EdgeInsets.symmetric(vertical: 0, horizontal: 10),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey[400]!),
),
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey[400]!),
),
suffixIcon: IconButton(
icon: Icon(
obscureText ? Icons.visibility : Icons.visibility_off,
color: Colors.grey,
),
onPressed: onToggleVisibility,
),
),
),
const SizedBox(height: 30.0)
],
);
}
Widget makeInput( Widget makeInput(
{label, required TextEditingController controller, obsureText = false}) { {label, required TextEditingController controller, obsureText = false}) {
return Column( return Column(

View File

@ -0,0 +1,381 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rogapp/pages/index/index_controller.dart';
import 'package:rogapp/routes/app_pages.dart';
import 'package:rogapp/widgets/helper_dialog.dart';
import 'package:rogapp/services/api_service.dart';
// 要検討:ログインボタンとサインアップボタンの配色を見直すことを検討してください。現在の配色では、ボタンの役割がわかりにくい可能性があります。
// エラーメッセージをローカライズすることを検討してください。
// ログイン処理中にエラーが発生した場合のエラーハンドリングを追加することをお勧めします。
//
class LoginPage extends StatefulWidget {
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
//class LoginPage extends StatelessWidget {
final IndexController indexController = Get.find<IndexController>();
final ApiService apiService = Get.find<ApiService>();
TextEditingController emailController = TextEditingController();
TextEditingController passwordController = TextEditingController();
bool _obscureText = true;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
showHelperDialog(
'参加するにはユーザー登録が必要です。サインアップからユーザー登録してください。',
'login_page'
);
});
}
void _showResetPasswordDialog() {
TextEditingController resetEmailController = TextEditingController();
Get.dialog(
AlertDialog(
title: Text('パスワードのリセット'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('パスワードをリセットするメールアドレスを入力してください。'),
SizedBox(height: 10),
TextField(
controller: resetEmailController,
decoration: InputDecoration(
labelText: 'メールアドレス',
border: OutlineInputBorder(),
),
),
],
),
actions: [
TextButton(
child: Text('キャンセル'),
onPressed: () => Get.back(),
),
ElevatedButton(
child: Text('リセット'),
onPressed: () async {
if (resetEmailController.text.isNotEmpty) {
bool success = await apiService.resetPassword(resetEmailController.text);
Get.back();
if (success) {
Get.dialog(
AlertDialog(
title: Text('パスワードリセット'),
content: Text('パスワードリセットメールを送信しました。メールのリンクからパスワードを設定してください。'),
actions: [
TextButton(
child: Text('OK'),
onPressed: () => Get.back(),
),
],
),
);
} else {
Get.snackbar('エラー', 'パスワードリセットに失敗しました。もう一度お試しください。',
snackPosition: SnackPosition.BOTTOM);
}
}
},
),
],
),
);
}
//LoginPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: Colors.white,
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.white,
automaticallyImplyLeading: false,
),
body: indexController.currentUser.isEmpty
? SizedBox(
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Column(
children: [
Column(
children: [
Container(
height: MediaQuery.of(context).size.height / 6,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(
'assets/images/login_image.jpg'))),
),
const SizedBox(
height: 5,
),
],
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 40),
child: Column(
children: [
makeInput(
label: "email".tr, controller: emailController),
makePasswordInput(
label: "password".tr,
controller: passwordController,
obscureText: _obscureText,
onToggleVisibility: () {
setState(() {
_obscureText = !_obscureText;
});
}),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 40),
child: Container(
padding: const EdgeInsets.only(top: 3, left: 3),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(40),
),
child: Obx(
(() => indexController.isLoading.value == true
? MaterialButton(
minWidth: double.infinity,
height: 60,
onPressed: () {},
color: Colors.grey[400],
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(40)),
child: const CircularProgressIndicator(),
)
: Column(
children: [
MaterialButton(
minWidth: double.infinity,
height: 40,
onPressed: () async {
if (emailController.text.isEmpty ||
passwordController
.text.isEmpty) {
Get.snackbar(
"no_values".tr,
"email_and_password_required"
.tr,
backgroundColor: Colors.red,
colorText: Colors.white,
icon: const Icon(
Icons
.assistant_photo_outlined,
size: 40.0,
color: Colors.blue),
snackPosition:
SnackPosition.TOP,
duration: const Duration(
seconds: 3),
// backgroundColor: Colors.yellow,
//icon:Image(image:AssetImage("assets/images/dora.png"))
);
return;
}
indexController.isLoading.value =
true;
indexController.login(
emailController.text,
passwordController.text,
context);
},
color: Colors.indigoAccent[400],
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(40)),
child: Text(
"login".tr,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
color: Colors.white70),
),
),
const SizedBox(
height: 5.0,
),
MaterialButton(
minWidth: double.infinity,
height: 36,
onPressed: () {
Get.toNamed(AppPages.REGISTER);
},
color: Colors.redAccent,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(40)),
child: Text(
"sign_up".tr,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
color: Colors.white70),
),
),
],
)),
),
)),
const SizedBox(
height: 3,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
onPressed: _showResetPasswordDialog,
child: Text(
"forgot_password".tr,
style: TextStyle(color: Colors.blue),
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"app_developed_by_gifu_dx".tr,
style: const TextStyle(
overflow: TextOverflow.ellipsis,
fontSize: 10.0),
),
),
),
],
),
const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text(
"※第8回と第9回は、岐阜県の令和年度「清流の国ぎふ」SDGs推進ネットワーク連携促進補助金を受けています",
style: TextStyle(
//overflow: TextOverflow.ellipsis,
fontSize:
10.0, // Consider adjusting the font size if the text is too small.
// Removed overflow: TextOverflow.ellipsis to allow text wrapping.
),
),
),
),
],
),
],
),
],
),
)
: TextButton(
onPressed: () {
indexController.logout();
Get.offAllNamed(AppPages.LOGIN);
},
child: const Text("Already Logged in, Click to logout"),
),
);
}
}
Widget makePasswordInput({
required String label,
required TextEditingController controller,
required bool obscureText,
required VoidCallback onToggleVisibility,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(
fontSize: 15, fontWeight: FontWeight.w400, color: Colors.black87),
),
const SizedBox(height: 5),
TextField(
controller: controller,
obscureText: obscureText,
decoration: InputDecoration(
contentPadding:
const EdgeInsets.symmetric(vertical: 0, horizontal: 10),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey[400]!),
),
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey[400]!),
),
suffixIcon: IconButton(
icon: Icon(
obscureText ? Icons.visibility : Icons.visibility_off,
color: Colors.grey,
),
onPressed: onToggleVisibility,
),
),
),
const SizedBox(height: 30.0)
],
);
}
Widget makeInput(
{label, required TextEditingController controller, obsureText = false}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(
fontSize: 15, fontWeight: FontWeight.w400, color: Colors.black87),
),
const SizedBox(
height: 5,
),
TextField(
controller: controller,
obscureText: obsureText,
decoration: InputDecoration(
contentPadding:
const EdgeInsets.symmetric(vertical: 0, horizontal: 10),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: (Colors.grey[400])!,
),
),
border: OutlineInputBorder(
borderSide: BorderSide(color: (Colors.grey[400])!),
),
),
),
const SizedBox(
height: 30.0,
)
],
);
}

View File

@ -207,7 +207,8 @@ class LoginPopupPage extends StatelessWidget {
) )
: TextButton( : TextButton(
onPressed: () { onPressed: () {
indexController.currentUser.clear(); indexController.logout();
Get.offAllNamed(AppPages.LOGIN);
}, },
child: const Text("Already Logged in, Click to logout"), child: const Text("Already Logged in, Click to logout"),
), ),

View File

@ -16,6 +16,9 @@ class _RegisterPageState extends State<RegisterPage> {
final TextEditingController passwordController = TextEditingController(); final TextEditingController passwordController = TextEditingController();
final TextEditingController confirmPasswordController = TextEditingController(); final TextEditingController confirmPasswordController = TextEditingController();
bool _obscurePassword = true;
bool _obscureConfirmPassword = true;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -60,8 +63,28 @@ class _RegisterPageState extends State<RegisterPage> {
), ),
const SizedBox(height: 30), const SizedBox(height: 30),
makeInput(label: "email".tr, controller: emailController), makeInput(label: "email".tr, controller: emailController),
makeInput(label: "password".tr, controller: passwordController, obsureText: true), //makeInput(label: "password".tr, controller: passwordController, obsureText: true),
makeInput(label: "confirm_password".tr, controller: confirmPasswordController, obsureText: true), //makeInput(label: "confirm_password".tr, controller: confirmPasswordController, obsureText: true),
makePasswordInput(
label: "password".tr,
controller: passwordController,
obscureText: _obscurePassword,
onToggleVisibility: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
),
makePasswordInput(
label: "confirm_password".tr,
controller: confirmPasswordController,
obscureText: _obscureConfirmPassword,
onToggleVisibility: () {
setState(() {
_obscureConfirmPassword = !_obscureConfirmPassword;
});
},
),
const SizedBox(height: 20), const SizedBox(height: 20),
ElevatedButton( ElevatedButton(
onPressed: _handleRegister, onPressed: _handleRegister,
@ -91,6 +114,38 @@ class _RegisterPageState extends State<RegisterPage> {
); );
} }
Widget makePasswordInput({
required String label,
required TextEditingController controller,
required bool obscureText,
required VoidCallback onToggleVisibility,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w400, color: Colors.black87)),
const SizedBox(height: 5),
TextField(
controller: controller,
obscureText: obscureText,
decoration: InputDecoration(
contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 10),
enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey[400]!)),
border: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey[400]!)),
suffixIcon: IconButton(
icon: Icon(
obscureText ? Icons.visibility : Icons.visibility_off,
color: Colors.grey,
),
onPressed: onToggleVisibility,
),
),
),
const SizedBox(height: 20),
],
);
}
void _handleRegister() { void _handleRegister() {
if (passwordController.text != confirmPasswordController.text) { if (passwordController.text != confirmPasswordController.text) {
_showErrorSnackbar("no_match".tr, "password_does_not_match".tr); _showErrorSnackbar("no_match".tr, "password_does_not_match".tr);
@ -106,6 +161,7 @@ class _RegisterPageState extends State<RegisterPage> {
indexController.register( indexController.register(
emailController.text, emailController.text,
passwordController.text, passwordController.text,
confirmPasswordController.text,
context context
); );
// 登録が成功したと仮定し、ログインページに遷移 // 登録が成功したと仮定し、ログインページに遷移

View File

@ -146,11 +146,6 @@ class _TeamDetailPageState extends State<TeamDetailPage> {
onChanged: (value) => controller.updateCategory(value), onChanged: (value) => controller.updateCategory(value),
); );
}), }),
if (mode.value == 'edit')
Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Text('ゼッケン番号: ${controller.selectedTeam.value?.zekkenNumber ?? ""}'),
),
Padding( Padding(
padding: EdgeInsets.symmetric(vertical: 16), padding: EdgeInsets.symmetric(vertical: 16),
child: Text('所有者: ${controller.currentUser.value?.email ?? "未設定"}'), child: Text('所有者: ${controller.currentUser.value?.email ?? "未設定"}'),

View File

@ -35,7 +35,7 @@ class TeamListPage extends GetWidget<TeamController> {
final team = controller.teams[index]; final team = controller.teams[index];
return ListTile( return ListTile(
title: Text(team.teamName), title: Text(team.teamName),
subtitle: Text('${team.category.categoryName} - ${team.zekkenNumber}'), subtitle: Text('${team.category.categoryName} '),
onTap: () async { onTap: () async {
await Get.toNamed( await Get.toNamed(
AppPages.TEAM_DETAIL, AppPages.TEAM_DETAIL,

View File

@ -114,9 +114,14 @@ class ApiService extends GetxService{
List<NewCategory> categories = []; List<NewCategory> categories = [];
for (var categoryJson in categoriesJson) { for (var categoryJson in categoriesJson) {
//print('\nCategory Data:'); try {
//_printDataComparison(categoryJson, NewCategory); //print('\nCategory Data:');
categories.add(NewCategory.fromJson(categoryJson)); //_printDataComparison(categoryJson, NewCategory);
categories.add(NewCategory.fromJson(categoryJson));
}catch(e){
print('Error parsing category: $e');
print('Problematic JSON: $categoryJson');
}
} }
return categories; return categories;
@ -131,6 +136,26 @@ class ApiService extends GetxService{
} }
} }
Future<NewCategory> getZekkenNumber(int categoryId) async {
try {
final response = await http.post(
Uri.parse('$baseUrl/categories-viewset/$categoryId/get_zekken_number/'),
headers: {'Authorization': 'Token $token',"Content-Type": "application/json; charset=UTF-8"},
);
if (response.statusCode == 200) {
final decodedResponse = utf8.decode(response.bodyBytes);
print('User Response body: $decodedResponse');
final categoriesJson = json.decode(decodedResponse);
return NewCategory.fromJson(categoriesJson);
} else {
throw Exception('Failed to increment category number');
}
} catch (e) {
throw Exception('Error incrementing category number: $e');
}
}
Future<User> getCurrentUser() async { Future<User> getCurrentUser() async {
init(); init();
getToken(); getToken();
@ -457,7 +482,7 @@ class ApiService extends GetxService{
} }
} }
Future<Entry> createEntry(int teamId, int eventId, int categoryId, DateTime date) async { Future<Entry> createEntry(int teamId, int eventId, int categoryId, DateTime date,String zekkenNumber) async {
init(); init();
getToken(); getToken();
@ -477,6 +502,7 @@ class ApiService extends GetxService{
'event': eventId, 'event': eventId,
'category': categoryId, 'category': categoryId,
'date': formattedDate, 'date': formattedDate,
'zekken_number':zekkenNumber,
}), }),
); );
@ -508,7 +534,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({
'zekken_number': entry.team.zekkenNumber, 'zekken_number': entry.zekkenNumber,
'event_code': entry.event.eventName, 'event_code': entry.event.eventName,
'group': entry.team.category.categoryName, 'group': entry.team.category.categoryName,
'team_name': entry.team.teamName, 'team_name': entry.team.teamName,
@ -608,4 +634,29 @@ class ApiService extends GetxService{
} }
} }
Future<bool> resetPassword(String email) async {
init();
try {
final response = await http.post(
Uri.parse('$baseUrl/password-reset/'),
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: json.encode({
'email': email,
}),
);
if (response.statusCode == 200) {
return true;
} else {
print('Password reset failed with status code: ${response.statusCode}');
return false;
}
} catch (e) {
print('Error in resetPassword: $e');
return false;
}
}
} }

View File

@ -132,6 +132,7 @@ class AuthService {
return cats; return cats;
} }
// ユーザー登録 // ユーザー登録
// //
/* /*
@ -151,7 +152,7 @@ class AuthService {
*/ */
static Future<Map<String, dynamic>> register( static Future<Map<String, dynamic>> register(
String email, String password) async { String email, String password, String password2) async {
Map<String, dynamic> cats = {}; Map<String, dynamic> cats = {};
String serverUrl = ConstValues.currentServer(); String serverUrl = ConstValues.currentServer();
String url = '$serverUrl/api/register/'; String url = '$serverUrl/api/register/';
@ -161,10 +162,10 @@ class AuthService {
headers: <String, String>{ headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8', 'Content-Type': 'application/json; charset=UTF-8',
}, },
body: jsonEncode(<String, String>{'email': email, 'password': password}), body: jsonEncode(<String, String>{'email': email, 'password': password, 'password2': password2}),
); );
//print(response.body); //print(response.body);
if (response.statusCode == 200) { if (response.statusCode == 201) {
cats = json.decode(utf8.decode(response.bodyBytes)); cats = json.decode(utf8.decode(response.bodyBytes));
} }
return cats; return cats;

View File

@ -4,8 +4,10 @@ import 'package:get/get.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:rogapp/model/rog.dart'; import 'package:rogapp/model/rog.dart';
import 'package:rogapp/model/team.dart';
import 'package:rogapp/pages/destination/destination_controller.dart'; import 'package:rogapp/pages/destination/destination_controller.dart';
import 'package:rogapp/pages/index/index_controller.dart'; import 'package:rogapp/pages/index/index_controller.dart';
import 'package:rogapp/pages/team/team_controller.dart';
import 'package:rogapp/utils/database_gps.dart'; import 'package:rogapp/utils/database_gps.dart';
import 'package:rogapp/utils/database_helper.dart'; import 'package:rogapp/utils/database_helper.dart';
import 'dart:convert'; import 'dart:convert';
@ -32,11 +34,19 @@ class ExternalService {
Future<Map<String, dynamic>> startRogaining() async { Future<Map<String, dynamic>> startRogaining() async {
final IndexController indexController = Get.find<IndexController>(); final IndexController indexController = Get.find<IndexController>();
//final TeamController teamController = Get.find<TeamController>();
debugPrint("== startRogaining =="); debugPrint("== startRogaining ==");
Map<String, dynamic> res = {}; Map<String, dynamic> res = {};
//final teamController = TeamController();
//Team team0 = teamController.teams[0];
//print("team={team0}");
//int teamId = indexController.currentUser[0]["user"]["team"]["id"];
int userId = indexController.currentUser[0]["user"]["id"]; int userId = indexController.currentUser[0]["user"]["id"];
//print("--- Pressed -----"); //print("--- Pressed -----");
String team = indexController.currentUser[0]["user"]['team_name']; String team = indexController.currentUser[0]["user"]['team_name'];
@ -60,8 +70,9 @@ class ExternalService {
} else { } else {
debugPrint("== startRogaining processing=="); debugPrint("== startRogaining processing==");
String url = 'https://rogaining.sumasen.net/gifuroge/start_from_rogapp'; String serverUrl = ConstValues.currentServer();
//print('++++++++$url'); String url = '$serverUrl/gifuroge/start_from_rogapp';
print('++++++++$url');
final http.Response response = await http.post( final http.Response response = await http.post(
Uri.parse(url), Uri.parse(url),
headers: <String, String>{ headers: <String, String>{
@ -82,7 +93,7 @@ class ExternalService {
} }
Future<Map<String, dynamic>> makeCheckpoint( Future<Map<String, dynamic>> makeCheckpoint(
int userId, int userId, // 中身はteamId
String token, String token,
String checkinTime, String checkinTime,
String teamname, String teamname,
@ -93,9 +104,17 @@ class ExternalService {
// print("~~~~ cp is $cp ~~~~"); // print("~~~~ cp is $cp ~~~~");
//print("--cpcp-- ${cp}"); //print("--cpcp-- ${cp}");
Map<String, dynamic> res = {}; Map<String, dynamic> res = {};
String url = 'https://rogaining.sumasen.net/gifuroge/checkin_from_rogapp'; String serverUrl = ConstValues.currentServer();
String url = '$serverUrl/gifuroge/checkin_from_rogapp';
//print('++++++++$url'); //print('++++++++$url');
final IndexController indexController = Get.find<IndexController>(); final IndexController indexController = Get.find<IndexController>();
//final TeamController teamController = Get.find<TeamController>();
// Team team0 = indexController.teamController.teams[0];
// print("team={team0}");
//int teamId = indexController.teamController.teams[0];
if (imageurl != null) { if (imageurl != null) {
if (indexController.connectionStatusName.value != "wifi" && if (indexController.connectionStatusName.value != "wifi" &&
@ -160,7 +179,7 @@ class ExternalService {
'cp_number': cp.toString(), 'cp_number': cp.toString(),
'event_code': eventcode, 'event_code': eventcode,
'image': res["checkinimage"].toString().replaceAll( 'image': res["checkinimage"].toString().replaceAll(
'http://localhost:8100', 'http://rogaining.sumasen.net') 'http://localhost:8100', serverUrl) //'http://rogaining.sumasen.net')
}), }),
); );
var vv = jsonEncode(<String, String>{ var vv = jsonEncode(<String, String>{
@ -168,7 +187,7 @@ class ExternalService {
'cp_number': cp.toString(), 'cp_number': cp.toString(),
'event_code': eventcode, 'event_code': eventcode,
'image': res["checkinimage"].toString().replaceAll( 'image': res["checkinimage"].toString().replaceAll(
'http://localhost:8100', 'http://rogaining.sumasen.net') 'http://localhost:8100', serverUrl) //'http://rogaining.sumasen.net')
}); });
//print("~~~~ api 2 values $vv ~~~~"); //print("~~~~ api 2 values $vv ~~~~");
//print("--json-- $vv"); //print("--json-- $vv");
@ -253,6 +272,9 @@ class ExternalService {
final DestinationController destinationController = final DestinationController destinationController =
Get.find<DestinationController>(); Get.find<DestinationController>();
// チームIDを取得
int teamId = indexController.currentUser[0]["user"]["team"]["id"];
debugPrint("== goal Rogaining =="); debugPrint("== goal Rogaining ==");
//if(indexController.connectionStatusName != "wifi" && indexController.connectionStatusName != "mobile"){ //if(indexController.connectionStatusName != "wifi" && indexController.connectionStatusName != "mobile"){
@ -261,7 +283,7 @@ class ExternalService {
id: 1, id: 1,
team_name: teamname, team_name: teamname,
event_code: eventcode, event_code: eventcode,
user_id: userId, user_id: teamId, //userId,
cp_number: -1, cp_number: -1,
checkintime: DateTime.now().toUtc().microsecondsSinceEpoch, checkintime: DateTime.now().toUtc().microsecondsSinceEpoch,
image: image, image: image,
@ -283,7 +305,7 @@ class ExternalService {
}, },
// 'id', 'user', 'goalimage', 'goaltime', 'team_name', 'event_code','cp_number' // 'id', 'user', 'goalimage', 'goaltime', 'team_name', 'event_code','cp_number'
body: jsonEncode(<String, String>{ body: jsonEncode(<String, String>{
'user': userId.toString(), 'user': teamId.toString(), //userId.toString(),
'team_name': teamname, 'team_name': teamname,
'event_code': eventcode, 'event_code': eventcode,
'goaltime': goalTime, 'goaltime': goalTime,
@ -292,7 +314,8 @@ class ExternalService {
}), }),
); );
String url = 'https://rogaining.sumasen.net/gifuroge/goal_from_rogapp'; //String serverUrl = ConstValues.currentServer();
String url = '$serverUrl/gifuroge/goal_from_rogapp';
//print('++++++++$url'); //print('++++++++$url');
if (response.statusCode == 201) { if (response.statusCode == 201) {
Map<String, dynamic> res = json.decode(utf8.decode(response.bodyBytes)); Map<String, dynamic> res = json.decode(utf8.decode(response.bodyBytes));
@ -308,7 +331,7 @@ class ExternalService {
'event_code': eventcode, 'event_code': eventcode,
'goal_time': goalTime, 'goal_time': goalTime,
'image': res["goalimage"].toString().replaceAll( 'image': res["goalimage"].toString().replaceAll(
'http://localhost:8100', 'http://rogaining.sumasen.net') 'http://localhost:8100', serverUrl) //'http://rogaining.sumasen.net')
}), }),
); );
String rec = jsonEncode(<String, String>{ String rec = jsonEncode(<String, String>{
@ -317,7 +340,7 @@ class ExternalService {
'goal_time': goalTime, 'goal_time': goalTime,
'image': res["goalimage"] 'image': res["goalimage"]
.toString() .toString()
.replaceAll('http://localhost:8100', 'http://rogaining.sumasen.net') .replaceAll('http://localhost:8100', serverUrl) //'http://rogaining.sumasen.net')
}); });
//print("-- json -- $rec"); //print("-- json -- $rec");
//print('----- response2 is $response2 --------'); //print('----- response2 is $response2 --------');
@ -343,8 +366,8 @@ class ExternalService {
indexController.connectionStatusName.value != "mobile") { indexController.connectionStatusName.value != "mobile") {
return Future.value(false); return Future.value(false);
} else { } else {
String url = String serverUrl = ConstValues.currentServer();
'https://rogaining.sumasen.net/gifuroge/remove_checkin_from_rogapp'; String url = '$serverUrl/gifuroge/remove_checkin_from_rogapp';
//print('++++++++$url'); //print('++++++++$url');
final http.Response response = await http.post( final http.Response response = await http.post(
Uri.parse(url), Uri.parse(url),
@ -413,8 +436,8 @@ class ExternalService {
//print("calling push gps step 2 ${payload}"); //print("calling push gps step 2 ${payload}");
String urlS = String serverUrl = ConstValues.currentServer();
'https://rogaining.sumasen.net/gifuroge/get_waypoint_datas_from_rogapp'; String urlS = '$serverUrl/gifuroge/get_waypoint_datas_from_rogapp';
//print('++++++++$url'); //print('++++++++$url');
var url = Uri.parse(urlS); // Replace with your server URL var url = Uri.parse(urlS); // Replace with your server URL
var response = await http.post( var response = await http.post(
@ -440,8 +463,8 @@ class ExternalService {
static Future<Map<String, dynamic>> usersEventCode( static Future<Map<String, dynamic>> usersEventCode(
String teamcode, String password) async { String teamcode, String password) async {
Map<String, dynamic> res = {}; Map<String, dynamic> res = {};
String url = String serverUrl = ConstValues.currentServer();
"https://rogaining.sumasen.net/gifuroge/check_event_code?zekken_number=$teamcode&password=$password"; String url = "$serverUrl/gifuroge/check_event_code?zekken_number=$teamcode&password=$password";
//print('++++++++$url'); //print('++++++++$url');
final http.Response response = final http.Response response =
await http.get(Uri.parse(url), headers: <String, String>{ await http.get(Uri.parse(url), headers: <String, String>{

View File

@ -2,10 +2,10 @@
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 container_svr = "http://container.sumasen.net:8100";
//static const server_uri = "https://rogaining.sumasen.net"; 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";

View File

@ -213,6 +213,9 @@ class StringValues extends Translations{
'reset_explain': 'All data has been reset. You should tap start rogaining to start game.', 'reset_explain': 'All data has been reset. You should tap start rogaining to start game.',
'no_match': 'No match!', 'no_match': 'No match!',
'password_does_not_match':'The passwords you entered were not match.', 'password_does_not_match':'The passwords you entered were not match.',
'forgot_password':'Forgot password',
'user_registration_successful':'Sent activation mail to you. Pls click activation link on the email.',
}, },
'ja_JP': { 'ja_JP': {
'drawer_title':'ロゲイニング参加者はログイン するとチェックポイントが参照 できます', 'drawer_title':'ロゲイニング参加者はログイン するとチェックポイントが参照 できます',
@ -428,6 +431,8 @@ class StringValues extends Translations{
'reset_explain': 'すべてリセットされました。ロゲ開始から再開して下さい。', 'reset_explain': 'すべてリセットされました。ロゲ開始から再開して下さい。',
'no_match': '不一致', 'no_match': '不一致',
'password_does_not_match':'入力したパスワードが一致しません', 'password_does_not_match':'入力したパスワードが一致しません',
'forgot_password':'パスワードを忘れた場合',
'user_registration_successful':'ユーザー認証のメールをお届けしました。メール上のリンクをクリックして正式登録してください。',
}, },
}; };
} }

View File

@ -216,6 +216,7 @@ class BottomSheetNew extends GetView<BottomSheetController> {
); );
saveGameState(); saveGameState();
//int teamId = indexController.teamId.value; // teamIdを使用
await ExternalService().startRogaining(); await ExternalService().startRogaining();
Get.back(); Get.back();
Get.back();// Close the dialog and potentially navigate away Get.back();// Close the dialog and potentially navigate away

381
login_page.dart Normal file
View File

@ -0,0 +1,381 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rogapp/pages/index/index_controller.dart';
import 'package:rogapp/routes/app_pages.dart';
import 'package:rogapp/widgets/helper_dialog.dart';
import 'package:rogapp/services/api_service.dart';
// 要検討:ログインボタンとサインアップボタンの配色を見直すことを検討してください。現在の配色では、ボタンの役割がわかりにくい可能性があります。
// エラーメッセージをローカライズすることを検討してください。
// ログイン処理中にエラーが発生した場合のエラーハンドリングを追加することをお勧めします。
//
class LoginPage extends StatefulWidget {
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
//class LoginPage extends StatelessWidget {
final IndexController indexController = Get.find<IndexController>();
final ApiService apiService = Get.find<ApiService>();
TextEditingController emailController = TextEditingController();
TextEditingController passwordController = TextEditingController();
bool _obscureText = true;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
showHelperDialog(
'参加するにはユーザー登録が必要です。サインアップからユーザー登録してください。',
'login_page'
);
});
}
void _showResetPasswordDialog() {
TextEditingController resetEmailController = TextEditingController();
Get.dialog(
AlertDialog(
title: Text('パスワードのリセット'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('パスワードをリセットするメールアドレスを入力してください。'),
SizedBox(height: 10),
TextField(
controller: resetEmailController,
decoration: InputDecoration(
labelText: 'メールアドレス',
border: OutlineInputBorder(),
),
),
],
),
actions: [
TextButton(
child: Text('キャンセル'),
onPressed: () => Get.back(),
),
ElevatedButton(
child: Text('リセット'),
onPressed: () async {
if (resetEmailController.text.isNotEmpty) {
bool success = await apiService.resetPassword(resetEmailController.text);
Get.back();
if (success) {
Get.dialog(
AlertDialog(
title: Text('パスワードリセット'),
content: Text('パスワードリセットメールを送信しました。メールのリンクからパスワードを設定してください。'),
actions: [
TextButton(
child: Text('OK'),
onPressed: () => Get.back(),
),
],
),
);
} else {
Get.snackbar('エラー', 'パスワードリセットに失敗しました。もう一度お試しください。',
snackPosition: SnackPosition.BOTTOM);
}
}
},
),
],
),
);
}
//LoginPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: Colors.white,
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.white,
automaticallyImplyLeading: false,
),
body: indexController.currentUser.isEmpty
? SizedBox(
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Column(
children: [
Column(
children: [
Container(
height: MediaQuery.of(context).size.height / 6,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(
'assets/images/login_image.jpg'))),
),
const SizedBox(
height: 5,
),
],
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 40),
child: Column(
children: [
makeInput(
label: "email".tr, controller: emailController),
makePasswordInput(
label: "password".tr,
controller: passwordController,
obscureText: _obscureText,
onToggleVisibility: () {
setState(() {
_obscureText = !_obscureText;
});
}),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 40),
child: Container(
padding: const EdgeInsets.only(top: 3, left: 3),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(40),
),
child: Obx(
(() => indexController.isLoading.value == true
? MaterialButton(
minWidth: double.infinity,
height: 60,
onPressed: () {},
color: Colors.grey[400],
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(40)),
child: const CircularProgressIndicator(),
)
: Column(
children: [
MaterialButton(
minWidth: double.infinity,
height: 40,
onPressed: () async {
if (emailController.text.isEmpty ||
passwordController
.text.isEmpty) {
Get.snackbar(
"no_values".tr,
"email_and_password_required"
.tr,
backgroundColor: Colors.red,
colorText: Colors.white,
icon: const Icon(
Icons
.assistant_photo_outlined,
size: 40.0,
color: Colors.blue),
snackPosition:
SnackPosition.TOP,
duration: const Duration(
seconds: 3),
// backgroundColor: Colors.yellow,
//icon:Image(image:AssetImage("assets/images/dora.png"))
);
return;
}
indexController.isLoading.value =
true;
indexController.login(
emailController.text,
passwordController.text,
context);
},
color: Colors.indigoAccent[400],
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(40)),
child: Text(
"login".tr,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
color: Colors.white70),
),
),
const SizedBox(
height: 5.0,
),
MaterialButton(
minWidth: double.infinity,
height: 36,
onPressed: () {
Get.toNamed(AppPages.REGISTER);
},
color: Colors.redAccent,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(40)),
child: Text(
"sign_up".tr,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
color: Colors.white70),
),
),
],
)),
),
)),
const SizedBox(
height: 3,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
onPressed: _showResetPasswordDialog,
child: Text(
"forgot_password".tr,
style: TextStyle(color: Colors.blue),
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"app_developed_by_gifu_dx".tr,
style: const TextStyle(
overflow: TextOverflow.ellipsis,
fontSize: 10.0),
),
),
),
],
),
const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text(
"※第8回と第9回は、岐阜県の令和年度「清流の国ぎふ」SDGs推進ネットワーク連携促進補助金を受けています",
style: TextStyle(
//overflow: TextOverflow.ellipsis,
fontSize:
10.0, // Consider adjusting the font size if the text is too small.
// Removed overflow: TextOverflow.ellipsis to allow text wrapping.
),
),
),
),
],
),
],
),
],
),
)
: TextButton(
onPressed: () {
indexController.logout();
Get.offAllNamed(AppPages.LOGIN);
},
child: const Text("Already Logged in, Click to logout"),
),
);
}
}
Widget makePasswordInput({
required String label,
required TextEditingController controller,
required bool obscureText,
required VoidCallback onToggleVisibility,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(
fontSize: 15, fontWeight: FontWeight.w400, color: Colors.black87),
),
const SizedBox(height: 5),
TextField(
controller: controller,
obscureText: obscureText,
decoration: InputDecoration(
contentPadding:
const EdgeInsets.symmetric(vertical: 0, horizontal: 10),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey[400]!),
),
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey[400]!),
),
suffixIcon: IconButton(
icon: Icon(
obscureText ? Icons.visibility : Icons.visibility_off,
color: Colors.grey,
),
onPressed: onToggleVisibility,
),
),
),
const SizedBox(height: 30.0)
],
);
}
Widget makeInput(
{label, required TextEditingController controller, obsureText = false}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(
fontSize: 15, fontWeight: FontWeight.w400, color: Colors.black87),
),
const SizedBox(
height: 5,
),
TextField(
controller: controller,
obscureText: obsureText,
decoration: InputDecoration(
contentPadding:
const EdgeInsets.symmetric(vertical: 0, horizontal: 10),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: (Colors.grey[400])!,
),
),
border: OutlineInputBorder(
borderSide: BorderSide(color: (Colors.grey[400])!),
),
),
),
const SizedBox(
height: 30.0,
)
],
);
}

View File

@ -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.5+485 version: 4.8.6+486
environment: environment:
sdk: ">=3.2.0 <4.0.0" sdk: ">=3.2.0 <4.0.0"