Compare commits
9 Commits
release_ev
...
66ade1fe09
| Author | SHA1 | Date | |
|---|---|---|---|
| 66ade1fe09 | |||
| 1e0af0b06b | |||
| ee007795b9 | |||
| 3d7a5ae0c1 | |||
| 9c98d3ed53 | |||
| 7f8adeea01 | |||
| 08ffc42cdd | |||
| c81bcef4bc | |||
| 616f87c0c5 |
@ -1,5 +1,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.dvox.gifunavi">
|
||||
<uses-sdk android:minSdkVersion="23" android:targetSdkVersion="34" />
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
|
||||
@ -64,6 +64,7 @@ class LocationService : Service() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Log.d("LocationService", "Android: onCreate.")
|
||||
|
||||
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
|
||||
gpsDatabaseHelper = GpsDatabaseHelper.getInstance(applicationContext)
|
||||
@ -71,6 +72,8 @@ class LocationService : Service() {
|
||||
// 位置情報の権限チェックとGPS有効化の確認を行う
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
|
||||
ContextCompat.checkSelfPermission(this, Manifest.permission.FOREGROUND_SERVICE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d("LocationService", "Android: onCreate : 位置情報の権限チェックとGPS有効化の確認")
|
||||
|
||||
val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
|
||||
val locationRequest = LocationRequest.create().apply {
|
||||
|
||||
@ -52,12 +52,14 @@ class MainActivity: FlutterActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
Log.d("MainActivity", "Android: onCreate.")
|
||||
|
||||
// 位置情報の権限をリクエストする
|
||||
// 位置情報の権限をリクエストする==> main() の前にコールされるので除外 2024-7-19
|
||||
/*
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), PERMISSION_REQUEST_CODE)
|
||||
} else {
|
||||
// startLocationService() // アプリ起動時にLocationServiceを開始する ==> main.dartで制御する。
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
||||
BIN
assets/images/QR_certificate.png
Normal file
BIN
assets/images/QR_certificate.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
BIN
assets/images/QR_gifuroge_stage1.png
Normal file
BIN
assets/images/QR_gifuroge_stage1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 518 B |
@ -11,9 +11,11 @@ PODS:
|
||||
- Flutter
|
||||
- flutter_keyboard_visibility (0.0.1):
|
||||
- Flutter
|
||||
- FMDB (2.7.10):
|
||||
- FMDB/standard (= 2.7.10)
|
||||
- FMDB/standard (2.7.10)
|
||||
- FMDB (2.7.12):
|
||||
- FMDB/standard (= 2.7.12)
|
||||
- FMDB/Core (2.7.12)
|
||||
- FMDB/standard (2.7.12):
|
||||
- FMDB/Core
|
||||
- geolocator_apple (1.2.0):
|
||||
- Flutter
|
||||
- image_gallery_saver (2.0.2):
|
||||
@ -35,7 +37,7 @@ PODS:
|
||||
- qr_code_scanner (0.2.0):
|
||||
- Flutter
|
||||
- MTBBarcodeScanner
|
||||
- ReachabilitySwift (5.2.1)
|
||||
- ReachabilitySwift (5.2.3)
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
@ -121,7 +123,7 @@ SPEC CHECKSUMS:
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
flutter_compass: cbbd285cea1584c7ac9c4e0c3e1f17cbea55e855
|
||||
flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069
|
||||
FMDB: eae540775bf7d0c87a5af926ae37af69effe5a19
|
||||
FMDB: 728731dd336af3936ce00f91d9d8495f5718a0e6
|
||||
geolocator_apple: 9157311f654584b9bb72686c55fc02a97b73f461
|
||||
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
|
||||
image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425
|
||||
@ -132,7 +134,7 @@ SPEC CHECKSUMS:
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
pointer_interceptor_ios: 9280618c0b2eeb80081a343924aa8ad756c21375
|
||||
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
|
||||
ReachabilitySwift: 5ae15e16814b5f9ef568963fb2c87aeb49158c66
|
||||
ReachabilitySwift: 7f151ff156cea1481a8411701195ac6a984f4979
|
||||
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
|
||||
sqflite: 50a33e1d72bd59ee092a519a35d107502757ebed
|
||||
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 60;
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@ -398,11 +398,11 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 480;
|
||||
CURRENT_PROJECT_VERSION = 488;
|
||||
DEVELOPMENT_TEAM = UMNEWT25JR;
|
||||
ENABLE_BITCODE = NO;
|
||||
FLUTTER_BUILD_NAME = 4.8.0;
|
||||
FLUTTER_BUILD_NUMBER = 480;
|
||||
FLUTTER_BUILD_NAME = 4.8.8;
|
||||
FLUTTER_BUILD_NUMBER = 488;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "岐阜ナビ";
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.healthcare-fitness";
|
||||
@ -411,7 +411,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 4.8.0;
|
||||
MARKETING_VERSION = 4.8.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.dvox.gifunavi;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -539,11 +539,11 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 480;
|
||||
CURRENT_PROJECT_VERSION = 488;
|
||||
DEVELOPMENT_TEAM = UMNEWT25JR;
|
||||
ENABLE_BITCODE = NO;
|
||||
FLUTTER_BUILD_NAME = 4.8.0;
|
||||
FLUTTER_BUILD_NUMBER = 480;
|
||||
FLUTTER_BUILD_NAME = 4.8.8;
|
||||
FLUTTER_BUILD_NUMBER = 488;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "岐阜ナビ";
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.healthcare-fitness";
|
||||
@ -552,7 +552,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 4.8.0;
|
||||
MARKETING_VERSION = 4.8.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.dvox.gifunavi;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -571,11 +571,11 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 480;
|
||||
CURRENT_PROJECT_VERSION = 488;
|
||||
DEVELOPMENT_TEAM = UMNEWT25JR;
|
||||
ENABLE_BITCODE = NO;
|
||||
FLUTTER_BUILD_NAME = 4.8.0;
|
||||
FLUTTER_BUILD_NUMBER = 480;
|
||||
FLUTTER_BUILD_NAME = 4.8.8;
|
||||
FLUTTER_BUILD_NUMBER = 488;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "岐阜ナビ";
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.healthcare-fitness";
|
||||
@ -584,7 +584,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 4.8.0;
|
||||
MARKETING_VERSION = 4.8.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.dvox.gifunavi;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
||||
@ -3,7 +3,7 @@ import 'dart:io';
|
||||
//import 'dart:convert';
|
||||
//import 'dart:developer';
|
||||
import 'package:rogapp/model/gps_data.dart';
|
||||
import 'package:rogapp/pages/home/home_page.dart';
|
||||
//import 'package:rogapp/pages/home/home_page.dart';
|
||||
import 'package:rogapp/utils/database_gps.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
|
||||
@ -20,6 +20,7 @@ import 'package:rogapp/pages/index/index_controller.dart';
|
||||
import 'package:rogapp/routes/app_pages.dart';
|
||||
import 'package:rogapp/utils/location_controller.dart';
|
||||
import 'package:rogapp/utils/string_values.dart';
|
||||
import 'package:rogapp/widgets/debug_widget.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
// import 'package:is_lock_screen/is_lock_screen.dart';
|
||||
|
||||
@ -33,7 +34,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
import 'pages/permission/permission.dart';
|
||||
|
||||
import 'package:rogapp/services/api_service.dart';
|
||||
|
||||
Map<String, dynamic> deviceInfo = {};
|
||||
|
||||
@ -99,6 +100,7 @@ void restoreGame() async {
|
||||
pref.getBool("rogaining_counted") ?? false;
|
||||
DestinationController.ready_for_goal =
|
||||
pref.getBool("ready_for_goal") ?? false;
|
||||
await Get.putAsync(() => ApiService().init());
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,7 +129,7 @@ void main() async {
|
||||
FlutterError.presentError(details);
|
||||
Get.log('Flutter error: ${details.exception}');
|
||||
Get.log('Stack trace: ${details.stack}');
|
||||
ErrorService.reportError(details.exception, details.stack ?? StackTrace.current, deviceInfo);
|
||||
ErrorService.reportError(details.exception, details.stack ?? StackTrace.current, deviceInfo, LogManager().operationLogs);
|
||||
};
|
||||
|
||||
//Get.put(LocationController());
|
||||
@ -140,6 +142,7 @@ void main() async {
|
||||
// startMemoryMonitoring(); // 2024-4-8 Akira: メモリ使用量のチェックを開始 See #2810
|
||||
Get.put(SettingsController()); // これを追加
|
||||
|
||||
|
||||
/*
|
||||
runZonedGuarded(() {
|
||||
runApp(const ProviderScope(child: MyApp()));
|
||||
@ -148,12 +151,40 @@ void main() async {
|
||||
});
|
||||
*/
|
||||
|
||||
runApp(const ProviderScope(child: MyApp()));
|
||||
//runApp(HomePage()); // MyApp()からHomePage()に変更
|
||||
//runApp(const MyApp());
|
||||
try {
|
||||
// ApiServiceを初期化
|
||||
//await Get.putAsync(() => ApiService().init());
|
||||
await initServices();
|
||||
|
||||
runApp(const ProviderScope(child: MyApp()));
|
||||
//runApp(HomePage()); // MyApp()からHomePage()に変更
|
||||
//runApp(const MyApp());
|
||||
}catch(e, stackTrace){
|
||||
print('Error during initialization: $e');
|
||||
print('Stack trace: $stackTrace');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initServices() async {
|
||||
print('Starting services ...');
|
||||
try {
|
||||
await Get.putAsync(() => ApiService().init());
|
||||
print('All services started...');
|
||||
}catch(e){
|
||||
print('Error initializing ApiService: $e');
|
||||
}
|
||||
|
||||
try {
|
||||
Get.put(SettingsController());
|
||||
print('SettingsController initialized successfully');
|
||||
} catch (e) {
|
||||
print('Error initializing SettingsController: $e');
|
||||
}
|
||||
|
||||
print('All services started...');
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
Future<void> requestLocationPermission() async {
|
||||
try {
|
||||
final status = await Permission.locationAlways.request();
|
||||
@ -167,7 +198,7 @@ Future<void> requestLocationPermission() async {
|
||||
print('Error requesting location permission: $e');
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
// メモリ使用量の解説:https://qiita.com/hukusuke1007/items/e4e987836412e9bc73b9
|
||||
@ -323,7 +354,7 @@ Future<void> addGPStoDB(double la, double ln) async {
|
||||
is_checkin: 0,
|
||||
created_at: DateTime.now().millisecondsSinceEpoch);
|
||||
var res = await db.insertGps(gps_data);
|
||||
//debugPrint("バックグラウンドでのGPS保存:");
|
||||
debugPrint("バックグラウンドでのGPS保存:");
|
||||
} catch (err) {
|
||||
print("errr ready gps ${err}");
|
||||
return;
|
||||
@ -366,15 +397,49 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
||||
}
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
||||
/*
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await PermissionController.checkAndRequestPermissions();
|
||||
// ウィジェットが構築された後に権限をチェック
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
PermissionController.checkAndRequestPermissions();
|
||||
});
|
||||
*/
|
||||
|
||||
debugPrint("Start MyAppState...");
|
||||
}
|
||||
|
||||
/*
|
||||
void showPermissionRequiredDialog() {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('権限が必要です'),
|
||||
content: Text('このアプリは機能するために位置情報の権限が必要です。設定で権限を許可してください。'),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: Text('設定を開く'),
|
||||
onPressed: () {
|
||||
openAppSettings();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text('アプリを終了'),
|
||||
onPressed: () {
|
||||
// アプリを終了
|
||||
Navigator.of(context).pop();
|
||||
// よりクリーンな終了のために 'flutter_exit_app' のようなプラグインを使用することをお勧めします
|
||||
// 今回は単にすべてのルートをポップします
|
||||
Navigator.of(context).popUntil((route) => false);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
|
||||
70
lib/model/category.dart
Normal file
70
lib/model/category.dart
Normal file
@ -0,0 +1,70 @@
|
||||
// lib/models/category.dart
|
||||
|
||||
class NewCategory {
|
||||
final int id;
|
||||
final String categoryName;
|
||||
final int categoryNumber;
|
||||
final Duration duration;
|
||||
final int numOfMember;
|
||||
final bool family;
|
||||
final bool female;
|
||||
|
||||
NewCategory({
|
||||
required this.id,
|
||||
required this.categoryName,
|
||||
required this.categoryNumber,
|
||||
required this.duration,
|
||||
required this.numOfMember,
|
||||
required this.family,
|
||||
required this.female,
|
||||
});
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is NewCategory &&
|
||||
runtimeType == other.runtimeType &&
|
||||
id == other.id;
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode;
|
||||
|
||||
factory NewCategory.fromJson(Map<String, dynamic> json) {
|
||||
return NewCategory(
|
||||
id: json['id'] ?? 0,
|
||||
categoryName: json['category_name'] ?? 'Unknown Category',
|
||||
categoryNumber: json['category_number'] ?? 0,
|
||||
duration: parseDuration(json['duration']),
|
||||
numOfMember: json['num_of_member'] ?? 1,
|
||||
family: json['family'] ?? 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() {
|
||||
return {
|
||||
'id': id,
|
||||
'category_name': categoryName,
|
||||
'category_number': categoryNumber,
|
||||
'duration': duration.inSeconds,
|
||||
'num_of_member': numOfMember,
|
||||
'family': family,
|
||||
'female': female,
|
||||
};
|
||||
}
|
||||
}
|
||||
51
lib/model/entry.dart
Normal file
51
lib/model/entry.dart
Normal file
@ -0,0 +1,51 @@
|
||||
// lib/models/entry.dart
|
||||
import 'event.dart';
|
||||
import 'event.dart';
|
||||
import 'team.dart';
|
||||
import 'category.dart';
|
||||
|
||||
class Entry {
|
||||
final int id;
|
||||
final Team team;
|
||||
final Event event;
|
||||
final NewCategory category;
|
||||
final DateTime? date;
|
||||
final int zekkenNumber; // 新しく追加
|
||||
final String owner;
|
||||
|
||||
Entry({
|
||||
required this.id,
|
||||
required this.team,
|
||||
required this.event,
|
||||
required this.category,
|
||||
required this.date,
|
||||
required this.zekkenNumber,
|
||||
required this.owner,
|
||||
});
|
||||
|
||||
factory Entry.fromJson(Map<String, dynamic> json) {
|
||||
return Entry(
|
||||
id: json['id'],
|
||||
team: Team.fromJson(json['team']),
|
||||
event: Event.fromJson(json['event']),
|
||||
category: NewCategory.fromJson(json['category']),
|
||||
date: json['date'] != null
|
||||
? DateTime.tryParse(json['date'])
|
||||
: null,
|
||||
zekkenNumber: json['zekken_number'], // 新しく追加
|
||||
owner: json['owner'] is Map ? json['owner']['name'] ?? '' : json['owner'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'team': team.toJson(),
|
||||
'event': event.toJson(),
|
||||
'category': category.toJson(),
|
||||
'date': date?.toIso8601String(),
|
||||
'zekken_number': zekkenNumber, // 新しく追加
|
||||
'owner': owner,
|
||||
};
|
||||
}
|
||||
}
|
||||
33
lib/model/event.dart
Normal file
33
lib/model/event.dart
Normal file
@ -0,0 +1,33 @@
|
||||
// lib/models/event.dart
|
||||
|
||||
class Event {
|
||||
final int id;
|
||||
final String eventName;
|
||||
final DateTime startDatetime;
|
||||
final DateTime endDatetime;
|
||||
|
||||
Event({
|
||||
required this.id,
|
||||
required this.eventName,
|
||||
required this.startDatetime,
|
||||
required this.endDatetime,
|
||||
});
|
||||
|
||||
factory Event.fromJson(Map<String, dynamic> json) {
|
||||
return Event(
|
||||
id: json['id'],
|
||||
eventName: json['event_name'],
|
||||
startDatetime: DateTime.parse(json['start_datetime']),
|
||||
endDatetime: DateTime.parse(json['end_datetime']),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'event_name': eventName,
|
||||
'start_datetime': startDatetime.toIso8601String(),
|
||||
'end_datetime': endDatetime.toIso8601String(),
|
||||
};
|
||||
}
|
||||
}
|
||||
46
lib/model/team.dart
Normal file
46
lib/model/team.dart
Normal file
@ -0,0 +1,46 @@
|
||||
// lib/models/team.dart
|
||||
|
||||
import 'dart:convert';
|
||||
import 'category.dart';
|
||||
import 'user.dart';
|
||||
|
||||
class Team {
|
||||
final int id;
|
||||
// final String zekkenNumber;
|
||||
final String teamName;
|
||||
final NewCategory category;
|
||||
final User owner;
|
||||
|
||||
|
||||
Team({
|
||||
required this.id,
|
||||
// required this.zekkenNumber,
|
||||
required this.teamName,
|
||||
required this.category,
|
||||
required this.owner,
|
||||
});
|
||||
|
||||
factory Team.fromJson(Map<String, dynamic> json) {
|
||||
return Team(
|
||||
id: json['id'] ?? 0,
|
||||
//zekkenNumber: json['zekken_number'] ?? 'Unknown',
|
||||
teamName: json['team_name'] ?? 'Unknown Team',
|
||||
category: json['category'] != null
|
||||
? NewCategory.fromJson(json['category'])
|
||||
: NewCategory(id: 0, categoryName: 'Unknown', categoryNumber: 0, duration: Duration.zero, numOfMember: 1, family: false, female: false),
|
||||
owner: json['owner'] != null
|
||||
? User.fromJson(json['owner'])
|
||||
: User(id: 0, email: 'unknown@example.com', firstname: 'Unknown', lastname: 'User', dateOfBirth: null, female: false, isActive: false),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
//'zekken_number': zekkenNumber,
|
||||
'team_name': teamName,
|
||||
'category': category.toJson(),
|
||||
'owner': owner.toJson(),
|
||||
};
|
||||
}
|
||||
}
|
||||
47
lib/model/user.dart
Normal file
47
lib/model/user.dart
Normal file
@ -0,0 +1,47 @@
|
||||
// lib/models/user.dart
|
||||
|
||||
class User {
|
||||
final int? id;
|
||||
final String? email;
|
||||
final String firstname;
|
||||
final String lastname;
|
||||
final DateTime? dateOfBirth;
|
||||
final bool female;
|
||||
final bool isActive;
|
||||
|
||||
User({
|
||||
this.id,
|
||||
this.email,
|
||||
required this.firstname,
|
||||
required this.lastname,
|
||||
this.dateOfBirth,
|
||||
required this.female,
|
||||
required this.isActive,
|
||||
});
|
||||
|
||||
factory User.fromJson(Map<String, dynamic> json) {
|
||||
return User(
|
||||
id: json['id'],
|
||||
email: json['email'],
|
||||
firstname: json['firstname'] ?? 'Unknown',
|
||||
lastname: json['lastname'] ?? 'Unknown',
|
||||
dateOfBirth: json['date_of_birth'] != null
|
||||
? DateTime.tryParse(json['date_of_birth'])
|
||||
: null,
|
||||
female: json['female'] ?? false,
|
||||
isActive: json['is_active'] ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'email': email,
|
||||
'firstname': firstname,
|
||||
'lastname': lastname,
|
||||
'date_of_birth': dateOfBirth?.toIso8601String(),
|
||||
'female': female,
|
||||
'is_active': isActive,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,12 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert'; // この行を追加または確認
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:rogapp/model/destination.dart';
|
||||
import 'package:rogapp/pages/destination/destination_controller.dart';
|
||||
import 'package:rogapp/pages/index/index_controller.dart';
|
||||
@ -10,6 +14,9 @@ import 'package:rogapp/services/external_service.dart';
|
||||
import 'package:rogapp/utils/const.dart';
|
||||
import 'package:qr_code_scanner/qr_code_scanner.dart';
|
||||
|
||||
import 'package:http/http.dart' as http; // この行を追加
|
||||
|
||||
|
||||
// 関数 getTagText は、特定の条件に基づいて文字列から特定の部分を抽出し、返却するためのものです。
|
||||
// 関数は2つのパラメータを受け取り、条件分岐を通じて結果を返します。
|
||||
//
|
||||
@ -489,7 +496,10 @@ class CameraPage extends StatelessWidget {
|
||||
if (buyPointPhoto == true) {
|
||||
// buyPointPhotoがtrueの場合は、BuyPointCameraウィジェットを返します。
|
||||
//print("--- buy point camera ${destination.toString()}");
|
||||
return BuyPointCamera(destination: destination);
|
||||
//return BuyPointCamera(destination: destination);
|
||||
|
||||
return SwitchableBuyPointCamera(destination: destination);
|
||||
|
||||
//}else if(destination.use_qr_code){
|
||||
// return QRCodeScannerPage(destination: destination);
|
||||
} else if (destinationController.isInRog.value) {
|
||||
@ -613,101 +623,83 @@ class StartRogaining extends StatelessWidget {
|
||||
// 完了ボタンをタップすると、購入ポイントの処理が行われます。
|
||||
// 購入なしボタンをタップすると、購入ポイントがキャンセルされます。
|
||||
//
|
||||
class BuyPointCamera extends StatelessWidget {
|
||||
BuyPointCamera({Key? key, required this.destination}) : super(key: key);
|
||||
class SwitchableBuyPointCamera extends StatefulWidget {
|
||||
final Destination destination;
|
||||
|
||||
Destination destination;
|
||||
const SwitchableBuyPointCamera({Key? key, required this.destination}) : super(key: key);
|
||||
|
||||
DestinationController destinationController =
|
||||
Get.find<DestinationController>();
|
||||
@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(
|
||||
"${destination.sub_loc_id} : ${destination.name}",
|
||||
),
|
||||
title: Text("${widget.destination.sub_loc_id} : ${widget.destination.name}"),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
body: SafeArea(
|
||||
child: Stack(
|
||||
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,
|
||||
if (isQRMode)
|
||||
Column(
|
||||
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],
|
||||
SizedBox(height: screenHeight * 0.1),
|
||||
Center(
|
||||
child: SizedBox(
|
||||
width: qrViewWidth,
|
||||
height: qrViewWidth,
|
||||
child: BuyPointCamera_QR(destination: widget.destination),
|
||||
),
|
||||
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())
|
||||
),
|
||||
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;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -718,130 +710,220 @@ class BuyPointCamera extends StatelessWidget {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
class BuyPointCamera extends StatelessWidget {
|
||||
BuyPointCamera({Key? key, required this.destination}) : super(key: key);
|
||||
|
||||
Destination destination;
|
||||
|
||||
DestinationController destinationController =
|
||||
Get.find<DestinationController>();
|
||||
Get.find<DestinationController>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
//print("in camera purchase 1 ${destinationController.isInRog.value}");
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
title: Text(
|
||||
"${destination.sub_loc_id} : ${destination.name}",
|
||||
),
|
||||
),
|
||||
body: Column(
|
||||
return 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)),
|
||||
),
|
||||
() =>
|
||||
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,
|
||||
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],
|
||||
),
|
||||
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())
|
||||
],
|
||||
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 BuyPointCamera_QR extends StatefulWidget {
|
||||
final Destination destination;
|
||||
|
||||
const BuyPointCamera_QR({Key? key, required this.destination}) : super(key: key);
|
||||
|
||||
@override
|
||||
_BuyPointCamera_QRState createState() => _BuyPointCamera_QRState();
|
||||
}
|
||||
|
||||
|
||||
|
||||
class _BuyPointCamera_QRState extends State<BuyPointCamera_QR> {
|
||||
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
|
||||
QRViewController? controller;
|
||||
bool isQRScanned = false;
|
||||
|
||||
final DestinationController destinationController = Get.find<DestinationController>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return QRView(
|
||||
key: qrKey,
|
||||
onQRViewCreated: _onQRViewCreated,
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
_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 {
|
||||
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 {
|
||||
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/index/index_controller.dart';
|
||||
import 'package:rogapp/widgets/debug_widget.dart';
|
||||
|
||||
class ChangePasswordPage extends StatelessWidget {
|
||||
ChangePasswordPage({Key? key}) : super(key: key);
|
||||
|
||||
LogManager logManager = LogManager();
|
||||
|
||||
IndexController indexController = Get.find<IndexController>();
|
||||
|
||||
TextEditingController oldPasswordController = TextEditingController();
|
||||
@ -20,6 +23,7 @@ class ChangePasswordPage extends StatelessWidget {
|
||||
backgroundColor: Colors.white,
|
||||
leading: IconButton(
|
||||
onPressed: () {
|
||||
logManager.addOperationLog('User clicked cancel button on the drawer');
|
||||
Navigator.pop(context);
|
||||
},
|
||||
icon: const Icon(
|
||||
@ -89,6 +93,7 @@ class ChangePasswordPage extends StatelessWidget {
|
||||
.text.isEmpty ||
|
||||
newPasswordController
|
||||
.text.isEmpty) {
|
||||
logManager.addOperationLog('User tried to login with blank old password ${oldPasswordController.text} or new password ${newPasswordController.text}.');
|
||||
Get.snackbar(
|
||||
"no_values".tr,
|
||||
"values_required".tr,
|
||||
|
||||
@ -16,6 +16,7 @@ import 'package:rogapp/model/gps_data.dart';
|
||||
import 'package:rogapp/pages/camera/camera_page.dart';
|
||||
import 'package:rogapp/pages/camera/custom_camera_view.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/services/DatabaseService.dart';
|
||||
import 'package:rogapp/services/destination_service.dart';
|
||||
@ -44,6 +45,7 @@ import 'package:rogapp/pages/permission/permission.dart';
|
||||
class DestinationController extends GetxController {
|
||||
late LocationSettings locationSettings; // 位置情報の設定を保持する変数です。
|
||||
|
||||
//late TeamController teamController = TeamController();
|
||||
//Timer? _GPStimer; // GPSタイマーを保持する変数です。
|
||||
|
||||
var destinationCount = 0.obs; // 目的地の数を保持するReactive変数です。
|
||||
@ -161,11 +163,11 @@ class DestinationController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//==== Akira .. GPS信号シミュレーション用 ======= ここまで
|
||||
*/
|
||||
|
||||
|
||||
// ルートをクリアする関数です。
|
||||
void clearRoute() {
|
||||
indexController.routePoints.clear();
|
||||
@ -1028,7 +1030,7 @@ class DestinationController extends GetxController {
|
||||
print("An error occurred: $e");
|
||||
// await checkForCheckin();
|
||||
} finally {
|
||||
await Future.delayed(const Duration(seconds: 5)); // 一定時間待機してから再帰呼び出し
|
||||
await Future.delayed(const Duration(seconds: 1)); // 一定時間待機してから再帰呼び出し
|
||||
//print("--- End of checkForCheckin function, calling recursively ---");
|
||||
unawaited( checkForCheckin() );
|
||||
}
|
||||
@ -1156,9 +1158,13 @@ class DestinationController extends GetxController {
|
||||
//await _saveImageFromPath(imageurl);
|
||||
await _saveImageToGallery(imageurl);
|
||||
|
||||
|
||||
|
||||
if (indexController.currentUser.isNotEmpty) {
|
||||
double cpNum = destination.cp!;
|
||||
|
||||
//int teamId = indexController.teamId.value; // teamIdを使用
|
||||
|
||||
int userId = indexController.currentUser[0]["user"]["id"];
|
||||
//print("--- Pressed -----");
|
||||
String team = indexController.currentUser[0]["user"]['team_name'];
|
||||
@ -1222,6 +1228,11 @@ class DestinationController extends GetxController {
|
||||
if (indexController.currentUser.isNotEmpty) {
|
||||
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"];
|
||||
//print("--- Pressed -----");
|
||||
String team = indexController.currentUser[0]["user"]['team_name'];
|
||||
@ -1238,7 +1249,7 @@ class DestinationController extends GetxController {
|
||||
// print("------ checkin event $eventCode ------");
|
||||
ExternalService()
|
||||
.makeCheckpoint(
|
||||
userId,
|
||||
userId, // teamIdを使用
|
||||
token,
|
||||
formattedDate,
|
||||
team,
|
||||
@ -1628,7 +1639,7 @@ class DestinationController extends GetxController {
|
||||
// 地図のイベントリスナーを設定
|
||||
indexController.mapController.mapEventStream.listen((MapEvent mapEvent) {
|
||||
if (mapEvent is MapEventMoveEnd) {
|
||||
indexController.loadLocationsBound();
|
||||
indexController.loadLocationsBound(indexController.currentUser[0]["user"]["event_code"]);
|
||||
}
|
||||
});
|
||||
|
||||
@ -1653,7 +1664,7 @@ class DestinationController extends GetxController {
|
||||
);
|
||||
indexController.currentBound.clear();
|
||||
indexController.currentBound.add(bnds);
|
||||
indexController.loadLocationsBound();
|
||||
indexController.loadLocationsBound(indexController.currentUser[0]["user"]["event_code"]);
|
||||
centerMapToCurrentLocation();
|
||||
}
|
||||
});
|
||||
@ -1716,6 +1727,8 @@ class DestinationController extends GetxController {
|
||||
//print('----- %%%%%%%%%%%%%%%%%%%%% ----- $val');
|
||||
Map<String, dynamic> res = {};
|
||||
if (val == "wifi" || val == "mobile") {
|
||||
//int teamId = indexController.teamId.value; // teamIdを使用
|
||||
|
||||
String token = indexController.currentUser[0]["token"];
|
||||
DatabaseHelper db = DatabaseHelper.instance;
|
||||
db.allRogianing().then((value) {
|
||||
@ -1725,7 +1738,7 @@ class DestinationController extends GetxController {
|
||||
} else if (e.rog_action_type == 1) {
|
||||
var datetime = DateTime.fromMicrosecondsSinceEpoch(e.checkintime!);
|
||||
res = await ExternalService().makeCheckpoint(
|
||||
e.user_id!,
|
||||
e.user_id!, // teamId???
|
||||
token,
|
||||
getFormatedTime(datetime),
|
||||
e.team_name!,
|
||||
@ -1735,7 +1748,7 @@ class DestinationController extends GetxController {
|
||||
} else if (e.rog_action_type == 2) {
|
||||
var datetime = DateTime.fromMicrosecondsSinceEpoch(e.checkintime!);
|
||||
res = await ExternalService().makeGoal(
|
||||
e.user_id!,
|
||||
e.user_id!, // // teamId???
|
||||
token,
|
||||
e.team_name!,
|
||||
e.image!,
|
||||
|
||||
@ -170,7 +170,7 @@ class DestinationMapPage extends StatelessWidget {
|
||||
indexController.currentBound.clear();
|
||||
indexController.currentBound.add(bounds);
|
||||
if (indexController.currentUser.isEmpty) {
|
||||
indexController.loadLocationsBound();
|
||||
indexController.loadLocationsBound(indexController.currentUser[0]["user"]["event_code"]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -5,6 +5,7 @@ import 'package:rogapp/pages/index/index_controller.dart';
|
||||
import 'package:rogapp/routes/app_pages.dart';
|
||||
import 'package:rogapp/services/auth_service.dart';
|
||||
import 'package:rogapp/utils/database_helper.dart';
|
||||
import 'package:rogapp/widgets/debug_widget.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:rogapp/pages/WebView/WebView_page.dart';
|
||||
|
||||
@ -16,6 +17,8 @@ class DrawerPage extends StatelessWidget {
|
||||
|
||||
final IndexController indexController = Get.find<IndexController>();
|
||||
|
||||
LogManager logManager = LogManager();
|
||||
|
||||
// 要検討:URLの起動に失敗した場合のエラーハンドリングが不十分です。適切なエラーメッセージを表示するなどの処理を追加してください。
|
||||
//
|
||||
/*
|
||||
@ -26,6 +29,7 @@ class DrawerPage extends StatelessWidget {
|
||||
|
||||
void _launchURL(BuildContext context,String urlString) async {
|
||||
try {
|
||||
logManager.addOperationLog('User clicked ${urlString} on the drawer');
|
||||
Uri url = Uri.parse(urlString);
|
||||
if (await canLaunchUrl(url)) {
|
||||
await launchUrl(url);
|
||||
@ -52,14 +56,8 @@ class DrawerPage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: Drawer(
|
||||
// Add a ListView to the drawer. This ensures the user can scroll
|
||||
// through the options in the drawer if there isn't enough vertical
|
||||
// space to fit everything.
|
||||
child: Column(
|
||||
children: [
|
||||
// 最初のアイテムは、ユーザーのログイン状態に応じて表示が変わります。
|
||||
// ユーザーがログインしていない場合は、"drawer_title".trというテキストを表示します。
|
||||
// ユーザーがログインしている場合は、ユーザーのメールアドレスを表示します。
|
||||
Container(
|
||||
height: 100,
|
||||
color: Colors.amber,
|
||||
@ -81,9 +79,40 @@ class DrawerPage extends StatelessWidget {
|
||||
),
|
||||
)),
|
||||
),
|
||||
// 次に、IndexControllerのcurrentUserリストが空かどうかに応じて、ログインまたはログアウトのアイテムを表示します。
|
||||
// currentUserリストが空の場合は、"login".trというテキストのログインアイテムを表示し、タップするとAppPages.LOGINにナビゲートします。
|
||||
// currentUserリストが空でない場合は、"logout".trというテキストのログアウトアイテムを表示し、タップするとindexController.logout()を呼び出してログアウトし、AppPages.LOGINにナビゲートします。
|
||||
|
||||
ListTile(
|
||||
leading: Icon(Icons.group),
|
||||
title: Text('チーム管理'),
|
||||
onTap: () {
|
||||
Get.back();
|
||||
Get.toNamed(AppPages.TEAM_LIST);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(Icons.event),
|
||||
title: Text('エントリー管理'),
|
||||
onTap: () {
|
||||
Get.back();
|
||||
Get.toNamed(AppPages.ENTRY_LIST);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(Icons.event),
|
||||
title: Text('イベント参加'),
|
||||
onTap: () {
|
||||
Get.back(); // ドロワーを閉じる
|
||||
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
|
||||
? ListTile(
|
||||
leading: const Icon(Icons.login),
|
||||
@ -100,8 +129,6 @@ class DrawerPage extends StatelessWidget {
|
||||
Get.toNamed(AppPages.LOGIN);
|
||||
},
|
||||
)),
|
||||
// パスワード変更のアイテムは、ユーザーがログインしている場合にのみ表示されます。
|
||||
// "change_password".trというテキストを表示し、タップするとAppPages.CHANGE_PASSWORDにナビゲートします。
|
||||
indexController.currentUser.isNotEmpty
|
||||
? ListTile(
|
||||
leading: const Icon(Icons.password),
|
||||
@ -114,8 +141,6 @@ class DrawerPage extends StatelessWidget {
|
||||
width: 0,
|
||||
height: 0,
|
||||
),
|
||||
// サインアップのアイテムは、ユーザーがログインしていない場合にのみ表示されます。
|
||||
// "sign_up".trというテキストを表示し、タップするとAppPages.REGISTERにナビゲートします。
|
||||
indexController.currentUser.isEmpty
|
||||
? ListTile(
|
||||
leading: const Icon(Icons.person),
|
||||
@ -128,20 +153,19 @@ class DrawerPage extends StatelessWidget {
|
||||
width: 0,
|
||||
height: 0,
|
||||
),
|
||||
// リセットのアイテムは、ユーザーがログインしている場合にのみ表示されます。
|
||||
// タップすると、確認ダイアログを表示し、ユーザーがリセットを確認するとDestinationControllerのresetRogaining()メソッドを呼び出してゲームデータをリセットします。
|
||||
indexController.currentUser.isNotEmpty
|
||||
? ListTile(
|
||||
leading: const Icon(Icons.password),
|
||||
title: const Text("リセット"),
|
||||
title: Text('reset_button'.tr),
|
||||
onTap: () {
|
||||
logManager.addOperationLog('User clicked RESET button on the drawer');
|
||||
// 要検討:リセット操作の確認メッセージをローカライズすることを検討してください。
|
||||
//
|
||||
Get.defaultDialog(
|
||||
title: "リセットしますがよろしいですか?",
|
||||
middleText: "これにより、すべてのゲーム データが削除され、すべての状態が削除されます",
|
||||
textConfirm: "確認する",
|
||||
textCancel: "キャンセルする",
|
||||
title: "reset_title".tr,
|
||||
middleText: "reset_message".tr,
|
||||
textConfirm: "confirm".tr,
|
||||
textCancel: "cancel".tr,
|
||||
onCancel: () => Get.back(),
|
||||
onConfirm: () async {
|
||||
DestinationController destinationController =
|
||||
@ -157,8 +181,8 @@ class DrawerPage extends StatelessWidget {
|
||||
//destinationController.deleteDBDestinations();
|
||||
Get.back();
|
||||
Get.snackbar(
|
||||
"リセット完了",
|
||||
"すべてリセットされました。ロゲ開始から再開して下さい。",
|
||||
"reset_done".tr,
|
||||
"reset_explain".tr,
|
||||
backgroundColor: Colors.green,
|
||||
colorText: Colors.white,
|
||||
duration: const Duration(seconds: 3),
|
||||
@ -171,20 +195,19 @@ class DrawerPage extends StatelessWidget {
|
||||
width: 0,
|
||||
height: 0,
|
||||
),
|
||||
// アカウント削除のアイテムは、ユーザーがログインしている場合にのみ表示されます。
|
||||
// "delete_account".trというテキストを表示し、タップするとAuthService.deleteUser()を呼び出してアカウントを削除し、AppPages.TRAVELにナビゲートします。
|
||||
indexController.currentUser.isNotEmpty
|
||||
? ListTile(
|
||||
leading: const Icon(Icons.delete_forever),
|
||||
title: Text("delete_account".tr),
|
||||
onTap: () {
|
||||
Get.defaultDialog(
|
||||
title: "アカウントを削除しますがよろしいですか?",
|
||||
middleText: "これにより、アカウント情報とすべてのゲーム データが削除され、すべての状態が削除されます",
|
||||
textConfirm: "確認する",
|
||||
textCancel: "キャンセルする",
|
||||
title: "delete_account_title".tr,
|
||||
middleText: "delete_account_middle".tr,
|
||||
textConfirm: "confirm".tr,
|
||||
textCancel: "cancel".tr,
|
||||
onCancel: () => Get.back(),
|
||||
onConfirm: () {
|
||||
logManager.addOperationLog('User clicked Confirm button on the account delete dialog');
|
||||
String token = indexController.currentUser[0]['token'];
|
||||
AuthService.deleteUser(token).then((value) {
|
||||
if (value.isNotEmpty) {
|
||||
@ -205,53 +228,6 @@ class DrawerPage extends StatelessWidget {
|
||||
width: 0,
|
||||
height: 0,
|
||||
),
|
||||
/*
|
||||
// ユーザーデータ削除のアイテムは、ユーザーがログインしている場合にのみ表示されます。
|
||||
// タップすると、AuthService.deleteUser()を呼び出してユーザーデータを削除します。
|
||||
indexController.currentUser.isNotEmpty
|
||||
? ListTile(
|
||||
// 要検討:アカウント削除のリクエストが失敗した場合のエラーハンドリングを追加することをお勧めします。
|
||||
//
|
||||
leading: const Icon(Icons.delete_forever),
|
||||
title: Text("ユーザーデータを削除する".tr),
|
||||
onTap: () {
|
||||
Get.defaultDialog(
|
||||
title: "アカウントを削除しますがよろしいですか?",
|
||||
middleText: "これにより、アカウント情報とすべてのゲーム データが削除され、すべての状態が削除されます",
|
||||
textConfirm: "確認する",
|
||||
textCancel: "キャンセルする",
|
||||
onCancel: () => Get.back(),
|
||||
onConfirm: () {
|
||||
String token = indexController.currentUser[0]['token'];
|
||||
AuthService.deleteUser(token).then((value) {
|
||||
Get.snackbar("ユーザーデータを削除する",
|
||||
"データを削除するためにユーザーの同意が設定されています アプリとサーバーでユーザーデータが削除されました");
|
||||
});
|
||||
});
|
||||
},
|
||||
)
|
||||
: const SizedBox(
|
||||
width: 0,
|
||||
height: 0,
|
||||
),
|
||||
// ListTile(
|
||||
// leading: const Icon(Icons.person),
|
||||
// title: Text("profile".tr),
|
||||
// onTap: (){},
|
||||
// ),
|
||||
// ListTile(
|
||||
// leading: const Icon(Icons.route),
|
||||
// title: Text("recommended_route".tr),
|
||||
// onTap: (){},
|
||||
// ),
|
||||
// ListTile(
|
||||
// leading: const Icon(Icons.favorite_rounded),
|
||||
// title: Text("point_rank".tr),
|
||||
// onTap: (){},
|
||||
// ),
|
||||
*/
|
||||
// "rog_web".trというテキストのアイテムは、ユーザーがログインしている場合にのみ表示されます。
|
||||
// タップすると、_launchURL()メソッドを呼び出して外部のウェブサイトを開きます。
|
||||
indexController.currentUser.isNotEmpty
|
||||
? ListTile(
|
||||
leading: const Icon(Icons.featured_video),
|
||||
@ -264,8 +240,7 @@ class DrawerPage extends StatelessWidget {
|
||||
width: 0,
|
||||
height: 0,
|
||||
),
|
||||
// "privacy".trというテキストのアイテムは、常に表示されます。
|
||||
// タップすると、_launchURL()メソッドを呼び出してプライバシーポリシーのURLを開きます。
|
||||
|
||||
ListTile(
|
||||
leading: const Icon(Icons.privacy_tip),
|
||||
title: Text("privacy".tr),
|
||||
@ -275,21 +250,24 @@ class DrawerPage extends StatelessWidget {
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.settings),
|
||||
title: const Text('設定'),
|
||||
title: Text('open_settings'.tr),
|
||||
onTap: () {
|
||||
Get.back(); // ドロワーを閉じる
|
||||
Get.toNamed(Routes.SETTINGS);
|
||||
},
|
||||
|
||||
),
|
||||
/*
|
||||
ListTile(
|
||||
leading: const Icon(Icons.developer_mode),
|
||||
title: const Text('開発者メニュー'),
|
||||
title: const Text('open_settings'),
|
||||
onTap: () {
|
||||
Get.back(); // ドロワーを閉じる
|
||||
Get.toNamed('/debug'); // デバッグ画面に遷移
|
||||
},
|
||||
),
|
||||
*/
|
||||
|
||||
// ListTile(
|
||||
// leading: const Icon(Icons.router),
|
||||
// title: Text("my_route".tr),
|
||||
|
||||
12
lib/pages/entry/entry_binding.dart
Normal file
12
lib/pages/entry/entry_binding.dart
Normal file
@ -0,0 +1,12 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/entry/entry_controller.dart';
|
||||
import 'package:rogapp/pages/team/member_controller.dart';
|
||||
import 'package:rogapp/services/api_service.dart';
|
||||
|
||||
class EntryBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<ApiService>(() => ApiService());
|
||||
Get.lazyPut<EntryController>(() => EntryController());
|
||||
}
|
||||
}
|
||||
241
lib/pages/entry/entry_controller.dart
Normal file
241
lib/pages/entry/entry_controller.dart
Normal file
@ -0,0 +1,241 @@
|
||||
// lib/entry/entry_controller.dart
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:rogapp/model/entry.dart';
|
||||
import 'package:rogapp/model/event.dart';
|
||||
import 'package:rogapp/model/team.dart';
|
||||
import 'package:rogapp/model/category.dart';
|
||||
import 'package:rogapp/services/api_service.dart';
|
||||
|
||||
import '../index/index_controller.dart';
|
||||
|
||||
class EntryController extends GetxController {
|
||||
late ApiService _apiService;
|
||||
|
||||
final entries = <Entry>[].obs;
|
||||
final events = <Event>[].obs;
|
||||
final teams = <Team>[].obs;
|
||||
final categories = <NewCategory>[].obs;
|
||||
|
||||
final selectedEvent = Rx<Event?>(null);
|
||||
final selectedTeam = Rx<Team?>(null);
|
||||
final selectedCategory = Rx<NewCategory?>(null);
|
||||
final selectedDate = Rx<DateTime?>(null);
|
||||
|
||||
final currentEntry = Rx<Entry?>(null);
|
||||
final isLoading = true.obs;
|
||||
|
||||
@override
|
||||
void onInit() async {
|
||||
super.onInit();
|
||||
await initializeApiService();
|
||||
await loadInitialData();
|
||||
}
|
||||
|
||||
Future<void> initializeApiService() async {
|
||||
try {
|
||||
_apiService = await Get.putAsync(() => ApiService().init());
|
||||
} catch (e) {
|
||||
print('Error initializing ApiService: $e');
|
||||
Get.snackbar('Error', 'Failed to initialize API service');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loadInitialData() async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
await Future.wait([
|
||||
fetchEntries(),
|
||||
fetchEvents(),
|
||||
fetchTeams(),
|
||||
fetchCategories(),
|
||||
]);
|
||||
if (Get.arguments != null && Get.arguments['entry'] != null) {
|
||||
currentEntry.value = Get.arguments['entry'];
|
||||
initializeEditMode(currentEntry.value!);
|
||||
} else {
|
||||
// 新規作成モードの場合、最初のイベントを選択
|
||||
if (events.isNotEmpty) {
|
||||
selectedEvent.value = events.first;
|
||||
selectedDate.value = events.first.startDatetime;
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
print('Error initializing data: $e');
|
||||
Get.snackbar('Error', 'Failed to load initial data');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void initializeEditMode(Entry entry) {
|
||||
currentEntry.value = entry;
|
||||
selectedEvent.value = entry.event;
|
||||
selectedTeam.value = entry.team;
|
||||
selectedCategory.value = entry.category;
|
||||
selectedDate.value = entry.date;
|
||||
}
|
||||
|
||||
void updateEvent(Event? value) {
|
||||
selectedEvent.value = value;
|
||||
if (value != null) {
|
||||
// イベント変更時に日付を調整
|
||||
if (selectedDate.value == null ||
|
||||
selectedDate.value!.isBefore(value.startDatetime) ||
|
||||
selectedDate.value!.isAfter(value.endDatetime)) {
|
||||
selectedDate.value = value.startDatetime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateTeam(Team? value) => selectedTeam.value = value;
|
||||
void updateCategory(NewCategory? value) => selectedCategory.value = value;
|
||||
void updateDate(DateTime value) => selectedDate.value = value;
|
||||
|
||||
/*
|
||||
void updateDate(DateTime value){
|
||||
selectedDate.value = DateFormat('yyyy-MM-dd').format(value!) as DateTime?;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
void _initializeEntryData() {
|
||||
if (currentEntry.value != null) {
|
||||
selectedEvent.value = currentEntry.value!.event;
|
||||
selectedTeam.value = currentEntry.value!.team;
|
||||
selectedCategory.value = currentEntry.value!.category;
|
||||
selectedDate.value = currentEntry.value!.date;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchEntries() async {
|
||||
try {
|
||||
final fetchedEntries = await _apiService.getEntries();
|
||||
entries.assignAll(fetchedEntries);
|
||||
} catch (e) {
|
||||
print('Error fetching entries: $e');
|
||||
Get.snackbar('Error', 'Failed to fetch entries');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchEvents() async {
|
||||
try {
|
||||
final fetchedEvents = await _apiService.getEvents();
|
||||
events.assignAll(fetchedEvents);
|
||||
} catch (e) {
|
||||
print('Error fetching events: $e');
|
||||
Get.snackbar('Error', 'Failed to fetch events');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchTeams() async {
|
||||
try {
|
||||
final fetchedTeams = await _apiService.getTeams();
|
||||
teams.assignAll(fetchedTeams);
|
||||
} catch (e) {
|
||||
print('Error fetching teams: $e');
|
||||
Get.snackbar('Error', 'Failed to fetch team');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchCategories() async {
|
||||
try {
|
||||
final fetchedCategories = await _apiService.getCategories();
|
||||
categories.assignAll(fetchedCategories);
|
||||
} catch (e) {
|
||||
print('Error fetching categories: $e');
|
||||
Get.snackbar('Error', 'Failed to fetch categories');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> createEntry() async {
|
||||
if (selectedEvent.value == null || selectedTeam.value == null ||
|
||||
selectedCategory.value == null || selectedDate.value == null) {
|
||||
Get.snackbar('Error', 'Please fill all fields');
|
||||
return;
|
||||
}
|
||||
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(
|
||||
selectedTeam.value!.id,
|
||||
selectedEvent.value!.id,
|
||||
selectedCategory.value!.id,
|
||||
selectedDate.value!,
|
||||
zekkenNumber,
|
||||
);
|
||||
entries.add(newEntry);
|
||||
Get.back();
|
||||
} catch (e) {
|
||||
print('Error creating entry: $e');
|
||||
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 {
|
||||
if (currentEntry.value == null) {
|
||||
Get.snackbar('Error', 'No entry selected for update');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
isLoading.value = true;
|
||||
final updatedEntry = await _apiService.updateEntry(
|
||||
currentEntry.value!.id,
|
||||
currentEntry.value!.team.id,
|
||||
selectedEvent.value!.id,
|
||||
selectedCategory.value!.id,
|
||||
selectedDate.value!,
|
||||
);
|
||||
final index = entries.indexWhere((entry) => entry.id == updatedEntry.id);
|
||||
if (index != -1) {
|
||||
entries[index] = updatedEntry;
|
||||
}
|
||||
Get.back();
|
||||
} catch (e) {
|
||||
print('Error updating entry: $e');
|
||||
Get.snackbar('Error', 'Failed to update entry');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteEntry() async {
|
||||
if (currentEntry.value == null) {
|
||||
Get.snackbar('Error', 'No entry selected for deletion');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
isLoading.value = true;
|
||||
await _apiService.deleteEntry(currentEntry.value!.id);
|
||||
entries.removeWhere((entry) => entry.id == currentEntry.value!.id);
|
||||
Get.back();
|
||||
} catch (e) {
|
||||
print('Error deleting entry: $e');
|
||||
Get.snackbar('Error', 'Failed to delete entry');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool isOwner() {
|
||||
// Implement logic to check if the current user is the owner of the entry
|
||||
return true; // Placeholder
|
||||
}
|
||||
}
|
||||
154
lib/pages/entry/entry_detail_page.dart
Normal file
154
lib/pages/entry/entry_detail_page.dart
Normal file
@ -0,0 +1,154 @@
|
||||
// lib/pages/entry/entry_detail_page.dart
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/entry/entry_controller.dart';
|
||||
import 'package:rogapp/model/event.dart';
|
||||
import 'package:rogapp/model/category.dart';
|
||||
import 'package:rogapp/model/team.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class EntryDetailPage extends GetView<EntryController> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Map<String, dynamic> arguments = Get.arguments ?? {};
|
||||
final mode = Get.arguments['mode'] as String? ?? 'new';
|
||||
final entry = Get.arguments['entry'];
|
||||
|
||||
if (mode == 'edit' && entry != null) {
|
||||
controller.initializeEditMode(entry);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(mode == 'new' ? 'エントリー登録' : 'エントリー詳細'),
|
||||
),
|
||||
body: Obx(() {
|
||||
if (controller.isLoading.value) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
return Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildDropdown<Event>(
|
||||
label: 'イベント',
|
||||
items: controller.events,
|
||||
selectedId: controller.selectedEvent.value?.id,
|
||||
onChanged: (eventId) => controller.updateEvent(
|
||||
controller.events.firstWhere((e) => e.id == eventId)
|
||||
),
|
||||
getDisplayName: (event) => event.eventName,
|
||||
getId: (event) => event.id,
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
_buildDropdown<Team>(
|
||||
label: 'チーム',
|
||||
items: controller.teams,
|
||||
selectedId: controller.selectedTeam.value?.id,
|
||||
onChanged: (teamId) => controller.updateTeam(
|
||||
controller.teams.firstWhere((t) => t.id == teamId)
|
||||
),
|
||||
getDisplayName: (team) => team.teamName,
|
||||
getId: (team) => team.id,
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
_buildDropdown<NewCategory>(
|
||||
label: 'カテゴリ',
|
||||
items: controller.categories,
|
||||
selectedId: controller.selectedCategory.value?.id,
|
||||
onChanged: (categoryId) => controller.updateCategory(
|
||||
controller.categories.firstWhere((c) => c.id == categoryId)
|
||||
),
|
||||
getDisplayName: (category) => category.categoryName,
|
||||
getId: (category) => category.id,
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
ListTile(
|
||||
title: Text('日付'),
|
||||
subtitle: Text(
|
||||
controller.selectedDate.value != null
|
||||
? DateFormat('yyyy-MM-dd').format(controller.selectedDate.value!)
|
||||
: '日付を選択してください',
|
||||
),
|
||||
onTap: () async {
|
||||
if (controller.selectedEvent.value == null) {
|
||||
Get.snackbar('Error', 'Please select an event first');
|
||||
return;
|
||||
}
|
||||
final DateTime? picked = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: controller.selectedDate.value ?? controller.selectedEvent.value!.startDatetime,
|
||||
firstDate: controller.selectedEvent.value!.startDatetime,
|
||||
lastDate: controller.selectedEvent.value!.endDatetime,
|
||||
);
|
||||
if (picked != null) {
|
||||
controller.updateDate(picked);
|
||||
}
|
||||
},
|
||||
),
|
||||
SizedBox(height: 32),
|
||||
if (mode == 'new')
|
||||
ElevatedButton(
|
||||
child: Text('エントリーを作成'),
|
||||
onPressed: () => controller.createEntry(),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue,
|
||||
minimumSize: Size(double.infinity, 50),
|
||||
),
|
||||
)
|
||||
else
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
child: Text('エントリーを削除'),
|
||||
onPressed: () => controller.deleteEntry(),
|
||||
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),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDropdown<T>({
|
||||
required String label,
|
||||
required List<T> items,
|
||||
required int? selectedId,
|
||||
required void Function(int?) onChanged,
|
||||
required String Function(T) getDisplayName,
|
||||
required int Function(T) getId,
|
||||
}) {
|
||||
return DropdownButtonFormField<int>(
|
||||
decoration: InputDecoration(labelText: label),
|
||||
value: selectedId,
|
||||
items: items.map((item) => DropdownMenuItem<int>(
|
||||
value: getId(item),
|
||||
child: Text(getDisplayName(item)),
|
||||
)).toList(),
|
||||
onChanged: onChanged,
|
||||
);
|
||||
}
|
||||
}
|
||||
104
lib/pages/entry/entry_list_page.dart
Normal file
104
lib/pages/entry/entry_list_page.dart
Normal file
@ -0,0 +1,104 @@
|
||||
// lib/pages/entry/entry_list_page.dart
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:rogapp/pages/entry/entry_controller.dart';
|
||||
import 'package:rogapp/routes/app_pages.dart';
|
||||
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('エントリー管理'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.add),
|
||||
onPressed: () => Get.toNamed(AppPages.ENTRY_DETAIL, arguments: {'mode': 'new'}),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Obx((){
|
||||
if (controller.isLoading.value) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
// エントリーを日付昇順にソート
|
||||
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) {
|
||||
final entry = sortedEntries[index];
|
||||
return ListTile(
|
||||
title: Text(entry.event?.eventName ?? 'イベント未設定'),
|
||||
subtitle: Text(
|
||||
'${entry.team?.teamName ?? 'チーム未設定'} - ${entry.category
|
||||
?.categoryName ?? 'カテゴリ未設定'}'),
|
||||
trailing: Text(
|
||||
entry.date?.toString().substring(0, 10) ?? '日付未設定'),
|
||||
onTap: () =>
|
||||
Get.toNamed(AppPages.ENTRY_DETAIL,
|
||||
arguments: {'mode': 'edit', 'entry': entry}),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
9
lib/pages/entry/event_entries_binding.dart
Normal file
9
lib/pages/entry/event_entries_binding.dart
Normal file
@ -0,0 +1,9 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/entry/event_entries_controller.dart';
|
||||
|
||||
class EventEntriesBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<EventEntriesController>(() => EventEntriesController());
|
||||
}
|
||||
}
|
||||
104
lib/pages/entry/event_entries_controller.dart
Normal file
104
lib/pages/entry/event_entries_controller.dart
Normal file
@ -0,0 +1,104 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/model/entry.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:flutter/material.dart';
|
||||
|
||||
class EventEntriesController extends GetxController {
|
||||
final ApiService _apiService = Get.find<ApiService>();
|
||||
final IndexController _indexController = Get.find<IndexController>();
|
||||
late final DestinationController _destinationController;
|
||||
|
||||
final entries = <Entry>[].obs;
|
||||
final filteredEntries = <Entry>[].obs;
|
||||
final showTodayEntries = true.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
// DestinationControllerが登録されていない場合に備えて、lazyPutを使用
|
||||
Get.lazyPut<DestinationController>(() => DestinationController(), fenix: true);
|
||||
_destinationController = Get.find<DestinationController>();
|
||||
|
||||
fetchEntries();
|
||||
}
|
||||
|
||||
Future<void> fetchEntries() async {
|
||||
try {
|
||||
final fetchedEntries = await _apiService.getEntries();
|
||||
entries.assignAll(fetchedEntries);
|
||||
filterEntries();
|
||||
} catch (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 {
|
||||
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"];
|
||||
|
||||
await _apiService.updateUserInfo(userid,entry);
|
||||
|
||||
_indexController.currentUser[0]["user"]["event_code"] = entry.event.eventName;
|
||||
_indexController.currentUser[0]["user"]["team_name"] = entry.team.teamName;
|
||||
_indexController.currentUser[0]["user"]["group"] = entry.team.category.categoryName;
|
||||
_indexController.currentUser[0]["user"]["zekken_number"] = entry.zekkenNumber;
|
||||
|
||||
Get.back(); // エントリー一覧ページを閉じる
|
||||
//_indexController.isLoading.value = true;
|
||||
_indexController.reloadMap(entry.event.eventName);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
102
lib/pages/entry/event_entries_page.dart
Normal file
102
lib/pages/entry/event_entries_page.dart
Normal file
@ -0,0 +1,102 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:rogapp/pages/entry/event_entries_controller.dart';
|
||||
|
||||
class EventEntriesPage_old extends GetView<EventEntriesController> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('イベント参加')),
|
||||
body: Obx(() => ListView.builder(
|
||||
itemCount: controller.entries.length,
|
||||
itemBuilder: (context, index) {
|
||||
final entry = controller.entries[index];
|
||||
return ListTile(
|
||||
title: Text(entry.event.eventName),
|
||||
subtitle: Text('${entry.category.categoryName} - ${entry.date}'),
|
||||
onTap: () => controller.joinEvent(entry),
|
||||
);
|
||||
},
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,16 +10,24 @@ import 'package:geolocator/geolocator.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:rogapp/model/destination.dart';
|
||||
import 'package:rogapp/model/entry.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/services/auth_service.dart';
|
||||
import 'package:rogapp/services/location_service.dart';
|
||||
import 'package:rogapp/utils/database_helper.dart';
|
||||
import 'package:rogapp/widgets/debug_widget.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../../main.dart';
|
||||
import 'package:rogapp/services/api_service.dart';
|
||||
import 'package:rogapp/model/user.dart';
|
||||
|
||||
|
||||
import 'package:rogapp/main.dart';
|
||||
|
||||
import 'package:rogapp/widgets/helper_dialog.dart';
|
||||
|
||||
class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
List<GeoJSONFeatureCollection> locations = <GeoJSONFeatureCollection>[].obs;
|
||||
List<GeoJSONFeature> currentFeature = <GeoJSONFeature>[].obs;
|
||||
@ -52,8 +60,13 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
MapController mapController = MapController();
|
||||
MapController rogMapController = MapController();
|
||||
|
||||
LogManager logManager = LogManager();
|
||||
|
||||
String? userToken;
|
||||
|
||||
//late final ApiService _apiService;
|
||||
final ApiService _apiService = Get.find<ApiService>();
|
||||
|
||||
// mode = 0 is map mode, mode = 1 list mode
|
||||
var mode = 0.obs;
|
||||
|
||||
@ -69,11 +82,44 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
String areaDropdownValue = "-1";
|
||||
String cateogory = "-all-";
|
||||
|
||||
final selectedEventName = 'add_location'.tr.obs;
|
||||
|
||||
void setSelectedEventName(String eventName) {
|
||||
selectedEventName.value = eventName;
|
||||
}
|
||||
|
||||
ConnectivityResult connectionStatus = ConnectivityResult.none;
|
||||
var connectionStatusName = "".obs;
|
||||
final Connectivity _connectivity = Connectivity();
|
||||
late StreamSubscription<ConnectivityResult> _connectivitySubscription;
|
||||
|
||||
final Rx<DateTime> lastUserUpdateTime = DateTime.now().obs;
|
||||
|
||||
RxInt teamId = RxInt(-1); // チームIDを保存するための変数
|
||||
|
||||
//late TeamController teamController = TeamController();
|
||||
/*
|
||||
void updateUserInfo(Map<String, dynamic> newUserInfo) {
|
||||
currentUser.clear();
|
||||
currentUser.add(newUserInfo);
|
||||
lastUserUpdateTime.value = DateTime.now();
|
||||
}
|
||||
*/
|
||||
|
||||
final isReferenceMode = false.obs;
|
||||
|
||||
void setReferenceMode(bool value) {
|
||||
isReferenceMode.value = value;
|
||||
}
|
||||
|
||||
bool canStartRoge() {
|
||||
return !isReferenceMode.value;
|
||||
}
|
||||
|
||||
bool canCheckin() {
|
||||
return !isReferenceMode.value;
|
||||
}
|
||||
|
||||
void toggleMode() {
|
||||
if (mode.value == 0) {
|
||||
mode += 1;
|
||||
@ -155,14 +201,15 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
return WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
child: AlertDialog(
|
||||
title: Text('位置情報の許可が必要です'),
|
||||
content: Text('設定>プライバシーとセキュリティ>位置情報サービス を開いて、岐阜ナビを探し、「位置情報の許可」を「常に」にして下さい。'),
|
||||
title: Text('location_permission_needed_title'.tr),
|
||||
content: Text('location_permission_needed_main'.tr),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
logManager.addOperationLog("User tapped confirm button for location permission required.");
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text('OK'),
|
||||
child: Text('confirm'.tr),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -186,11 +233,22 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
WidgetsBinding.instance?.addObserver(this);
|
||||
_startLocationService(); // アプリ起動時にLocationServiceを開始する
|
||||
|
||||
initializeApiService();
|
||||
|
||||
print('IndexController onInit called'); // デバッグ用の出力を追加
|
||||
|
||||
//teamController = Get.find<TeamController>();
|
||||
|
||||
}
|
||||
|
||||
|
||||
Future<void> initializeApiService() async {
|
||||
if (currentUser.isNotEmpty) {
|
||||
// 既にログインしている場合
|
||||
await Get.putAsync(() => ApiService().init());
|
||||
//await Get.putAsync(() => ApiService().init());
|
||||
// 必要に応じて追加の初期化処理
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
void checkPermission()
|
||||
@ -230,6 +288,7 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
void _startLocationService() async {
|
||||
const platform = MethodChannel('location');
|
||||
try {
|
||||
logManager.addOperationLog("Called start location service.");
|
||||
await platform.invokeMethod('startLocationService');
|
||||
} on PlatformException catch (e) {
|
||||
print("Failed to start location service: '${e.message}'.");
|
||||
@ -239,6 +298,7 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
void _stopLocationService() async {
|
||||
const platform = MethodChannel('location');
|
||||
try {
|
||||
logManager.addOperationLog("Called stop location service.");
|
||||
await platform.invokeMethod('stopLocationService');
|
||||
} on PlatformException catch (e) {
|
||||
print("Failed to stop location service: '${e.message}'.");
|
||||
@ -301,24 +361,40 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
}
|
||||
}
|
||||
|
||||
logManager.addOperationLog("Called boundsFromLatLngList (${x1!},${y1!})-(${x0!},${y0!}).");
|
||||
|
||||
return LatLngBounds(LatLng(x1!, y1!), LatLng(x0!, y0!));
|
||||
}
|
||||
|
||||
// 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。
|
||||
//
|
||||
void login(String email, String password, BuildContext context) {
|
||||
AuthService.login(email, password).then((value) {
|
||||
void login(String email, String password, BuildContext context) async{
|
||||
|
||||
AuthService.login(email, password).then((value) async {
|
||||
print("------- logged in user details ######## $value ###### --------");
|
||||
if (value.isNotEmpty) {
|
||||
logManager.addOperationLog("User logged in : ${value}.");
|
||||
|
||||
// Navigator.pop(context);
|
||||
print("--------- user details login ----- $value");
|
||||
//await Future.delayed(const Duration(milliseconds: 500)); // Added Akira:2024-4-6, #2800
|
||||
changeUser(value);
|
||||
|
||||
// ログイン成功後、api_serviceを初期化
|
||||
await Get.putAsync(() => ApiService().init());
|
||||
|
||||
// ユーザー情報の完全性をチェック
|
||||
if (await checkUserInfoComplete()) {
|
||||
Get.offAllNamed(AppPages.INDEX);
|
||||
} else {
|
||||
Get.offAllNamed(AppPages.USER_DETAILS_EDIT);
|
||||
}
|
||||
|
||||
} else {
|
||||
logManager.addOperationLog("User failed login : ${email} , ${password}.");
|
||||
isLoading.value = false;
|
||||
Get.snackbar(
|
||||
"ログイン失敗",
|
||||
"ログインIDかパスワードを確認して下さい。",
|
||||
"login_failed".tr,
|
||||
"check_login_id_or_password".tr,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
icon: const Icon(Icons.error, size: 40.0, color: Colors.blue),
|
||||
@ -331,6 +407,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(
|
||||
@ -340,6 +424,7 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
AuthService.changePassword(oldpassword, newpassword, token).then((value) {
|
||||
////print("------- change password ######## $value ###### --------");
|
||||
if (value.isNotEmpty) {
|
||||
logManager.addOperationLog("User successed to change password : ${oldpassword} , ${newpassword}.");
|
||||
isLoading.value = false;
|
||||
Navigator.pop(context);
|
||||
if (rogMode.value == 1) {
|
||||
@ -348,6 +433,7 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
switchPage(AppPages.INDEX);
|
||||
}
|
||||
} else {
|
||||
logManager.addOperationLog("User failed to change password : ${oldpassword} , ${newpassword}.");
|
||||
Get.snackbar(
|
||||
'failed'.tr,
|
||||
'password_change_failed_please_try_again'.tr,
|
||||
@ -383,6 +469,7 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
*/
|
||||
|
||||
void logout() async {
|
||||
logManager.addOperationLog("User logout : ${currentUser} .");
|
||||
saveGameState();
|
||||
locations.clear();
|
||||
DatabaseHelper db = DatabaseHelper.instance;
|
||||
@ -400,24 +487,40 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
|
||||
// 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。
|
||||
//
|
||||
void register(String email, String password, BuildContext context) {
|
||||
AuthService.register(email, password).then((value) {
|
||||
void register(String email, String password, String password2, BuildContext context) {
|
||||
AuthService.register(email, password,password2).then((value) {
|
||||
if (value.isNotEmpty) {
|
||||
debugPrint("ユーザー登録成功:${email}, ${password}");
|
||||
logManager.addOperationLog("User tried to register new account : ${email} , ${password} .");
|
||||
|
||||
currentUser.clear();
|
||||
currentUser.add(value);
|
||||
//currentUser.add(value);
|
||||
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);
|
||||
} else {
|
||||
debugPrint("ユーザー登録失敗:${email}, ${password}");
|
||||
logManager.addOperationLog("User failed to register new account : ${email} , ${password} .");
|
||||
isLoading.value = false;
|
||||
Get.snackbar(
|
||||
'failed'.tr,
|
||||
'user_registration_failed_please_try_again'.tr,
|
||||
'failed'.tr, // 失敗
|
||||
'user_registration_failed_please_try_again'.tr, // ユーザー登録に失敗しました。
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
icon: const Icon(Icons.error, size: 40.0, color: Colors.blue),
|
||||
snackPosition: SnackPosition.TOP,
|
||||
duration: const Duration(milliseconds: 800),
|
||||
duration: const Duration(seconds: 3),
|
||||
//backgroundColor: Colors.yellow,
|
||||
//icon:Image(image:AssetImage("assets/images/dora.png"))
|
||||
);
|
||||
@ -451,23 +554,40 @@ 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.add(value);
|
||||
if (replace) {
|
||||
saveToDevice(currentUser[0]["token"]);
|
||||
}
|
||||
isLoading.value = false;
|
||||
loadLocationsBound();
|
||||
loadLocationsBound( currentUser[0]["user"]["event_code"]);
|
||||
if (currentUser.isNotEmpty) {
|
||||
rogMode.value = 0;
|
||||
restoreGame();
|
||||
|
||||
// チームデータを取得
|
||||
await fetchTeamData();
|
||||
} else {
|
||||
rogMode.value = 1;
|
||||
}
|
||||
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 {
|
||||
AuthService.userForToken(token).then((value) {
|
||||
print("----token val-- $value ------");
|
||||
@ -543,7 +663,7 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
// 要検討:Future.delayedを使用して非同期処理を待たずに先に進むようにしていますが、
|
||||
// これによってメモリリークが発生する可能性があります。非同期処理の結果を適切に処理することを検討してください。
|
||||
//
|
||||
void loadLocationsBound() async {
|
||||
void loadLocationsBound(String eventCode) async {
|
||||
if (isCustomAreaSelected.value == true) {
|
||||
return;
|
||||
}
|
||||
@ -575,12 +695,13 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
currentBound.clear();
|
||||
currentBound.add(bounds);
|
||||
|
||||
isLoading.value = true; // ローディング状態をtrueに設定
|
||||
//isLoading.value = true; // ローディング状態をtrueに設定
|
||||
|
||||
//print("bounds --- (${bounds.southWest.latitude},${bounds.southWest.longitude}),(${bounds.northWest.latitude},${bounds.northWest.longitude}),(${bounds.northEast.latitude},${bounds.northEast.longitude}),(${bounds.southEast.latitude},${bounds.southEast.longitude})");
|
||||
|
||||
// 要検討:APIからのレスポンスがnullの場合のエラーハンドリングが不十分です。適切なエラーメッセージを表示するなどの処理を追加してください。
|
||||
try {
|
||||
final eventCode = currentUser[0]["user"]["event_code"];
|
||||
final value = await LocationService.loadLocationsBound(
|
||||
bounds.southWest.latitude,
|
||||
bounds.southWest.longitude,
|
||||
@ -590,7 +711,8 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
bounds.northEast.longitude,
|
||||
bounds.southEast.latitude,
|
||||
bounds.southEast.longitude,
|
||||
cat
|
||||
cat,
|
||||
eventCode
|
||||
);
|
||||
/*
|
||||
if (value == null) {
|
||||
@ -694,4 +816,30 @@ class IndexController extends GetxController with WidgetsBindingObserver {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
void reloadMap( String 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です。このクラスは、アプリのメインページを表すウィジェットです。
|
||||
//
|
||||
class IndexPage extends GetView<IndexController> {
|
||||
IndexPage({Key? key}) : super(key: key);
|
||||
import 'package:rogapp/widgets/helper_dialog.dart';
|
||||
|
||||
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のインスタンスを取得しています。
|
||||
//
|
||||
@ -46,7 +78,8 @@ class IndexPage extends GetView<IndexController> {
|
||||
//
|
||||
drawer: DrawerPage(),
|
||||
appBar: AppBar(
|
||||
title: Text("add_location".tr),
|
||||
title: Obx(() => Text(indexController.selectedEventName.value)),
|
||||
//title: Text("add_location".tr),
|
||||
actions: [
|
||||
// IconButton(
|
||||
// onPressed: () {
|
||||
|
||||
@ -2,18 +2,95 @@ 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 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 ApiService apiService = Get.find<ApiService>();
|
||||
|
||||
TextEditingController emailController = TextEditingController();
|
||||
TextEditingController passwordController = TextEditingController();
|
||||
bool _obscureText = true;
|
||||
|
||||
LoginPage({Key? key}) : super(key: key);
|
||||
@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) {
|
||||
@ -25,226 +102,243 @@ class LoginPage extends StatelessWidget {
|
||||
backgroundColor: Colors.white,
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
body: indexController.currentUser.isEmpty
|
||||
? SizedBox(
|
||||
width: double.infinity,
|
||||
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
body: GestureDetector(
|
||||
onTap: () => FocusScope.of(context).unfocus(),
|
||||
child: indexController.currentUser.isEmpty
|
||||
? SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
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,
|
||||
),
|
||||
],
|
||||
Container(
|
||||
height: MediaQuery.of(context).size.height / 6,
|
||||
decoration: const BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage(
|
||||
'assets/images/login_image.jpg'))),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
child: Column(
|
||||
children: [
|
||||
makeInput(
|
||||
label: "email".tr, controller: emailController),
|
||||
makeInput(
|
||||
label: "password".tr,
|
||||
controller: passwordController,
|
||||
obsureText: true),
|
||||
],
|
||||
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),
|
||||
);
|
||||
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(
|
||||
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: 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: [
|
||||
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),
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
"rogaining_user_need_tosign_up".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回は、岐阜県の令和5年度「清流の国ぎふ」SDGs推進ネットワーク連携促進補助金を受けています",
|
||||
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回は、岐阜県の令和5年度「清流の国ぎふ」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.currentUser.clear();
|
||||
},
|
||||
child: const Text("Already Logged in, Click to logout"),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: 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(
|
||||
|
||||
381
lib/pages/login/login_page.dart_backup
Normal file
381
lib/pages/login/login_page.dart_backup
Normal 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回は、岐阜県の令和5年度「清流の国ぎふ」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,
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -207,7 +207,8 @@ class LoginPopupPage extends StatelessWidget {
|
||||
)
|
||||
: TextButton(
|
||||
onPressed: () {
|
||||
indexController.currentUser.clear();
|
||||
indexController.logout();
|
||||
Get.offAllNamed(AppPages.LOGIN);
|
||||
},
|
||||
child: const Text("Already Logged in, Click to logout"),
|
||||
),
|
||||
|
||||
@ -4,21 +4,174 @@ import 'package:get/get.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:rogapp/services/location_service.dart';
|
||||
import 'dart:developer' as developer;
|
||||
import 'dart:async';
|
||||
|
||||
|
||||
class PermissionController {
|
||||
|
||||
/*
|
||||
static Future<bool> checkLocationPermissions() async {
|
||||
debugPrint("(gifunavi)== checkLocationPermissions ==");
|
||||
final alwaysPermission = await Permission.locationAlways.status;
|
||||
final whenInUsePermission = await Permission.locationWhenInUse.status;
|
||||
final locationPermission = await Permission.location.status;
|
||||
static bool _isRequestingPermission = false;
|
||||
static Completer<bool>? _permissionCompleter;
|
||||
|
||||
return (alwaysPermission == PermissionStatus.granted || whenInUsePermission == PermissionStatus.granted) &&
|
||||
(locationPermission == PermissionStatus.granted);
|
||||
static Future<bool> checkLocationPermissions() async {
|
||||
final locationPermission = await Permission.location.status;
|
||||
final whenInUsePermission = await Permission.locationWhenInUse.status;
|
||||
final alwaysPermission = await Permission.locationAlways.status;
|
||||
|
||||
return locationPermission == PermissionStatus.granted &&
|
||||
(whenInUsePermission == PermissionStatus.granted || alwaysPermission == PermissionStatus.granted);
|
||||
}
|
||||
*/
|
||||
|
||||
static Future<bool> checkAndRequestPermissions() async {
|
||||
if (_isRequestingPermission) {
|
||||
return _permissionCompleter!.future;
|
||||
}
|
||||
|
||||
_isRequestingPermission = true;
|
||||
_permissionCompleter = Completer<bool>();
|
||||
|
||||
bool hasPermissions = await checkLocationPermissions();
|
||||
if (!hasPermissions) {
|
||||
bool userAgreed = await showLocationDisclosure();
|
||||
if (userAgreed) {
|
||||
try {
|
||||
await requestAllLocationPermissions();
|
||||
hasPermissions = await checkLocationPermissions();
|
||||
} catch (e) {
|
||||
print('Error requesting location permissions: $e');
|
||||
hasPermissions = false;
|
||||
}
|
||||
} else {
|
||||
print('User did not agree to location usage');
|
||||
hasPermissions = false;
|
||||
// アプリを終了
|
||||
SystemNavigator.pop();
|
||||
}
|
||||
}
|
||||
|
||||
_isRequestingPermission = false;
|
||||
_permissionCompleter!.complete(hasPermissions);
|
||||
return _permissionCompleter!.future;
|
||||
}
|
||||
|
||||
static Future<void> requestAllLocationPermissions() async {
|
||||
await Permission.location.request();
|
||||
await Permission.locationWhenInUse.request();
|
||||
await Permission.locationAlways.request();
|
||||
|
||||
if (await Permission.locationAlways.isGranted) {
|
||||
const platform = MethodChannel('location');
|
||||
try {
|
||||
await platform.invokeMethod('startLocationService');
|
||||
} on PlatformException catch (e) {
|
||||
debugPrint("Failed to start location service: '${e.message}'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Future<bool> showLocationDisclosure() async {
|
||||
return await Get.dialog<bool>(
|
||||
AlertDialog(
|
||||
title: Text('位置情報の使用について'),
|
||||
content: const SingleChildScrollView(
|
||||
child: ListBody(
|
||||
children: <Widget>[
|
||||
Text('このアプリでは、以下の目的で位置情報を使用します:'),
|
||||
Text('• チェックポイントの自動チェックイン(アプリが閉じているときも含む)'),
|
||||
Text('• 移動履歴の記録(バックグラウンドでも継続)'),
|
||||
Text('• 現在地周辺の情報表示'),
|
||||
Text('\nバックグラウンドでも位置情報を継続的に取得します。'),
|
||||
Text('これにより、バッテリーの消費が増加する可能性があります。'),
|
||||
Text('同意しない場合には、アプリは終了します。'),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: const Text('同意しない'),
|
||||
onPressed: () => Get.back(result: false),
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('同意する'),
|
||||
onPressed: () => Get.back(result: true),
|
||||
),
|
||||
],
|
||||
),
|
||||
barrierDismissible: false,
|
||||
) ?? false;
|
||||
}
|
||||
|
||||
static void showPermissionDeniedDialog(String title,String message) {
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
//title: Text('location_permission_needed_title'.tr),
|
||||
title: Text(title.tr),
|
||||
// 位置情報への許可が必要です
|
||||
//content: Text('location_permission_needed_main'.tr),
|
||||
content: Text(message.tr),
|
||||
// 岐阜ロゲでは、位置情報を使用してスタート・チェックイン・ゴール等の通過照明及び移動手段の記録のために、位置情報のトラッキングを行なっています。このためバックグラウンドでもトラッキングができるように位置情報の権限が必要です。
|
||||
// 設定画面で、「岐阜ナビ」に対して、常に位置情報を許可するように設定してください。
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('キャンセル'),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
TextButton(
|
||||
child: Text('設定'),
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
openAppSettings();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
static Future<bool> requestLocationPermissions(BuildContext context) async {
|
||||
if (_isRequestingPermission) {
|
||||
// If a request is already in progress, wait for it to complete
|
||||
return _permissionCompleter!.future;
|
||||
}
|
||||
|
||||
_isRequestingPermission = true;
|
||||
_permissionCompleter = Completer<bool>();
|
||||
|
||||
bool userAgreed = await showLocationDisclosure(context);
|
||||
if (userAgreed) {
|
||||
try {
|
||||
final locationStatus = await Permission.location.request();
|
||||
final whenInUseStatus = await Permission.locationWhenInUse.request();
|
||||
final alwaysStatus = await Permission.locationAlways.request();
|
||||
|
||||
if (locationStatus == PermissionStatus.granted &&
|
||||
(whenInUseStatus == PermissionStatus.granted || alwaysStatus == PermissionStatus.granted)) {
|
||||
_permissionCompleter!.complete(true);
|
||||
} else {
|
||||
showPermissionDeniedDialog('location_permission_needed_title', 'location_permission_needed_main');
|
||||
_permissionCompleter!.complete(false);
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error requesting location permission: $e');
|
||||
_permissionCompleter!.complete(false);
|
||||
}
|
||||
} else {
|
||||
print('User did not agree to location usage');
|
||||
_permissionCompleter!.complete(false);
|
||||
// Exit the app
|
||||
SystemNavigator.pop();
|
||||
}
|
||||
|
||||
_isRequestingPermission = false;
|
||||
return _permissionCompleter!.future;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
static Future<bool> checkStoragePermission() async {
|
||||
//debugPrint("(gifunavi)== checkStoragePermission ==");
|
||||
@ -42,6 +195,7 @@ class PermissionController {
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
static Future<bool> checkLocationBasicPermission() async {
|
||||
//debugPrint("(gifunavi)== checkLocationBasicPermission ==");
|
||||
final locationPermission = await Permission.location.status;
|
||||
@ -152,31 +306,8 @@ class PermissionController {
|
||||
}
|
||||
}
|
||||
|
||||
static void showPermissionDeniedDialog(String title,String message) {
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
//title: Text('location_permission_needed_title'.tr),
|
||||
title: Text(title.tr),
|
||||
// 位置情報への許可が必要です
|
||||
//content: Text('location_permission_needed_main'.tr),
|
||||
content: Text(message.tr),
|
||||
// 岐阜ロゲでは、位置情報を使用してスタート・チェックイン・ゴール等の通過照明及び移動手段の記録のために、位置情報のトラッキングを行なっています。このためバックグラウンドでもトラッキングができるように位置情報の権限が必要です。
|
||||
// 設定画面で、「岐阜ナビ」に対して、常に位置情報を許可するように設定してください。
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('キャンセル'),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
TextButton(
|
||||
child: Text('設定'),
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
openAppSettings();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
|
||||
}
|
||||
@ -2,169 +2,110 @@ 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';
|
||||
|
||||
class RegisterPage extends StatelessWidget {
|
||||
class RegisterPage extends StatefulWidget {
|
||||
@override
|
||||
_RegisterPageState createState() => _RegisterPageState();
|
||||
}
|
||||
|
||||
class _RegisterPageState extends State<RegisterPage> {
|
||||
final IndexController indexController = Get.find<IndexController>();
|
||||
|
||||
TextEditingController emailController = TextEditingController();
|
||||
TextEditingController passwordController = TextEditingController();
|
||||
TextEditingController confirmPasswordController = TextEditingController();
|
||||
final TextEditingController emailController = TextEditingController();
|
||||
final TextEditingController passwordController = TextEditingController();
|
||||
final TextEditingController confirmPasswordController = TextEditingController();
|
||||
|
||||
RegisterPage({Key? key}) : super(key: key);
|
||||
bool _obscurePassword = true;
|
||||
bool _obscureConfirmPassword = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showHelperDialog(
|
||||
'登録メールにアクティベーションメールが送信されます。メールにあるリンクをタップすると正式登録になります。',
|
||||
'register_page'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
resizeToAvoidBottomInset: true,
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.white,
|
||||
leading: IconButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.arrow_back_ios,
|
||||
size: 20,
|
||||
color: Colors.black,
|
||||
)),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: const Icon(Icons.arrow_back_ios, size: 20, color: Colors.black),
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
child: SizedBox(
|
||||
child: Container(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Text(
|
||||
"sign_up".tr,
|
||||
style: 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),
|
||||
child: Column(
|
||||
children: [
|
||||
makeInput(label: "email".tr, controller: emailController),
|
||||
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",
|
||||
"Passwords does not match",
|
||||
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(milliseconds: 800),
|
||||
// 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(milliseconds: 800),
|
||||
//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,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(child: Text("already_have_account".tr)),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.toNamed(AppPages.LOGIN);
|
||||
},
|
||||
child: Text(
|
||||
"login".tr,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600, fontSize: 18),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
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),
|
||||
makeInput(label: "email".tr, controller: emailController),
|
||||
//makeInput(label: "password".tr, controller: passwordController, 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),
|
||||
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),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(child: Text("already_have_account".tr)),
|
||||
TextButton(
|
||||
onPressed: () => Get.toNamed(AppPages.LOGIN),
|
||||
child: Text("login".tr, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 18)),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -172,40 +113,96 @@ class RegisterPage extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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() {
|
||||
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,
|
||||
confirmPasswordController.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(
|
||||
{label, required TextEditingController controller, obsureText = false}) {
|
||||
Widget makeInput({required String label, required TextEditingController controller, bool 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,
|
||||
),
|
||||
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])!),
|
||||
),
|
||||
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,
|
||||
)
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
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))),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
11
lib/pages/team/member_binding.dart
Normal file
11
lib/pages/team/member_binding.dart
Normal file
@ -0,0 +1,11 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/team/member_controller.dart';
|
||||
import 'package:rogapp/services/api_service.dart';
|
||||
|
||||
class MemberBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<ApiService>(() => ApiService());
|
||||
Get.lazyPut<MemberController>(() => MemberController());
|
||||
}
|
||||
}
|
||||
270
lib/pages/team/member_controller.dart
Normal file
270
lib/pages/team/member_controller.dart
Normal file
@ -0,0 +1,270 @@
|
||||
// lib/controllers/member_controller.dart
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/model/user.dart';
|
||||
import 'package:rogapp/pages/team/team_controller.dart';
|
||||
import 'package:rogapp/services/api_service.dart';
|
||||
|
||||
class MemberController extends GetxController {
|
||||
late final ApiService _apiService;
|
||||
|
||||
final selectedMember = Rx<User?>(null);
|
||||
int teamId = 0;
|
||||
final member = Rx<User?>(null);
|
||||
final email = ''.obs;
|
||||
final firstname = ''.obs;
|
||||
final lastname = ''.obs;
|
||||
final female = false.obs;
|
||||
final dateOfBirth = Rx<DateTime?>(null);
|
||||
final isLoading = false.obs; // isLoadingプロパティを追加
|
||||
final isActive = false.obs;
|
||||
|
||||
//MemberController(this._apiService);
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
_apiService = Get.find<ApiService>();
|
||||
ever(member, (_) => _initializeMemberData());
|
||||
loadInitialData();
|
||||
}
|
||||
|
||||
bool get isDummyEmail => email.value.startsWith('dummy_');
|
||||
bool get isApproved => !email.value.startsWith('dummy_') && member.value?.isActive == true;
|
||||
|
||||
Future<void> loadInitialData() async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
if (Get.arguments != null) {
|
||||
if (Get.arguments['member'] != null) {
|
||||
member.value = Get.arguments['member'];
|
||||
}
|
||||
if (Get.arguments['teamId'] != null) {
|
||||
teamId = Get.arguments['teamId'];
|
||||
}
|
||||
}
|
||||
// 他の必要な初期データの取得をここで行う
|
||||
} catch (e) {
|
||||
print('Error loading initial data: $e');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void _initializeMemberData() {
|
||||
if (member.value != null) {
|
||||
email.value = member.value!.email ?? '';
|
||||
firstname.value = member.value!.firstname ?? '';
|
||||
lastname.value = member.value!.lastname ?? '';
|
||||
dateOfBirth.value = member.value!.dateOfBirth;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void setSelectedMember(User member) {
|
||||
this.member.value = member;
|
||||
email.value = member.email ?? '';
|
||||
firstname.value = member.firstname ?? '';
|
||||
lastname.value = member.lastname ?? '';
|
||||
dateOfBirth.value = member.dateOfBirth;
|
||||
female.value = member.female ?? false;
|
||||
isActive.value = member.isActive ?? false;
|
||||
}
|
||||
|
||||
bool _validateInputs() {
|
||||
if (email.value.isNotEmpty && !isDummyEmail) {
|
||||
return true; // Emailのみの場合は有効
|
||||
}
|
||||
if (firstname.value.isEmpty || lastname.value.isEmpty || dateOfBirth.value == null || female.value == null) {
|
||||
Get.snackbar('エラー', 'Emailが空の場合、姓名と生年月日及び性別は必須です', snackPosition: SnackPosition.BOTTOM);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void updateFirstName(String value) {
|
||||
firstname.value = value;
|
||||
}
|
||||
|
||||
void updateLastName(String value) {
|
||||
lastname.value = value;
|
||||
}
|
||||
|
||||
Future<bool> saveMember() async {
|
||||
if (!_validateInputs()) return false;
|
||||
|
||||
try {
|
||||
isLoading.value = true;
|
||||
User updatedMember;
|
||||
if (member.value == null || member.value!.id == null) {
|
||||
// 新規メンバー作成
|
||||
updatedMember = await _apiService.createTeamMember(
|
||||
teamId,
|
||||
isDummyEmail ? null : email.value, // dummy_メールの場合はnullを送信
|
||||
firstname.value,
|
||||
lastname.value,
|
||||
dateOfBirth.value,
|
||||
female.value,
|
||||
);
|
||||
} else {
|
||||
// 既存メンバー更新
|
||||
updatedMember = await _apiService.updateTeamMember(
|
||||
teamId,
|
||||
member.value!.id!,
|
||||
firstname.value,
|
||||
lastname.value,
|
||||
dateOfBirth.value,
|
||||
female.value,
|
||||
);
|
||||
}
|
||||
member.value = updatedMember;
|
||||
Get.snackbar('成功', 'メンバーが保存されました', snackPosition: SnackPosition.BOTTOM);
|
||||
return true;
|
||||
} catch (e) {
|
||||
print('Error saving member: $e');
|
||||
Get.snackbar('エラー', 'メンバーの保存に失敗しました: ${e.toString()}', snackPosition: SnackPosition.BOTTOM);
|
||||
return false;
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String getDisplayName() {
|
||||
if (!isActive.value && !isDummyEmail) {
|
||||
final displayName = email.value.split('@')[0];
|
||||
return '$displayName(未承認)';
|
||||
}
|
||||
return '${lastname.value} ${firstname.value}'.trim();
|
||||
}
|
||||
|
||||
Future<void> updateMember() async {
|
||||
if (member.value == null) return;
|
||||
int? memberId = member.value?.id;
|
||||
try {
|
||||
final updatedMember = await _apiService.updateTeamMember(
|
||||
teamId,
|
||||
memberId!,
|
||||
firstname.value,
|
||||
lastname.value,
|
||||
dateOfBirth.value,
|
||||
female.value,
|
||||
);
|
||||
member.value = updatedMember;
|
||||
} catch (e) {
|
||||
print('Error updating member: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteMember() async {
|
||||
if (member.value == null || member.value!.id == null) {
|
||||
Get.snackbar('エラー', 'メンバー情報が不正です', snackPosition: SnackPosition.BOTTOM);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
isLoading.value = true;
|
||||
await _apiService.deleteTeamMember(teamId, member.value!.id!);
|
||||
Get.snackbar('成功', 'メンバーが削除されました', snackPosition: SnackPosition.BOTTOM);
|
||||
member.value = null;
|
||||
} catch (e) {
|
||||
print('Error deleting member: $e');
|
||||
Get.snackbar('エラー', 'メンバーの削除に失敗しました: ${e.toString()}', snackPosition: SnackPosition.BOTTOM);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Future<void> deleteMember() async {
|
||||
if (member.value == null) return;
|
||||
int? memberId = member.value?.id;
|
||||
try {
|
||||
await _apiService.deleteTeamMember(teamId,memberId!);
|
||||
member.value = null;
|
||||
Get.back();
|
||||
} catch (e) {
|
||||
print('Error deleting member: $e');
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
Future<void> resendInvitation() async {
|
||||
if (isDummyEmail) {
|
||||
Get.snackbar('エラー', 'ダミーメールアドレスには招待メールを送信できません', snackPosition: SnackPosition.BOTTOM);
|
||||
return;
|
||||
}
|
||||
|
||||
if (member.value == null || member.value!.email == null) return;
|
||||
int? memberId = member.value?.id;
|
||||
try {
|
||||
await _apiService.resendMemberInvitation(memberId!);
|
||||
Get.snackbar('Success', 'Invitation resent successfully');
|
||||
} catch (e) {
|
||||
print('Error resending invitation: $e');
|
||||
Get.snackbar('Error', 'Failed to resend invitation');
|
||||
}
|
||||
}
|
||||
|
||||
void updateEmail(String value) => email.value = value;
|
||||
void updateFirstname(String value) => firstname.value = value;
|
||||
void updateLastname(String value) => lastname.value = value;
|
||||
void updateDateOfBirth(DateTime value) => dateOfBirth.value = value;
|
||||
|
||||
String getMemberStatus() {
|
||||
if (member.value == null) return '';
|
||||
if (member.value!.email == null) return '未登録';
|
||||
if (member.value!.isActive) return '承認済';
|
||||
return '招待中';
|
||||
}
|
||||
|
||||
int calculateAge() {
|
||||
if (dateOfBirth.value == null) return 0;
|
||||
final today = DateTime.now();
|
||||
int age = today.year - dateOfBirth.value!.year;
|
||||
if (today.month < dateOfBirth.value!.month ||
|
||||
(today.month == dateOfBirth.value!.month && today.day < dateOfBirth.value!.day)) {
|
||||
age--;
|
||||
}
|
||||
return age;
|
||||
}
|
||||
|
||||
String calculateGrade() {
|
||||
if (dateOfBirth.value == null) return '不明';
|
||||
|
||||
final today = DateTime.now();
|
||||
final birthDate = dateOfBirth.value!;
|
||||
|
||||
// 今年の4月1日
|
||||
final thisYearSchoolStart = DateTime(today.year, 4, 1);
|
||||
|
||||
// 生まれた年の翌年の4月1日(学齢期の始まり)
|
||||
final schoolStartDate = DateTime(birthDate.year + 1, 4, 1);
|
||||
|
||||
// 学齢期の開始からの年数
|
||||
int yearsFromSchoolStart = today.year - schoolStartDate.year;
|
||||
|
||||
// 今年の4月1日より前なら1年引く
|
||||
if (today.isBefore(thisYearSchoolStart)) {
|
||||
yearsFromSchoolStart--;
|
||||
}
|
||||
|
||||
if (yearsFromSchoolStart < 0) return '未就学';
|
||||
if (yearsFromSchoolStart < 6) return '小${yearsFromSchoolStart + 1}';
|
||||
if (yearsFromSchoolStart < 9) return '中${yearsFromSchoolStart - 5}';
|
||||
if (yearsFromSchoolStart < 12) return '高${yearsFromSchoolStart - 8}';
|
||||
return '成人';
|
||||
}
|
||||
|
||||
String getAgeAndGrade() {
|
||||
final age = calculateAge();
|
||||
final grade = calculateGrade();
|
||||
return '$age歳/$grade';
|
||||
}
|
||||
|
||||
bool isOver18() {
|
||||
return calculateAge() >= 18;
|
||||
}
|
||||
}
|
||||
244
lib/pages/team/member_detail_page.dart
Normal file
244
lib/pages/team/member_detail_page.dart
Normal file
@ -0,0 +1,244 @@
|
||||
// lib/pages/team/member_detail_page.dart
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/team/member_controller.dart';
|
||||
import 'package:intl/intl.dart'; // この行を追加
|
||||
import 'package:flutter_localizations/flutter_localizations.dart'; // 追加
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class MemberDetailPage extends StatefulWidget {
|
||||
@override
|
||||
_MemberDetailPageState createState() => _MemberDetailPageState();
|
||||
}
|
||||
|
||||
class _MemberDetailPageState extends State<MemberDetailPage> {
|
||||
final MemberController controller = Get.find<MemberController>();
|
||||
late TextEditingController _firstNameController;
|
||||
late TextEditingController _lastNameController;
|
||||
late TextEditingController _emailController;
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initializeControllers();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final mode = Get.arguments['mode'];
|
||||
final member = Get.arguments['member'];
|
||||
if (mode == 'edit' && member != null) {
|
||||
controller.setSelectedMember(member);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _initializeControllers() {
|
||||
_firstNameController = TextEditingController(text: controller.firstname.value);
|
||||
_lastNameController = TextEditingController(text: controller.lastname.value);
|
||||
_emailController = TextEditingController(text: controller.email.value);
|
||||
|
||||
controller.firstname.listen((value) {
|
||||
if (_firstNameController.text != value) {
|
||||
_firstNameController.value = TextEditingValue(
|
||||
text: value,
|
||||
selection: TextSelection.fromPosition(TextPosition(offset: value.length)),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
controller.lastname.listen((value) {
|
||||
if (_lastNameController.text != value) {
|
||||
_lastNameController.value = TextEditingValue(
|
||||
text: value,
|
||||
selection: TextSelection.fromPosition(TextPosition(offset: value.length)),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
controller.email.listen((value) {
|
||||
if (_emailController.text != value) {
|
||||
_emailController.value = TextEditingValue(
|
||||
text: value,
|
||||
selection: TextSelection.fromPosition(TextPosition(offset: value.length)),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final mode = Get.arguments['mode'] as String;
|
||||
//final member = Get.arguments['member'];
|
||||
final teamId = Get.arguments['teamId'] as int;
|
||||
|
||||
/*
|
||||
return MaterialApp( // MaterialApp をここに追加
|
||||
localizationsDelegates: [
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: [
|
||||
const Locale('ja', 'JP'),
|
||||
],
|
||||
home:Scaffold(
|
||||
*/
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(mode == 'new' ? 'メンバー追加' : 'メンバー詳細'),
|
||||
),
|
||||
body: Obx(() {
|
||||
if (controller.isLoading.value) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
// TextEditingControllerをObxの中で作成
|
||||
final emailController = TextEditingController(text: controller.email.value);
|
||||
// カーソル位置を保持
|
||||
emailController.selection = TextSelection.fromPosition(
|
||||
TextPosition(offset: controller.email.value.length),
|
||||
);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (mode == 'new')
|
||||
TextFormField(
|
||||
decoration: InputDecoration(labelText: 'メールアドレス'),
|
||||
//onChanged: (value) => controller.email.value = 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)
|
||||
Text('メールアドレス: (メアド無し)')
|
||||
else
|
||||
Text('メールアドレス: ${controller.email.value}'),
|
||||
|
||||
if (controller.email.value.isEmpty || mode == 'edit') ...[
|
||||
TextFormField(
|
||||
decoration: InputDecoration(labelText: '姓'),
|
||||
onChanged: (value) => controller.lastname.value = value,
|
||||
controller: TextEditingController(text: controller.lastname.value),
|
||||
),
|
||||
TextFormField(
|
||||
decoration: InputDecoration(labelText: '名'),
|
||||
onChanged: (value) => controller.firstname.value = value,
|
||||
controller: TextEditingController(text: controller.firstname.value),
|
||||
),
|
||||
// 生年月日
|
||||
if (controller.isDummyEmail || !controller.isOver18())
|
||||
ListTile(
|
||||
title: Text('生年月日'),
|
||||
subtitle: Text(controller.dateOfBirth.value != null
|
||||
? '${DateFormat('yyyy年MM月dd日').format(controller.dateOfBirth.value!)} (${controller.getAgeAndGrade()})'
|
||||
: '未設定'),
|
||||
onTap: () async {
|
||||
final date = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: controller.dateOfBirth.value ?? DateTime.now(),
|
||||
firstDate: DateTime(1900),
|
||||
lastDate: DateTime.now(),
|
||||
//locale: const Locale('ja', 'JP'),
|
||||
);
|
||||
if (date != null) controller.dateOfBirth.value = date;
|
||||
},
|
||||
)
|
||||
else
|
||||
Text('18歳以上'),
|
||||
|
||||
SwitchListTile(
|
||||
title: Text('性別'),
|
||||
subtitle: Text(controller.female.value ? '女性' : '男性'),
|
||||
value: controller.female.value,
|
||||
onChanged: (value) => controller.female.value = value,
|
||||
),
|
||||
],
|
||||
]),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
child: Text('削除'),
|
||||
onPressed: () async {
|
||||
final confirmed = await Get.dialog<bool>(
|
||||
AlertDialog(
|
||||
title: Text('確認'),
|
||||
content: Text('このメンバーを削除してもよろしいですか?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('キャンセル'),
|
||||
onPressed: () => Get.back(result: false),
|
||||
),
|
||||
TextButton(
|
||||
child: Text('削除'),
|
||||
onPressed: () => Get.back(result: true),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (confirmed == true) {
|
||||
await controller.deleteMember();
|
||||
Get.back(result: true);
|
||||
}
|
||||
},
|
||||
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
|
||||
void dispose() {
|
||||
_firstNameController.dispose();
|
||||
_lastNameController.dispose();
|
||||
_emailController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
11
lib/pages/team/team_binding.dart
Normal file
11
lib/pages/team/team_binding.dart
Normal file
@ -0,0 +1,11 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/team/team_controller.dart';
|
||||
import 'package:rogapp/services/api_service.dart';
|
||||
|
||||
class TeamBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<ApiService>(() => ApiService());
|
||||
Get.lazyPut<TeamController>(() => TeamController());
|
||||
}
|
||||
}
|
||||
332
lib/pages/team/team_controller.dart
Normal file
332
lib/pages/team/team_controller.dart
Normal file
@ -0,0 +1,332 @@
|
||||
// lib/controllers/team_controller.dart
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/model/team.dart';
|
||||
import 'package:rogapp/model/category.dart';
|
||||
import 'package:rogapp/model/user.dart';
|
||||
import 'package:rogapp/services/api_service.dart';
|
||||
|
||||
class TeamController extends GetxController {
|
||||
late final ApiService _apiService;
|
||||
|
||||
final teams = <Team>[].obs;
|
||||
final categories = <NewCategory>[].obs;
|
||||
final teamMembers = <User>[].obs;
|
||||
|
||||
|
||||
final selectedCategory = Rx<NewCategory?>(null);
|
||||
final selectedTeam = Rx<Team?>(null);
|
||||
final currentUser = Rx<User?>(null);
|
||||
|
||||
final teamName = ''.obs;
|
||||
final isLoading = false.obs;
|
||||
final error = RxString('');
|
||||
|
||||
@override
|
||||
void onInit() async{
|
||||
super.onInit();
|
||||
try {
|
||||
_apiService = Get.find<ApiService>();
|
||||
isLoading.value = true;
|
||||
|
||||
await fetchCategories();
|
||||
await Future.wait([
|
||||
fetchTeams(),
|
||||
getCurrentUser(),
|
||||
]);
|
||||
|
||||
print("selectedCategory=$selectedCategory.value");
|
||||
// カテゴリが取得できたら、最初のカテゴリを選択状態にする
|
||||
if (categories.isNotEmpty && selectedCategory.value == null) {
|
||||
selectedCategory.value = categories.first;
|
||||
}
|
||||
|
||||
}catch(e){
|
||||
print("Team Controller error: $e");
|
||||
error.value = e.toString();
|
||||
}finally{
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void setSelectedTeam(Team team) {
|
||||
selectedTeam.value = team;
|
||||
teamName.value = team.teamName;
|
||||
if (categories.isNotEmpty) {
|
||||
selectedCategory.value = categories.firstWhere(
|
||||
(category) => category.id == team.category.id,
|
||||
orElse: () => categories.first,
|
||||
);
|
||||
} else {
|
||||
// カテゴリリストが空の場合、teamのカテゴリをそのまま使用
|
||||
selectedCategory.value = team.category;
|
||||
}
|
||||
fetchTeamMembers(team.id);
|
||||
}
|
||||
|
||||
void resetForm() {
|
||||
selectedTeam.value = null;
|
||||
teamName.value = '';
|
||||
if (categories.isNotEmpty) {
|
||||
selectedCategory.value = categories.first;
|
||||
} else {
|
||||
selectedCategory.value = null;
|
||||
// カテゴリが空の場合、エラーメッセージをセット
|
||||
error.value = 'カテゴリデータが取得できませんでした。';
|
||||
} teamMembers.clear();
|
||||
}
|
||||
|
||||
void cleanupForNavigation() {
|
||||
selectedTeam.value = null;
|
||||
teamName.value = '';
|
||||
selectedCategory.value = categories.isNotEmpty ? categories.first : null;
|
||||
teamMembers.clear();
|
||||
//teamMembersはクリアしない
|
||||
// 必要に応じて他のクリーンアップ処理を追加
|
||||
}
|
||||
|
||||
Future<void> fetchTeams() async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
final fetchedTeams = await _apiService.getTeams();
|
||||
teams.assignAll(fetchedTeams);
|
||||
} catch (e) {
|
||||
error.value = 'チームの取得に失敗しました: $e';
|
||||
print('Error fetching teams: $e');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool checkIfUserHasEntryData(){
|
||||
if (teams.isEmpty) {
|
||||
return false;
|
||||
}else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchCategories() async {
|
||||
try {
|
||||
final fetchedCategories = await _apiService.getCategories();
|
||||
categories.assignAll(fetchedCategories);
|
||||
print("Fetched categories: ${categories.length}"); // デバッグ用
|
||||
|
||||
} catch (e) {
|
||||
print('Error fetching categories: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> getCurrentUser() async {
|
||||
try {
|
||||
final user = await _apiService.getCurrentUser();
|
||||
currentUser.value = user;
|
||||
} catch (e) {
|
||||
print('Error getting current user: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<Team> createTeam(String teamName, int categoryId) async {
|
||||
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;
|
||||
}
|
||||
|
||||
Future<Team> updateTeam(int teamId, String teamName, int categoryId) async {
|
||||
// APIサービスを使用してチームを更新
|
||||
final updatedTeam = await _apiService.updateTeam(teamId, teamName, categoryId);
|
||||
return updatedTeam;
|
||||
}
|
||||
|
||||
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 {
|
||||
// まず、チームのメンバーを全て削除
|
||||
await _apiService.deleteAllTeamMembers(teamId);
|
||||
|
||||
// その後、チームを削除
|
||||
await _apiService.deleteTeam(teamId);
|
||||
|
||||
// ローカルのチームリストを更新
|
||||
teams.removeWhere((team) => team.id == teamId);
|
||||
|
||||
/*
|
||||
Get.snackbar(
|
||||
'成功',
|
||||
'チームとそのメンバーが削除されました',
|
||||
backgroundColor: Colors.green,
|
||||
colorText: Colors.white,
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
*/
|
||||
|
||||
// チームリスト画面に戻る
|
||||
Get.back();
|
||||
|
||||
} catch (e) {
|
||||
print('Error deleting team and members: $e');
|
||||
Get.snackbar('エラー', 'チームとメンバーの削除に失敗しました');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchTeamMembers(int teamId) async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
final members = await _apiService.getTeamMembers(teamId);
|
||||
teamMembers.assignAll(members);
|
||||
} catch (e) {
|
||||
error.value = 'メンバーの取得に失敗しました: $e';
|
||||
print('Error fetching team members: $e');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateMember(teamId, User member) async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
await _apiService.updateTeamMember(teamId,member.id, member.firstname, member.lastname, member.dateOfBirth, member.female);
|
||||
await fetchTeamMembers(selectedTeam.value!.id);
|
||||
} catch (e) {
|
||||
error.value = 'メンバーの更新に失敗しました: $e';
|
||||
print('Error updating member: $e');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteMember(int memberId, int teamId) async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
await _apiService.deleteTeamMember(teamId,memberId);
|
||||
await fetchTeamMembers(teamId);
|
||||
} catch (e) {
|
||||
error.value = 'メンバーの削除に失敗しました: $e';
|
||||
print('Error deleting member: $e');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Future<void> addMember(User member, int teamId) async {
|
||||
// try {
|
||||
// isLoading.value = true;
|
||||
// await _apiService.createTeamMember(teamId, member.email, member.firstname, member.lastname, member.dateOfBirth);
|
||||
// await fetchTeamMembers(teamId);
|
||||
// } catch (e) {
|
||||
// error.value = 'メンバーの追加に失敗しました: $e';
|
||||
// print('Error adding member: $e');
|
||||
// } finally {
|
||||
// isLoading.value = false;
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
void updateTeamName(String value) {
|
||||
teamName.value = value;
|
||||
}
|
||||
|
||||
void updateCategory(NewCategory? value) {
|
||||
if (value != null) {
|
||||
selectedCategory.value = value;
|
||||
}
|
||||
}
|
||||
//void updateCategory(NewCategory? value) {
|
||||
// if (value != null) {
|
||||
// selectedCategory.value = categories.firstWhere(
|
||||
// (category) => category.id == value.id,
|
||||
// orElse: () => value,
|
||||
// );
|
||||
// }
|
||||
//}
|
||||
|
||||
Future<void> saveTeam() async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
if (selectedCategory.value == null) {
|
||||
throw Exception('カテゴリを選択してください');
|
||||
}
|
||||
|
||||
if (selectedTeam.value == null) {
|
||||
await createTeam(teamName.value, selectedCategory.value!.id);
|
||||
} else {
|
||||
await updateTeam(selectedTeam.value!.id, teamName.value, selectedCategory.value!.id);
|
||||
}
|
||||
|
||||
// サーバーから最新のデータを再取得
|
||||
await fetchTeams();
|
||||
update(); // UIを強制的に更新
|
||||
} catch (e) {
|
||||
error.value = 'チームの保存に失敗しました: $e';
|
||||
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<void> deleteSelectedTeam() async {
|
||||
if (selectedTeam.value != null) {
|
||||
await deleteTeam(selectedTeam.value!.id);
|
||||
selectedTeam.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
215
lib/pages/team/team_detail_page.dart
Normal file
215
lib/pages/team/team_detail_page.dart
Normal file
@ -0,0 +1,215 @@
|
||||
// lib/pages/team/team_detail_page.dart
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/team/team_controller.dart';
|
||||
import 'package:rogapp/routes/app_pages.dart';
|
||||
import 'package:rogapp/model/team.dart';
|
||||
import 'package:rogapp/model/category.dart';
|
||||
|
||||
class TeamDetailPage extends StatefulWidget {
|
||||
@override
|
||||
_TeamDetailPageState createState() => _TeamDetailPageState();
|
||||
}
|
||||
|
||||
class _TeamDetailPageState extends State<TeamDetailPage> {
|
||||
late TeamController controller;
|
||||
late TextEditingController _teamNameController = TextEditingController();
|
||||
final RxString mode = ''.obs;
|
||||
final Rx<Team?> team = Rx<Team?>(null);
|
||||
Worker? _teamNameWorker;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller = Get.find<TeamController>();
|
||||
_teamNameController = TextEditingController();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_initializeData();
|
||||
});
|
||||
}
|
||||
|
||||
void _initializeData() {
|
||||
final args = Get.arguments;
|
||||
if (args != null && args is Map<String, dynamic>) {
|
||||
mode.value = args['mode'] as String? ?? '';
|
||||
team.value = args['team'] as Team?;
|
||||
|
||||
if (mode.value == 'edit' && team.value != null) {
|
||||
controller.setSelectedTeam(team.value!);
|
||||
} else {
|
||||
controller.resetForm();
|
||||
}
|
||||
} else {
|
||||
// 引数がない場合は新規作成モードとして扱う
|
||||
mode.value = 'new';
|
||||
controller.resetForm();
|
||||
}
|
||||
|
||||
_teamNameController.text = controller.teamName.value;
|
||||
|
||||
// Use ever instead of direct listener
|
||||
_teamNameWorker = ever(controller.teamName, (String value) {
|
||||
if (_teamNameController.text != value) {
|
||||
_teamNameController.text = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_teamNameWorker?.dispose();
|
||||
_teamNameController.dispose();
|
||||
//controller.resetForm(); // Reset the form when leaving the page
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(Get.arguments['mode'] == 'new' ? '新規チーム作成' : 'チーム詳細'),
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
controller.cleanupForNavigation();
|
||||
Get.back();
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.save),
|
||||
onPressed: () async {
|
||||
try {
|
||||
await controller.saveTeam();
|
||||
Get.back();
|
||||
} catch (e) {
|
||||
Get.snackbar('エラー', e.toString(), snackPosition: SnackPosition.BOTTOM);
|
||||
}
|
||||
},
|
||||
),
|
||||
Obx(() {
|
||||
if (mode.value == 'edit') {
|
||||
return IconButton(
|
||||
icon: Icon(Icons.delete),
|
||||
onPressed: () async {
|
||||
try {
|
||||
await controller.deleteSelectedTeam();
|
||||
Get.back();
|
||||
} catch (e) {
|
||||
Get.snackbar('エラー', e.toString(), snackPosition: SnackPosition.BOTTOM);
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
}),
|
||||
],
|
||||
),
|
||||
body: Obx(() {
|
||||
if (controller.isLoading.value) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextFormField(
|
||||
decoration: InputDecoration(labelText: 'チーム名'),
|
||||
controller: _teamNameController,
|
||||
onChanged: (value) => controller.updateTeamName(value),
|
||||
),
|
||||
|
||||
SizedBox(height: 16),
|
||||
Obx(() {
|
||||
if (controller.categories.isEmpty) {
|
||||
return Text('カテゴリデータを読み込めませんでした。', style: TextStyle(color: Colors.red));
|
||||
}
|
||||
return DropdownButtonFormField<NewCategory>(
|
||||
decoration: InputDecoration(labelText: 'カテゴリ'),
|
||||
value: controller.selectedCategory.value,
|
||||
items: controller.categories.map((category) => DropdownMenuItem(
|
||||
value: category,
|
||||
child: Text(category.categoryName),
|
||||
)).toList(),
|
||||
/*
|
||||
items: controller.getFilteredCategories().map((category) => DropdownMenuItem(
|
||||
value: category,
|
||||
child: Text(category.categoryName),
|
||||
)).toList(),
|
||||
*/
|
||||
onChanged: (value) => controller.updateCategory(value),
|
||||
);
|
||||
}),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
child: Text('所有者: ${controller.currentUser.value?.email ?? "未設定"}'),
|
||||
),
|
||||
|
||||
if (mode.value == 'edit') ...[
|
||||
SizedBox(height: 24),
|
||||
Text('メンバーリスト', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
SizedBox(height: 8),
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
itemCount: controller.teamMembers.length,
|
||||
itemBuilder: (context, index) {
|
||||
final member = controller.teamMembers[index];
|
||||
final isDummyEmail = member.email?.startsWith('dummy_') ?? false;
|
||||
final displayName = isDummyEmail
|
||||
? '${member.lastname} ${member.firstname}'
|
||||
: member.isActive
|
||||
? '${member.lastname} ${member.firstname}'
|
||||
: '${member.email?.split('@')[0] ?? ''}'; //(未承認)';
|
||||
return ListTile(
|
||||
title: Text(displayName),
|
||||
subtitle: isDummyEmail ? Text('Email未設定') : null,
|
||||
onTap: () async {
|
||||
final result = await Get.toNamed(
|
||||
AppPages.MEMBER_DETAIL,
|
||||
arguments: {'mode': 'edit', 'member': member, 'teamId': controller.selectedTeam.value?.id},
|
||||
);
|
||||
if (result == true) {
|
||||
await controller.fetchTeamMembers(controller.selectedTeam.value!.id);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
child: Text('新しいメンバーを追加'),
|
||||
onPressed: () async {
|
||||
await Get.toNamed(
|
||||
AppPages.MEMBER_DETAIL,
|
||||
arguments: {'mode': 'new', 'teamId': controller.selectedTeam.value?.id},
|
||||
);
|
||||
if (controller.selectedTeam.value != null) {
|
||||
controller.fetchTeamMembers(controller.selectedTeam.value!.id);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
)
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
56
lib/pages/team/team_list_page.dart
Normal file
56
lib/pages/team/team_list_page.dart
Normal file
@ -0,0 +1,56 @@
|
||||
// lib/pages/team/team_list_page.dart
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rogapp/pages/team/team_controller.dart';
|
||||
import 'package:rogapp/routes/app_pages.dart';
|
||||
import 'package:rogapp/services/api_service.dart';
|
||||
|
||||
class TeamListPage extends GetWidget<TeamController> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('チーム管理'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.add),
|
||||
onPressed: () => Get.toNamed(AppPages.TEAM_DETAIL, arguments: {'mode': 'new'}),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Obx(() {
|
||||
if (controller.isLoading.value) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
} else if (controller.error.value.isNotEmpty) {
|
||||
return Center(child: Text(controller.error.value));
|
||||
} else if (controller.teams.isEmpty) {
|
||||
return Center(child: Text('チームがありません'));
|
||||
} else {
|
||||
return RefreshIndicator(
|
||||
onRefresh: controller.fetchTeams,
|
||||
child: ListView.builder(
|
||||
itemCount: controller.teams.length,
|
||||
itemBuilder: (context, index) {
|
||||
final team = controller.teams[index];
|
||||
return ListTile(
|
||||
title: Text(team.teamName),
|
||||
subtitle: Text('${team.category.categoryName} '),
|
||||
onTap: () async {
|
||||
await Get.toNamed(
|
||||
AppPages.TEAM_DETAIL,
|
||||
arguments: {'mode': 'edit', 'team': team},
|
||||
);
|
||||
controller.fetchTeams();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -28,6 +28,19 @@ import 'package:rogapp/spa/spa_binding.dart';
|
||||
import 'package:rogapp/spa/spa_page.dart';
|
||||
import 'package:rogapp/widgets/permission_handler_screen.dart';
|
||||
|
||||
import 'package:rogapp/pages/team/team_binding.dart';
|
||||
import 'package:rogapp/pages/team/team_list_page.dart';
|
||||
import 'package:rogapp/pages/team/team_detail_page.dart';
|
||||
import 'package:rogapp/pages/team/member_binding.dart';
|
||||
import 'package:rogapp/pages/team/member_detail_page.dart';
|
||||
import 'package:rogapp/pages/entry/entry_list_page.dart';
|
||||
import 'package:rogapp/pages/entry/entry_detail_page.dart';
|
||||
import 'package:rogapp/pages/entry/entry_binding.dart';
|
||||
|
||||
import 'package:rogapp/pages/entry/event_entries_page.dart';
|
||||
import 'package:rogapp/pages/entry/event_entries_binding.dart';
|
||||
import 'package:rogapp/pages/register/user_detail_page.dart';
|
||||
|
||||
part 'app_routes.dart';
|
||||
|
||||
class AppPages {
|
||||
@ -56,6 +69,14 @@ class AppPages {
|
||||
static const GPS = Routes.GPS;
|
||||
static const SETTINGS = Routes.SETTINGS;
|
||||
static const DEBUG = Routes.DEBUG;
|
||||
static const TEAM_LIST = Routes.TEAM_LIST;
|
||||
static const TEAM_DETAIL = Routes.TEAM_DETAIL;
|
||||
static const MEMBER_DETAIL = Routes.MEMBER_DETAIL;
|
||||
static const ENTRY_LIST = Routes.ENTRY_LIST;
|
||||
static const ENTRY_DETAIL = Routes.ENTRY_DETAIL;
|
||||
static const EVENT_ENTRY = Routes.EVENT_ENTRIES;
|
||||
static const USER_DETAILS_EDIT = Routes.USER_DETAILS_EDIT;
|
||||
|
||||
|
||||
static final routes = [
|
||||
GetPage(
|
||||
@ -127,7 +148,40 @@ class AppPages {
|
||||
page: () => DebugPage(),
|
||||
binding: DebugBinding(),
|
||||
),
|
||||
|
||||
GetPage(
|
||||
name: Routes.TEAM_LIST,
|
||||
page: () => TeamListPage(),
|
||||
binding: TeamBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.TEAM_DETAIL,
|
||||
page: () => TeamDetailPage(),
|
||||
binding: TeamBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.MEMBER_DETAIL,
|
||||
page: () => MemberDetailPage(),
|
||||
binding: MemberBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.ENTRY_LIST,
|
||||
page: () => EntryListPage(),
|
||||
binding: EntryBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.ENTRY_DETAIL,
|
||||
page: () => EntryDetailPage(),
|
||||
binding: EntryBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.EVENT_ENTRIES,
|
||||
page: () => EventEntriesPage(),
|
||||
binding: EventEntriesBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.USER_DETAILS_EDIT,
|
||||
page: () => UserDetailsEditPage(),
|
||||
),
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
@ -28,4 +28,14 @@ abstract class Routes {
|
||||
static const GPS = '/gp';
|
||||
static const SETTINGS = '/settings';
|
||||
static const DEBUG = '/debug';
|
||||
|
||||
static const TEAM_LIST = '/team-list';
|
||||
static const TEAM_DETAIL = '/team-detail';
|
||||
static const MEMBER_DETAIL = '/member-detail';
|
||||
static const ENTRY_LIST = '/entry-list';
|
||||
static const ENTRY_DETAIL = '/entry-detail';
|
||||
|
||||
static const EVENT_ENTRIES = '/event-entries';
|
||||
static const USER_DETAILS_EDIT = '/user-details-edit';
|
||||
|
||||
}
|
||||
|
||||
662
lib/services/api_service.dart
Normal file
662
lib/services/api_service.dart
Normal file
@ -0,0 +1,662 @@
|
||||
// lib/services/api_service.dart
|
||||
import 'package:get/get.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:rogapp/model/entry.dart';
|
||||
import 'package:rogapp/model/event.dart';
|
||||
import 'package:rogapp/model/team.dart';
|
||||
import 'package:rogapp/model/category.dart';
|
||||
import 'package:rogapp/model/user.dart';
|
||||
import 'package:rogapp/pages/index/index_controller.dart';
|
||||
import '../utils/const.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
|
||||
|
||||
class ApiService extends GetxService{
|
||||
static ApiService get to => Get.find<ApiService>();
|
||||
String serverUrl = '';
|
||||
String baseUrl = '';
|
||||
String token = 'your-auth-token';
|
||||
|
||||
Future<ApiService> init() async {
|
||||
try {
|
||||
// ここで必要な初期化処理を行う
|
||||
serverUrl = ConstValues.currentServer();
|
||||
baseUrl = '$serverUrl/api';
|
||||
//await Future.delayed(Duration(seconds: 2)); // 仮の遅延(実際の初期化処理に置き換えてください)
|
||||
print('ApiService initialized successfully');
|
||||
return this;
|
||||
} catch(e) {
|
||||
print('Error in ApiService initialization: $e');
|
||||
rethrow; // エラーを再スローして、呼び出し元で処理できるようにする
|
||||
//return this;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
このメソッドは以下のように動作します:
|
||||
|
||||
まず、渡された type パラメータに基づいて、どのクラスのフィールドを扱っているかを判断します。
|
||||
次に、クラス内で fieldName に対応する期待される型を返します。
|
||||
クラスや フィールド名が予期されていないものである場合、'Unknown' または 'Unknown Type' を返します。
|
||||
|
||||
このメソッドを ApiService クラスに追加することで、_printDataComparison メソッドは各フィールドの期待される型を正確に表示できるようになります。
|
||||
さらに、このメソッドを使用することで、API レスポンスのデータ型が期待と異なる場合に簡単に検出できるようになります。例えば、Category クラスの duration フィールドが整数型(秒数)で期待されているのに対し、API が文字列を返した場合、すぐに問題を特定できます。
|
||||
注意点として、API のレスポンス形式が変更された場合や、新しいフィールドが追加された場合は、このメソッドも更新する必要があります。そのため、API の変更とクライアントサイドのコードの同期を保つことが重要です。
|
||||
*/
|
||||
|
||||
String getToken()
|
||||
{
|
||||
// IndexControllerの初期化を待つ
|
||||
final indexController = Get.find<IndexController>();
|
||||
|
||||
if (indexController.currentUser.isNotEmpty) {
|
||||
token = indexController.currentUser[0]['token'] ?? '';
|
||||
print("Get token = $token");
|
||||
}else{
|
||||
token = "";
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
Future<List<Team>> getTeams() async {
|
||||
init();
|
||||
getToken();
|
||||
|
||||
try {
|
||||
final response = await http.get(
|
||||
Uri.parse('$baseUrl/teams/'),
|
||||
headers: {'Authorization': 'Token $token',"Content-Type": "application/json; charset=UTF-8"},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
// UTF-8でデコード
|
||||
final decodedResponse = utf8.decode(response.bodyBytes);
|
||||
//print('User Response body: $decodedResponse');
|
||||
List<dynamic> teamsJson = json.decode(decodedResponse);
|
||||
|
||||
List<Team> teams = [];
|
||||
for (var teamJson in teamsJson) {
|
||||
//print('\nTeam Data:');
|
||||
//_printDataComparison(teamJson, Team);
|
||||
teams.add(Team.fromJson(teamJson));
|
||||
}
|
||||
return teams;
|
||||
} else {
|
||||
throw Exception('Failed to load teams. Status code: ${response.statusCode}');
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
print('Error in getTeams: $e');
|
||||
print('Stack trace: $stackTrace');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Future<List<NewCategory>> getCategories() async {
|
||||
init();
|
||||
getToken();
|
||||
|
||||
try {
|
||||
final response = await http.get(
|
||||
Uri.parse('$baseUrl/categories/'),
|
||||
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');
|
||||
List<dynamic> categoriesJson = json.decode(decodedResponse);
|
||||
|
||||
List<NewCategory> categories = [];
|
||||
for (var categoryJson in categoriesJson) {
|
||||
try {
|
||||
//print('\nCategory Data:');
|
||||
//_printDataComparison(categoryJson, NewCategory);
|
||||
categories.add(NewCategory.fromJson(categoryJson));
|
||||
}catch(e){
|
||||
print('Error parsing category: $e');
|
||||
print('Problematic JSON: $categoryJson');
|
||||
}
|
||||
}
|
||||
|
||||
return categories;
|
||||
} else {
|
||||
throw Exception(
|
||||
'Failed to load categories. Status code: ${response.statusCode}');
|
||||
}
|
||||
}catch(e, stackTrace){
|
||||
print('Error in getCategories: $e');
|
||||
print('Stack trace: $stackTrace');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
init();
|
||||
getToken();
|
||||
|
||||
try {
|
||||
final response = await http.get(
|
||||
Uri.parse('$baseUrl/user/'),
|
||||
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 jsonData = json.decode(decodedResponse);
|
||||
|
||||
//print('\nUser Data Comparison:');
|
||||
//_printDataComparison(jsonData, User);
|
||||
|
||||
return User.fromJson(jsonData);
|
||||
} else {
|
||||
throw Exception('Failed to get current user. Status code: ${response.statusCode}');
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
print('Error in getCurrentUser: $e');
|
||||
print('Stack trace: $stackTrace');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
void _printDataComparison(Map<String, dynamic> data, Type expectedType) {
|
||||
print('Field\t\t| Expected Type\t| Actual Type\t| Actual Value');
|
||||
print('------------------------------------------------------------');
|
||||
data.forEach((key, value) {
|
||||
String expectedFieldType = _getExpectedFieldType(expectedType, key);
|
||||
_printComparison(key, expectedFieldType, value);
|
||||
});
|
||||
}
|
||||
|
||||
String _getExpectedFieldType(Type type, String fieldName) {
|
||||
// This method should return the expected type for each field based on the class definition
|
||||
// You might need to implement this based on your class structures
|
||||
|
||||
switch (type) {
|
||||
case NewCategory:
|
||||
switch (fieldName) {
|
||||
case 'id': return 'int';
|
||||
case 'category_name': return 'String';
|
||||
case 'category_number': return 'int';
|
||||
case 'duration': return 'int (seconds)';
|
||||
case 'num_of_member': return 'int';
|
||||
case 'family': return 'bool';
|
||||
case 'female': return 'bool';
|
||||
default: return 'Unknown';
|
||||
}
|
||||
case Team:
|
||||
switch (fieldName) {
|
||||
case 'id': return 'int';
|
||||
case 'zekken_number': return 'String';
|
||||
case 'team_name': return 'String';
|
||||
case 'category': return 'NewCategory (Object)';
|
||||
case 'owner': return 'User (Object)';
|
||||
default: return 'Unknown';
|
||||
}
|
||||
case User:
|
||||
switch (fieldName) {
|
||||
case 'id': return 'int';
|
||||
case 'email': return 'String';
|
||||
case 'firstname': return 'String';
|
||||
case 'lastname': return 'String';
|
||||
case 'date_of_birth': return 'String (ISO8601)';
|
||||
case 'female': return 'bool';
|
||||
case 'is_active': return 'bool';
|
||||
default: return 'Unknown';
|
||||
}
|
||||
default:
|
||||
return 'Unknown Type';
|
||||
}
|
||||
}
|
||||
|
||||
void _printComparison(String fieldName, String expectedType, dynamic actualValue) {
|
||||
String actualType = actualValue?.runtimeType.toString() ?? 'null';
|
||||
String displayValue = actualValue.toString();
|
||||
if (displayValue.length > 50) {
|
||||
displayValue = '${displayValue.substring(0, 47)}...';
|
||||
}
|
||||
print('$fieldName\t\t| $expectedType\t\t| $actualType\t\t| $displayValue');
|
||||
}
|
||||
|
||||
Future<Team> createTeam(String teamName, int categoryId) async {
|
||||
init();
|
||||
getToken();
|
||||
|
||||
final response = await http.post(
|
||||
Uri.parse('$baseUrl/teams/'),
|
||||
headers: {
|
||||
'Authorization': 'Token $token',
|
||||
"Content-Type": "application/json; charset=UTF-8",
|
||||
},
|
||||
body: json.encode({
|
||||
'team_name': teamName,
|
||||
'category': categoryId,
|
||||
}),
|
||||
);
|
||||
|
||||
if (response.statusCode == 201) {
|
||||
final decodedResponse = utf8.decode(response.bodyBytes);
|
||||
return Team.fromJson(json.decode(decodedResponse));
|
||||
} else {
|
||||
throw Exception('Failed to create team');
|
||||
}
|
||||
}
|
||||
|
||||
Future<Team> updateTeam(int teamId, String teamName, int categoryId) async {
|
||||
init();
|
||||
getToken();
|
||||
|
||||
final response = await http.put(
|
||||
Uri.parse('$baseUrl/teams/$teamId/'),
|
||||
headers: {
|
||||
'Authorization': 'Token $token',
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
body: json.encode({
|
||||
'team_name': teamName,
|
||||
'category': categoryId,
|
||||
}),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final decodedResponse = utf8.decode(response.bodyBytes);
|
||||
|
||||
return Team.fromJson(json.decode(decodedResponse));
|
||||
} else {
|
||||
throw Exception('Failed to update team');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteTeam(int teamId) async {
|
||||
init();
|
||||
getToken();
|
||||
|
||||
final response = await http.delete(
|
||||
Uri.parse('$baseUrl/teams/$teamId/'),
|
||||
headers: {'Authorization': 'Token $token','Content-Type': 'application/json; charset=UTF-8'},
|
||||
);
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<User>> getTeamMembers(int teamId) async {
|
||||
init();
|
||||
getToken();
|
||||
|
||||
final response = await http.get(
|
||||
Uri.parse('$baseUrl/teams/$teamId/members/'),
|
||||
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');
|
||||
List<dynamic> membersJson = json.decode(decodedResponse);
|
||||
|
||||
return membersJson.map((json) => User.fromJson(json)).toList();
|
||||
} else {
|
||||
throw Exception('Failed to load team members');
|
||||
}
|
||||
}
|
||||
|
||||
Future<User> createTeamMember(int teamId, String? email, String? firstname, String? lastname, DateTime? dateOfBirth,bool? female) async {
|
||||
init();
|
||||
getToken();
|
||||
|
||||
// emailが値を持っている場合の処理
|
||||
if (email != null && email.isNotEmpty) {
|
||||
firstname ??= "dummy";
|
||||
lastname ??= "dummy";
|
||||
dateOfBirth ??= DateTime.now();
|
||||
female ??= false;
|
||||
}
|
||||
|
||||
String? formattedDateOfBirth;
|
||||
if (dateOfBirth != null) {
|
||||
formattedDateOfBirth = DateFormat('yyyy-MM-dd').format(dateOfBirth);
|
||||
}
|
||||
|
||||
final response = await http.post(
|
||||
Uri.parse('$baseUrl/teams/$teamId/members/'),
|
||||
headers: {
|
||||
'Authorization': 'Token $token',
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
body: json.encode({
|
||||
'email': email,
|
||||
'firstname': firstname,
|
||||
'lastname': lastname,
|
||||
'date_of_birth': formattedDateOfBirth,
|
||||
'female': female,
|
||||
}),
|
||||
);
|
||||
|
||||
if (response.statusCode == 201) {
|
||||
final decodedResponse = utf8.decode(response.bodyBytes);
|
||||
return User.fromJson(json.decode(decodedResponse));
|
||||
} else {
|
||||
throw Exception('Failed to create team member');
|
||||
}
|
||||
}
|
||||
|
||||
Future<User> updateTeamMember(int teamId,int? memberId, String firstname, String lastname, DateTime? dateOfBirth,bool? female) async {
|
||||
init();
|
||||
getToken();
|
||||
|
||||
String? formattedDateOfBirth;
|
||||
if (dateOfBirth != null) {
|
||||
formattedDateOfBirth = DateFormat('yyyy-MM-dd').format(dateOfBirth);
|
||||
}
|
||||
|
||||
final response = await http.put(
|
||||
Uri.parse('$baseUrl/teams/$teamId/members/$memberId/'),
|
||||
headers: {
|
||||
'Authorization': 'Token $token',
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
body: json.encode({
|
||||
'firstname': firstname,
|
||||
'lastname': lastname,
|
||||
'date_of_birth': formattedDateOfBirth,
|
||||
'female': female,
|
||||
}),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final decodedResponse = utf8.decode(response.bodyBytes);
|
||||
return User.fromJson(json.decode(decodedResponse));
|
||||
} else {
|
||||
throw Exception('Failed to update team member');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteTeamMember(int teamId,int memberId) async {
|
||||
init();
|
||||
getToken();
|
||||
|
||||
final response = await http.delete(
|
||||
Uri.parse('$baseUrl/teams/$teamId/members/$memberId/'),
|
||||
headers: {'Authorization': 'Token $token'},
|
||||
);
|
||||
|
||||
if (response.statusCode != 204) {
|
||||
throw Exception('Failed to delete team member');
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
init();
|
||||
getToken();
|
||||
|
||||
final response = await http.post(
|
||||
Uri.parse('$baseUrl/members/$memberId/resend-invitation/'),
|
||||
headers: {'Authorization': 'Token $token', 'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Failed to resend invitation');
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<Entry>> getEntries() async {
|
||||
init();
|
||||
getToken();
|
||||
|
||||
final response = await http.get(
|
||||
Uri.parse('$baseUrl/entry/'),
|
||||
headers: {'Authorization': 'Token $token', 'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final decodedResponse = utf8.decode(response.bodyBytes);
|
||||
List<dynamic> entriesJson = json.decode(decodedResponse);
|
||||
return entriesJson.map((json) => Entry.fromJson(json)).toList();
|
||||
} else {
|
||||
throw Exception('Failed to load entries');
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<Event>> getEvents() async {
|
||||
init();
|
||||
getToken();
|
||||
|
||||
final response = await http.get(
|
||||
Uri.parse('$baseUrl/new-events/',),
|
||||
headers: {'Authorization': 'Token $token', 'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final decodedResponse = utf8.decode(response.bodyBytes);
|
||||
print('Response body: $decodedResponse');
|
||||
List<dynamic> eventsJson = json.decode(decodedResponse);
|
||||
|
||||
return eventsJson.map((json) => Event.fromJson(json)).toList();
|
||||
} else {
|
||||
throw Exception('Failed to load events');
|
||||
}
|
||||
}
|
||||
|
||||
Future<Entry> createEntry(int teamId, int eventId, int categoryId, DateTime date,String zekkenNumber) async {
|
||||
init();
|
||||
getToken();
|
||||
|
||||
String? formattedDate;
|
||||
if (date != null) {
|
||||
formattedDate = DateFormat('yyyy-MM-dd').format(date);
|
||||
}
|
||||
|
||||
final response = await http.post(
|
||||
Uri.parse('$baseUrl/entry/'),
|
||||
headers: {
|
||||
'Authorization': 'Token $token',
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
body: json.encode({
|
||||
'team': teamId,
|
||||
'event': eventId,
|
||||
'category': categoryId,
|
||||
'date': formattedDate,
|
||||
'zekken_number':zekkenNumber,
|
||||
}),
|
||||
);
|
||||
|
||||
if (response.statusCode == 201) {
|
||||
final decodedResponse = utf8.decode(response.bodyBytes);
|
||||
|
||||
return Entry.fromJson(json.decode(decodedResponse));
|
||||
} else {
|
||||
throw Exception('Failed to create entry');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateUserInfo(int userId, Entry entry) async {
|
||||
init();
|
||||
getToken();
|
||||
|
||||
final entryId = entry.id;
|
||||
|
||||
DateTime? date = entry.date;
|
||||
String? formattedDate;
|
||||
if (date != null) {
|
||||
formattedDate = DateFormat('yyyy-MM-dd').format(date);
|
||||
}
|
||||
|
||||
final response = await http.put(
|
||||
Uri.parse('$baseUrl/userinfo/$userId/'),
|
||||
headers: {
|
||||
'Authorization': 'Token $token',
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
body: json.encode({
|
||||
'zekken_number': entry.zekkenNumber,
|
||||
'event_code': entry.event.eventName,
|
||||
'group': entry.team.category.categoryName,
|
||||
'team_name': entry.team.teamName,
|
||||
'date': formattedDate,
|
||||
}),
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
final decodedResponse = utf8.decode(response.bodyBytes);
|
||||
final updatedUserInfo = json.decode(decodedResponse);
|
||||
//Get.find<IndexController>().updateUserInfo(updatedUserInfo);
|
||||
|
||||
} else {
|
||||
throw Exception('Failed to update entry');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<Entry> updateEntry(int entryId, int teamId, int eventId, int categoryId, DateTime date) async {
|
||||
init();
|
||||
getToken();
|
||||
|
||||
String? formattedDate;
|
||||
if (date != null) {
|
||||
formattedDate = DateFormat('yyyy-MM-dd').format(date);
|
||||
}
|
||||
|
||||
final response = await http.put(
|
||||
Uri.parse('$baseUrl/entry/$entryId/'),
|
||||
headers: {
|
||||
'Authorization': 'Token $token',
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
body: json.encode({
|
||||
'team': teamId,
|
||||
'event': eventId,
|
||||
'category': categoryId,
|
||||
'date': formattedDate,
|
||||
}),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final decodedResponse = utf8.decode(response.bodyBytes);
|
||||
|
||||
return Entry.fromJson(json.decode(decodedResponse));
|
||||
} else {
|
||||
throw Exception('Failed to update entry');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteEntry(int entryId) async {
|
||||
init();
|
||||
getToken();
|
||||
|
||||
final response = await http.delete(
|
||||
Uri.parse('$baseUrl/entry/$entryId/'),
|
||||
headers: {'Authorization': 'Token $token'},
|
||||
);
|
||||
|
||||
if (response.statusCode != 204) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,9 @@ import 'package:get/get.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../utils/const.dart';
|
||||
//import 'package:rogapp/services/team_service.dart';
|
||||
//import 'package:rogapp/services/member_service.dart';
|
||||
|
||||
|
||||
class AuthService {
|
||||
Future<AuthUser?> userLogin(String email, String password) async {
|
||||
@ -129,21 +132,40 @@ class AuthService {
|
||||
return cats;
|
||||
}
|
||||
|
||||
|
||||
// ユーザー登録
|
||||
//
|
||||
/*
|
||||
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(
|
||||
String email, String password) async {
|
||||
String email, String password, String password2) async {
|
||||
Map<String, dynamic> cats = {};
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = '$serverUrl/api/register/';
|
||||
//print('++++++++$url');
|
||||
debugPrint('++++++++${url}');
|
||||
final http.Response response = await http.post(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'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);
|
||||
if (response.statusCode == 200) {
|
||||
if (response.statusCode == 201) {
|
||||
cats = json.decode(utf8.decode(response.bodyBytes));
|
||||
}
|
||||
return cats;
|
||||
@ -166,6 +188,7 @@ class AuthService {
|
||||
return cats;
|
||||
}
|
||||
|
||||
|
||||
static Future<List<dynamic>?> userDetails(int userid) async {
|
||||
List<dynamic> cats = [];
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
|
||||
@ -4,7 +4,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
class ErrorService {
|
||||
static Future<void> reportError(dynamic error, StackTrace stackTrace, Map<String, dynamic> deviceInfo) async {
|
||||
static Future<void> reportError(dynamic error, StackTrace stackTrace, Map<String, dynamic> deviceInfo, List<String> operationLogs) async {
|
||||
try {
|
||||
final String errorMessage = error.toString();
|
||||
final String stackTraceString = stackTrace.toString();
|
||||
@ -19,16 +19,20 @@ class ErrorService {
|
||||
'stack_trace': stackTraceString,
|
||||
'estimated_cause': estimatedCause,
|
||||
'device_info': deviceInfo,
|
||||
'operation_logs': operationLogs.join('\n'), // オペレーションログを改行で結合して送信
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
// エラー報告が成功した場合の処理(必要に応じて)
|
||||
debugPrint("===== エラーログ送信成功しました。 ====");
|
||||
} else {
|
||||
// エラー報告が失敗した場合の処理(必要に応じて)
|
||||
debugPrint("===== エラーログ送信失敗しました。 ====");
|
||||
}
|
||||
} catch (e) {
|
||||
// エラー報告中にエラーが発生した場合の処理(必要に応じて)
|
||||
debugPrint("===== エラーログ送信中にエラーになりました。 ====");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,8 +4,10 @@ import 'package:get/get.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:intl/intl.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/index/index_controller.dart';
|
||||
import 'package:rogapp/pages/team/team_controller.dart';
|
||||
import 'package:rogapp/utils/database_gps.dart';
|
||||
import 'package:rogapp/utils/database_helper.dart';
|
||||
import 'dart:convert';
|
||||
@ -32,11 +34,19 @@ class ExternalService {
|
||||
|
||||
Future<Map<String, dynamic>> startRogaining() async {
|
||||
final IndexController indexController = Get.find<IndexController>();
|
||||
//final TeamController teamController = Get.find<TeamController>();
|
||||
|
||||
debugPrint("== startRogaining ==");
|
||||
|
||||
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"];
|
||||
//print("--- Pressed -----");
|
||||
String team = indexController.currentUser[0]["user"]['team_name'];
|
||||
@ -60,8 +70,9 @@ class ExternalService {
|
||||
} else {
|
||||
debugPrint("== startRogaining processing==");
|
||||
|
||||
String url = 'https://rogaining.sumasen.net/gifuroge/start_from_rogapp';
|
||||
//print('++++++++$url');
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = '$serverUrl/gifuroge/start_from_rogapp';
|
||||
print('++++++++$url');
|
||||
final http.Response response = await http.post(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
@ -82,7 +93,7 @@ class ExternalService {
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> makeCheckpoint(
|
||||
int userId,
|
||||
int userId, // 中身はteamId
|
||||
String token,
|
||||
String checkinTime,
|
||||
String teamname,
|
||||
@ -93,9 +104,17 @@ class ExternalService {
|
||||
// print("~~~~ cp is $cp ~~~~");
|
||||
//print("--cpcp-- ${cp}");
|
||||
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');
|
||||
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 (indexController.connectionStatusName.value != "wifi" &&
|
||||
@ -160,7 +179,7 @@ class ExternalService {
|
||||
'cp_number': cp.toString(),
|
||||
'event_code': eventcode,
|
||||
'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>{
|
||||
@ -168,7 +187,7 @@ class ExternalService {
|
||||
'cp_number': cp.toString(),
|
||||
'event_code': eventcode,
|
||||
'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("--json-- $vv");
|
||||
@ -253,6 +272,9 @@ class ExternalService {
|
||||
final DestinationController destinationController =
|
||||
Get.find<DestinationController>();
|
||||
|
||||
// チームIDを取得
|
||||
int teamId = indexController.currentUser[0]["user"]["team"]["id"];
|
||||
|
||||
debugPrint("== goal Rogaining ==");
|
||||
|
||||
//if(indexController.connectionStatusName != "wifi" && indexController.connectionStatusName != "mobile"){
|
||||
@ -261,7 +283,7 @@ class ExternalService {
|
||||
id: 1,
|
||||
team_name: teamname,
|
||||
event_code: eventcode,
|
||||
user_id: userId,
|
||||
user_id: teamId, //userId,
|
||||
cp_number: -1,
|
||||
checkintime: DateTime.now().toUtc().microsecondsSinceEpoch,
|
||||
image: image,
|
||||
@ -283,7 +305,7 @@ class ExternalService {
|
||||
},
|
||||
// 'id', 'user', 'goalimage', 'goaltime', 'team_name', 'event_code','cp_number'
|
||||
body: jsonEncode(<String, String>{
|
||||
'user': userId.toString(),
|
||||
'user': teamId.toString(), //userId.toString(),
|
||||
'team_name': teamname,
|
||||
'event_code': eventcode,
|
||||
'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');
|
||||
if (response.statusCode == 201) {
|
||||
Map<String, dynamic> res = json.decode(utf8.decode(response.bodyBytes));
|
||||
@ -308,7 +331,7 @@ class ExternalService {
|
||||
'event_code': eventcode,
|
||||
'goal_time': goalTime,
|
||||
'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>{
|
||||
@ -317,7 +340,7 @@ class ExternalService {
|
||||
'goal_time': goalTime,
|
||||
'image': res["goalimage"]
|
||||
.toString()
|
||||
.replaceAll('http://localhost:8100', 'http://rogaining.sumasen.net')
|
||||
.replaceAll('http://localhost:8100', serverUrl) //'http://rogaining.sumasen.net')
|
||||
});
|
||||
//print("-- json -- $rec");
|
||||
//print('----- response2 is $response2 --------');
|
||||
@ -343,8 +366,8 @@ class ExternalService {
|
||||
indexController.connectionStatusName.value != "mobile") {
|
||||
return Future.value(false);
|
||||
} else {
|
||||
String url =
|
||||
'https://rogaining.sumasen.net/gifuroge/remove_checkin_from_rogapp';
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = '$serverUrl/gifuroge/remove_checkin_from_rogapp';
|
||||
//print('++++++++$url');
|
||||
final http.Response response = await http.post(
|
||||
Uri.parse(url),
|
||||
@ -413,8 +436,8 @@ class ExternalService {
|
||||
|
||||
//print("calling push gps step 2 ${payload}");
|
||||
|
||||
String urlS =
|
||||
'https://rogaining.sumasen.net/gifuroge/get_waypoint_datas_from_rogapp';
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String urlS = '$serverUrl/gifuroge/get_waypoint_datas_from_rogapp';
|
||||
//print('++++++++$url');
|
||||
var url = Uri.parse(urlS); // Replace with your server URL
|
||||
var response = await http.post(
|
||||
@ -440,8 +463,8 @@ class ExternalService {
|
||||
static Future<Map<String, dynamic>> usersEventCode(
|
||||
String teamcode, String password) async {
|
||||
Map<String, dynamic> res = {};
|
||||
String url =
|
||||
"https://rogaining.sumasen.net/gifuroge/check_event_code?zekken_number=$teamcode&password=$password";
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
String url = "$serverUrl/gifuroge/check_event_code?zekken_number=$teamcode&password=$password";
|
||||
//print('++++++++$url');
|
||||
final http.Response response =
|
||||
await http.get(Uri.parse(url), headers: <String, String>{
|
||||
|
||||
@ -82,59 +82,93 @@ class LocationService {
|
||||
double lon3,
|
||||
double lat4,
|
||||
double lon4,
|
||||
String cat) async {
|
||||
String cat,
|
||||
String event_code) async {
|
||||
//print("-------- in location for bound -------------");
|
||||
final IndexController indexController = Get.find<IndexController>();
|
||||
String url = "";
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
if (cat.isNotEmpty) {
|
||||
if (indexController.currentUser.isNotEmpty) {
|
||||
bool rog = indexController.currentUser[0]['user']['is_rogaining'];
|
||||
String r = rog == true ? 'True' : 'False';
|
||||
var grp = indexController.currentUser[0]['user']['event_code'];
|
||||
url =
|
||||
'$serverUrl/api/inbound?rog=$r&grp=$grp&ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4&cat=$cat';
|
||||
} else {
|
||||
url =
|
||||
'$serverUrl/api/inbound?ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4&cat=$cat';
|
||||
}
|
||||
} else {
|
||||
if (indexController.currentUser.isNotEmpty) {
|
||||
bool rog = indexController.currentUser[0]['user']['is_rogaining'];
|
||||
String r = rog == true ? 'True' : 'False';
|
||||
var grp = indexController.currentUser[0]['user']['event_code'];
|
||||
//print("-------- requested user group $grp -------------");
|
||||
url =
|
||||
'$serverUrl/api/inbound?rog=$r&grp=$grp&ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4';
|
||||
} else {
|
||||
url =
|
||||
'$serverUrl/api/inbound?ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4';
|
||||
}
|
||||
}
|
||||
//print('++++++++$url');
|
||||
final response = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
);
|
||||
final updateTime = indexController.lastUserUpdateTime.value;
|
||||
|
||||
if (response.statusCode == 500) {
|
||||
return null; //featuresFromGeoJson(utf8.decode(response.bodyBytes));
|
||||
}
|
||||
// ユーザー情報の更新を最大5秒間待つ
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
DestinationController destinationController =
|
||||
Get.find<DestinationController>();
|
||||
GeoJSONFeatureCollection cc =
|
||||
GeoJSONFeatureCollection.fromJSON(utf8.decode(response.bodyBytes));
|
||||
if (cc.features.isEmpty) {
|
||||
try {
|
||||
/*
|
||||
// ユーザー情報の更新を最大5秒間待つ
|
||||
final newUpdateTime = await indexController.lastUserUpdateTime.stream
|
||||
.firstWhere(
|
||||
(time) => time.isAfter(updateTime),
|
||||
orElse: () => updateTime,
|
||||
).timeout(Duration(seconds: 5));
|
||||
|
||||
if (newUpdateTime == updateTime) {
|
||||
print('ユーザー情報の更新がタイムアウトしました');
|
||||
// タイムアウト時の処理(例:エラー表示やリトライ)
|
||||
return null;
|
||||
} else {
|
||||
//print("---- feature got from server is ${cc.collection[0].properties} ------");
|
||||
return cc;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
await indexController.lastUserUpdateTime.stream.firstWhere(
|
||||
(time) => time.isAfter(updateTime),
|
||||
orElse: () => updateTime,
|
||||
).timeout(Duration(seconds: 2), onTimeout: () => updateTime);
|
||||
*/
|
||||
|
||||
String url = "";
|
||||
String serverUrl = ConstValues.currentServer();
|
||||
if (cat.isNotEmpty) {
|
||||
if (indexController.currentUser.isNotEmpty) {
|
||||
bool rog = indexController.currentUser[0]['user']['is_rogaining'];
|
||||
String r = rog == true ? 'True' : 'False';
|
||||
var grp = event_code; //indexController.currentUser[0]['user']['event_code'];
|
||||
print("Group=$grp");
|
||||
url =
|
||||
'$serverUrl/api/inbound?rog=$r&grp=$grp&ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4&cat=$cat';
|
||||
} else {
|
||||
url =
|
||||
'$serverUrl/api/inbound?ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4&cat=$cat';
|
||||
}
|
||||
} else {
|
||||
if (indexController.currentUser.isNotEmpty) {
|
||||
bool rog = indexController.currentUser[0]['user']['is_rogaining'];
|
||||
String r = rog == true ? 'True' : 'False';
|
||||
var grp = indexController.currentUser[0]['user']['event_code'];
|
||||
print("-------- requested user group $grp -------------");
|
||||
url =
|
||||
'$serverUrl/api/inbound?rog=$r&grp=$grp&ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4';
|
||||
} else {
|
||||
url =
|
||||
'$serverUrl/api/inbound?ln1=$lon1&la1=$lat1&ln2=$lon2&la2=$lat2&ln3=$lon3&la3=$lat3&ln4=$lon4&la4=$lat4';
|
||||
}
|
||||
print('++++++++$url');
|
||||
final response = await http.get(
|
||||
Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 500) {
|
||||
return null; //featuresFromGeoJson(utf8.decode(response.bodyBytes));
|
||||
}
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
DestinationController destinationController =
|
||||
Get.find<DestinationController>();
|
||||
|
||||
GeoJSONFeatureCollection cc =
|
||||
GeoJSONFeatureCollection.fromJSON(utf8.decode(response.bodyBytes));
|
||||
if (cc.features.isEmpty) {
|
||||
return null;
|
||||
} else {
|
||||
//print("---- feature got from server is ${cc.collection[0].properties} ------");
|
||||
return cc;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
print("Error: $e");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,9 @@
|
||||
|
||||
|
||||
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 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_ip_server = "http://192.168.8.100:8100";
|
||||
|
||||
@ -7,7 +7,8 @@ class StringValues extends Translations{
|
||||
'drawer_title':'Rogaining participants can view checkpoints by logging in',
|
||||
'app_title': '- Rogaining -',
|
||||
'address':'address',
|
||||
'email':'Email',
|
||||
'bib':'Bib number',
|
||||
'email':'Email address',
|
||||
'password':'Password',
|
||||
'web':'Web',
|
||||
'wikipedia':'Wikipedia',
|
||||
@ -29,7 +30,7 @@ class StringValues extends Translations{
|
||||
'visit_history': 'Visit History',
|
||||
'rog_web': 'rog website',
|
||||
'no_values': 'No Values',
|
||||
'email_and_password_required': 'Email and password required',
|
||||
'email_and_password_required': 'Email and password are required to register user',
|
||||
'rogaining_user_need_tosign_up': "Rogaining participants do need to sign up.",
|
||||
'add_location': 'Gifu',
|
||||
'select_travel_mode':'Select your travel mode',
|
||||
@ -70,6 +71,8 @@ class StringValues extends Translations{
|
||||
"Not reached the goal yet": "Not reached the goal yet",
|
||||
"You have not reached the goal yet.":"You have not reached the goal yet.",
|
||||
"delete_account": "Delete account",
|
||||
"delete_account_title": "Are you ok to delete your account?",
|
||||
"delete_account_middle": "All your account information and data history will be removed from local device and server side.",
|
||||
"accounted_deleted": "Account deleted",
|
||||
"account_deleted_message": "your account has beed successfully deleted",
|
||||
"privacy": "Privacy policy",
|
||||
@ -95,7 +98,6 @@ class StringValues extends Translations{
|
||||
'already_have_account': 'Already have an account?',
|
||||
'sign_up': 'Sign Up',
|
||||
'create_account': 'Create an account, it\'s free',
|
||||
'email': 'Email',
|
||||
'confirm_password': 'Confirm Password',
|
||||
'cancel_checkin': 'Cancel Check-in',
|
||||
'go_here': 'Show route',
|
||||
@ -203,13 +205,24 @@ class StringValues extends Translations{
|
||||
'location_permission_required_title': 'Location Permission Required',
|
||||
'location_permission_required_message': 'This app requires access to your location. Please grant permission to continue.',
|
||||
'cancel': 'Cancel',
|
||||
'checkins': 'Check-ins'
|
||||
'checkins': 'Check-ins',
|
||||
'reset_button': 'Reset data',
|
||||
'reset_title': 'Reset the data in this device.',
|
||||
'reset_message': 'Are you ok to reset all data in this device?',
|
||||
'reset_done': 'Reset Done.',
|
||||
'reset_explain': 'All data has been reset. You should tap start rogaining to start game.',
|
||||
'no_match': 'No 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': {
|
||||
'drawer_title':'ロゲイニング参加者はログイン するとチェックポイントが参照 できます',
|
||||
'app_title': '旅行工程表',
|
||||
'address':'住所',
|
||||
'email':'ゼッケン番号',
|
||||
'bib':'ゼッケン番号',
|
||||
'email':'メールアドレス',
|
||||
'password':'パスワード',
|
||||
'web':'ウェブ',
|
||||
'wikipedia':'ウィキペディア',
|
||||
@ -233,7 +246,7 @@ class StringValues extends Translations{
|
||||
'visit_history': '訪問履歴',
|
||||
'rog_web': 'ロゲイニングウェブサイト',
|
||||
'no_values': '値なし',
|
||||
'email_and_password_required': 'メールとパスワードが必要です',
|
||||
'email_and_password_required': 'メールとパスワードの入力が必要です',
|
||||
'rogaining_user_need_tosign_up': "ロゲイニング参加者はサインアップの必要はありません。",
|
||||
'add_location': '岐阜',
|
||||
'select_travel_mode':'移動モードを選択してください',
|
||||
@ -245,12 +258,12 @@ class StringValues extends Translations{
|
||||
'confirm': '確認',
|
||||
'cancel': 'キャンセル',
|
||||
'all_destinations_are_deleted_successfully' : 'すべての宛先が正常に削除されました',
|
||||
'deleted': "削除された",
|
||||
'deleted': "削除されました",
|
||||
'remarks' : '備考',
|
||||
'old_password' : '以前のパスワード',
|
||||
'new_password' : '新しいパスワード',
|
||||
'values_required' : '必要な値',
|
||||
'failed' : '失敗した',
|
||||
'failed' : '失敗',
|
||||
'password_change_failed_please_try_again' : 'パスワードの変更に失敗しました。もう一度お試しください',
|
||||
'user_registration_failed_please_try_again' : 'ユーザー登録に失敗しました。もう一度お試しください',
|
||||
'all': '全て',
|
||||
@ -273,11 +286,13 @@ class StringValues extends Translations{
|
||||
"You have not started rogaining yet.":"あなたはまだロゲイニングを始めていません。",
|
||||
"Not reached the goal yet": "まだ目標に達していない",
|
||||
"You have not reached the goal yet.":"あなたはまだゴールに達していません。",
|
||||
"delete_account": "アカウントを削除する",
|
||||
"delete_account": "アカウントを削除します",
|
||||
"delete_account_title": "アカウントを削除しますがよろしいですか?",
|
||||
"delete_account_middle": "これにより、アカウント情報とすべてのゲーム データが削除され、すべての状態が削除されます",
|
||||
"accounted_deleted": "アカウントが削除されました",
|
||||
"account_deleted_message": "あなたのアカウントは正常に削除されました",
|
||||
"privacy": "プライバシーポリシー",
|
||||
"app_developed_by_gifu_dx": "※このアプリは令和4年度岐阜県DX補助金事業で開発されました。",
|
||||
"app_developed_by_gifu_dx": "※このアプリは令和4、6年度岐阜県DX補助金事業で開発されました。",
|
||||
|
||||
'location_permission_title': 'ロケーション許可',
|
||||
'location_permission_content': 'このアプリでは、位置情報の収集を行います。\n岐阜ナビアプリではチェックポイントの自動チェックインの機能を可能にするために、現在地のデータが収集されます。アプリを閉じている時や、使用していないときにも収集されます。位置情報は、個人を特定できない統計的な情報として、ユーザーの個人情報とは一切結びつかない形で送信されます。お知らせの配信、位置情報の利用を許可しない場合は、この後表示されるダイアログで「許可しない」を選択してください。',
|
||||
@ -299,8 +314,7 @@ class StringValues extends Translations{
|
||||
'already_have_account': 'すでにアカウントをお持ちですか?',
|
||||
'sign_up': 'サインアップ',
|
||||
'create_account': 'アカウントを無料で作成します',
|
||||
'email': 'ゼッケン番号',
|
||||
'confirm_password': 'パスワードを認証する',
|
||||
'confirm_password': '確認用パスワード',
|
||||
'cancel_checkin': 'チェックイン取消',
|
||||
'go_here': 'ルート表示',
|
||||
'cancel_route':'ルート消去',
|
||||
@ -410,6 +424,15 @@ class StringValues extends Translations{
|
||||
'location_permission_required_message': 'このアプリを使用するには、位置情報へのアクセスが必要です。続行するには許可を付与してください。',
|
||||
'cancel': 'キャンセル',
|
||||
'checkins': 'チェックイン',
|
||||
'reset_button': 'リセット',
|
||||
'reset_title': 'リセットしますがよろしいですか?',
|
||||
'reset_message': 'これにより、すべてのゲーム データが削除され、すべての状態が削除されます',
|
||||
'reset_done': 'リセット完了',
|
||||
'reset_explain': 'すべてリセットされました。ロゲ開始から再開して下さい。',
|
||||
'no_match': '不一致',
|
||||
'password_does_not_match':'入力したパスワードが一致しません',
|
||||
'forgot_password':'パスワードを忘れた場合',
|
||||
'user_registration_successful':'ユーザー認証のメールをお届けしました。メール上のリンクをクリックして正式登録してください。',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -216,6 +216,7 @@ class BottomSheetNew extends GetView<BottomSheetController> {
|
||||
);
|
||||
|
||||
saveGameState();
|
||||
//int teamId = indexController.teamId.value; // teamIdを使用
|
||||
await ExternalService().startRogaining();
|
||||
Get.back();
|
||||
Get.back();// Close the dialog and potentially navigate away
|
||||
|
||||
@ -12,6 +12,9 @@ class LogManager {
|
||||
List<String> _logs = [];
|
||||
List<VoidCallback> _listeners = [];
|
||||
|
||||
List<String> _operationLogs = [];
|
||||
List<String> get operationLogs => _operationLogs;
|
||||
|
||||
List<String> get logs => _logs;
|
||||
|
||||
void addLog(String log) {
|
||||
@ -24,6 +27,16 @@ class LogManager {
|
||||
_notifyListeners(); // Notify all listeners
|
||||
}
|
||||
|
||||
void addOperationLog(String log) {
|
||||
_operationLogs.add(log);
|
||||
_notifyListeners();
|
||||
}
|
||||
|
||||
void clearOperationLogs() {
|
||||
_operationLogs.clear();
|
||||
_notifyListeners();
|
||||
}
|
||||
|
||||
void addListener(VoidCallback listener) {
|
||||
_listeners.add(listener);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
381
login_page.dart
Normal file
381
login_page.dart
Normal 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回は、岐阜県の令和5年度「清流の国ぎふ」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,
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -374,6 +374,11 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
flutter_localizations:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_map:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -660,10 +665,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
|
||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.18.1"
|
||||
version: "0.19.0"
|
||||
isar:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@ -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.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 4.8.0+480
|
||||
version: 4.8.6+486
|
||||
|
||||
environment:
|
||||
sdk: ">=3.2.0 <4.0.0"
|
||||
@ -29,6 +29,8 @@ environment:
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
@ -81,7 +83,7 @@ dependencies:
|
||||
circular_menu: ^2.0.1
|
||||
camera: ^0.10.0+3
|
||||
camera_camera: ^3.0.0
|
||||
intl: ^0.18.1
|
||||
intl: ^0.19.0 #^0.18.1
|
||||
modal_bottom_sheet: ^3.0.0-pre
|
||||
connectivity_plus: ^5.0.2
|
||||
flutter_map_tile_caching: ^9.0.0-dev.5
|
||||
|
||||
Reference in New Issue
Block a user