Files
rog_app/all_combined.txt

15598 lines
572 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:keyboard_dismisser/keyboard_dismisser.dart';
import 'package:rogapp/model/auth_user.dart';
import 'package:rogapp/nrog/pages/home_page.dart';
import 'package:rogapp/provider/auth_provider.dart';
import 'package:rogapp/services/auth_service.dart';
import 'package:rogapp/widgets/c_form_text_field.dart';
import 'package:rogapp/widgets/c_password_text_filed.dart';
class AuthPage extends ConsumerStatefulWidget {
const AuthPage({super.key});
@override
ConsumerState<AuthPage> createState() => _AuthPageState();
}
class _AuthPageState extends ConsumerState<AuthPage> {
final _formkey = GlobalKey<FormState>();
final FocusNode focusEmail = FocusNode();
final FocusNode focusPwd = FocusNode();
var _authMode = 'login';
bool _isLoginProgress = false;
final TextEditingController _emailTextEditingController =
TextEditingController();
final TextEditingController _passwordTextEditingController =
TextEditingController();
@override
void initState() {
super.initState();
_emailTextEditingController.addListener(() => setState(() {}));
WidgetsBinding.instance.addPostFrameCallback((_) {
checkUser();
});
}
void _submit() async {
setState(() {
_isLoginProgress = true;
});
if (_formkey.currentState!.validate()) {
AuthService authService = AuthService();
AuthUser? user = await authService.userLogin(
_emailTextEditingController.text,
_passwordTextEditingController.text);
if (user != null) {
setState(() {
ref.read(authUserStateProvider.notifier).addLogin(user);
});
}
}
setState(() {
_isLoginProgress = false;
});
}
Future<void> _submitToken(String token) async {
setState(() {
_isLoginProgress = true;
});
AuthService authService = AuthService();
AuthUser? user = await authService.userFromToken(token);
//////////////print("---user is ${user} ---");
if (user != null) {
setState(() {
_isLoginProgress = false;
ref.read(authUserStateProvider.notifier).addLogin(user);
});
} else {}
}
void checkUser() async {
String? token =
await ref.read(authUserStateProvider.notifier).tokenFromDevice();
//////////////print("--- red token is ${token} ---");
if (token != null) {
await _submitToken(token);
final id = ref.read(authUserStateProvider).id;
if (id != null) {
if (context.mounted) {
Navigator.of(context)
.push(MaterialPageRoute(builder: (ctx) => const HomePage()));
}
}
}
return;
}
@override
Widget build(BuildContext context) {
if (ref.read(authUserStateProvider).id != null) {
Navigator.of(context)
.push(MaterialPageRoute(builder: (ctx) => const HomePage()));
}
return Scaffold(
resizeToAvoidBottomInset: true,
body: KeyboardDismisser(
gestures: const [
GestureType.onTap,
//GestureType.onVerticalDragDown
],
child: Center(
child: SizedBox(
child: Stack(
clipBehavior: Clip.none,
children: [
buildAuthCard(),
buildLogo(),
],
),
)),
),
);
}
Positioned buildLogo() {
return Positioned(
top: -170,
left: MediaQuery.of(context).size.width / 2 - 100,
child: Center(
child: Container(
alignment: Alignment.center,
width: 200,
height: 200,
decoration: const BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: AssetImage('assets/images/appicon.png'),
fit: BoxFit.fill),
),
),
),
);
}
Widget buildAuthCard() {
return Card(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Form(
key: _formkey,
child: Padding(
padding: const EdgeInsets.only(
top: 40, bottom: 10, left: 12, right: 12),
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(10.0),
child: CFormTextField(
cFocus: focusEmail,
cController: _emailTextEditingController),
),
Padding(
padding: const EdgeInsets.all(10),
child: CPasswordTextField(
cController: _passwordTextEditingController,
cFocusNode: focusPwd,
),
),
const SizedBox(
height: 12,
),
buildControlls(),
// SizedBox(height: MediaQuery.of(context).viewInsets.bottom,)
],
),
),
),
],
),
);
}
Widget buildControlls() {
if (_isLoginProgress) {
return const Center(
child: CircularProgressIndicator(),
);
}
final usr = ref.read(authUserStateProvider);
return Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Padding(
padding: const EdgeInsets.only(right: 12.0),
child: ElevatedButton(
onPressed: _submit,
child: Text(_authMode == "login" ? "Submit" : "Register",
style: GoogleFonts.lato(
color: Theme.of(context).colorScheme.secondary))),
)
],
),
TextButton(
onPressed: () {
setState(() {
if (_authMode == 'login') {
_authMode = 'register';
} else {
_authMode = 'login';
}
});
},
child: Text(
_authMode == "login"
? "${usr.id} Dont have account, please Register"
: "Already Registered, Login",
style: GoogleFonts.lato(
color: Theme.of(context).colorScheme.primary,
decoration: TextDecoration.underline,
fontSize: 16,
fontWeight: FontWeight.bold),
),
),
],
);
}
}
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:rogapp/nrog/pages/auth_page.dart';
class PermissionPage extends StatefulWidget {
const PermissionPage({super.key});
@override
State<PermissionPage> createState() => _PermissionPageState();
}
class _PermissionPageState extends State<PermissionPage> {
bool hasNavigated = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_checkPermissionStatus();
});
}
_checkPermissionStatus() async {
PermissionStatus status = await Permission.location.status;
if (status.isGranted == false) {
if (context.mounted) {
showAlert(context);
}
} else if (status.isPermanentlyDenied) {
await requestPermission();
} else {
if (mounted) {
Navigator.of(context)
.push(MaterialPageRoute(builder: (_) => const AuthPage()));
}
}
}
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Text(""),
);
}
void showAlert(BuildContext context) {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: const Text('ロケーション許可'),
content: const SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text('このアプリでは、位置情報の収集を行います。'),
Text(
'岐阜ナビアプリではチェックポイントの自動チェックインの機能を可能にするために、現在地のデータが収集されます。アプリを閉じている時や、使用していないときにも収集されます。位置情報は、個人を特定できない統計的な情報として、ユーザーの個人情報とは一切結びつかない形で送信されます。お知らせの配信、位置情報の利用を許可しない場合は、この後表示されるダイアログで「許可しない」を選択してください。'),
],
),
),
actions: <Widget>[
ElevatedButton(
child: const Text('OK'),
onPressed: () {
requestPermission();
},
),
],
));
}
Future<void> requestPermission() async {
PermissionStatus permission = await Permission.location.status;
if (permission == PermissionStatus.permanentlyDenied) {
showPermanentAlert();
} else {
PermissionStatus newPermission = await Permission.location.request();
if (newPermission != PermissionStatus.granted) {
exit(0);
} else {
if (context.mounted) {
Navigator.of(context)
.push(MaterialPageRoute(builder: (_) => const AuthPage()));
}
}
}
}
void showPermanentAlert() {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: const Text('無効'),
content: const SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text('位置情報が無効になっています'),
Text(
'このアプリケーションへの位置情報アクセスが無効になっています。続行するには設定>プライバシーとセキュリティ>位置情報サービス>岐阜ナビ で有効にしてください。'),
],
),
),
actions: <Widget>[
ElevatedButton(
child: const Text('OK'),
onPressed: () async {
await openAppSettings().then(
(value) async {
if (value) {
if (await Permission
.location.status.isPermanentlyDenied ==
true &&
await Permission.location.status.isGranted ==
false) {
requestPermission(); /* opens app settings until permission is granted */
}
}
},
);
},
),
],
));
}
}
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_location_marker/flutter_map_location_marker.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:latlong2/latlong.dart';
import 'package:rogapp/provider/map_state_provider.dart';
import 'package:rogapp/widgets/base_layer_widget.dart';
class HomePage extends ConsumerStatefulWidget {
const HomePage({super.key});
@override
ConsumerState<HomePage> createState() => _HomePageState();
}
class _HomePageState extends ConsumerState<HomePage> {
StreamSubscription? subscription;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
final mapStateInstance = ref.watch(mapStateNotifierProvider);
return Scaffold(
//drawer: DrawerPage(),
appBar: AppBar(
title: const Text("Rogaining"),
actions: [
IconButton(
onPressed: () {
//Get.toNamed(AppPages.HISTORY);
},
icon: const Icon(Icons.history)),
IconButton(
onPressed: () {
// final tk = indexController.currentUser[0]["token"];
// if (tk != null) {
// destinationController.fixMapBound(tk);
// }
},
icon: const Icon(Icons.refresh)),
InkWell(
onTap: () {
//Get.toNamed(AppPages.SEARCH);
},
child: Container(
height: 32,
width: 75,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(25),
),
child: const Center(
child: Icon(Icons.search),
),
),
),
//CatWidget(indexController: indexController,),
],
),
body: Center(
child: FlutterMap(
mapController: mapStateInstance.mapController,
options: MapOptions(
maxZoom: 18.4,
onMapReady: () {
// indexController.is_mapController_loaded.value = true;
subscription = mapStateInstance.mapController?.mapEventStream
.listen((MapEvent mapEvent) {
if (mapEvent is MapEventMoveStart) {
//print(DateTime.now().toString() + ' [MapEventMoveStart] START');
// do something
}
// if (mapEvent is MapEventMoveEnd &&
// indexController.currentUser.isEmpty) {
//print(DateTime.now().toString() + ' [MapEventMoveStart] END');
// indexController.loadLocationsBound();
//indexController.rogMapController!.move(c.center, c.zoom);
// }
});
},
center: LatLng(37.15319600454702, 139.58765950528198),
//bounds:
zoom: 18,
interactiveFlags: InteractiveFlag.pinchZoom | InteractiveFlag.drag,
onPositionChanged: (MapPosition pos, isvalue) {
//indexController.currentBound = [pos.bounds!];
},
// onTap: (_, __) => popupController
// .hideAllPopups(), // Hide popup when the map is tapped.
),
children: [
const BaseLayer(),
CurrentLocationLayer(
followOnLocationUpdate: FollowOnLocationUpdate.once,
turnOnHeadingUpdate: TurnOnHeadingUpdate.never,
style: const LocationMarkerStyle(
marker: DefaultLocationMarker(
child: Icon(
Icons.navigation,
color: Colors.yellowAccent,
),
),
markerSize: Size(27, 27),
markerDirection: MarkerDirection.heading,
),
),
const MarkerLayer(markers: [])
],
),
),
);
}
}
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:rogapp/model/auth_user.dart';
import 'package:shared_preferences/shared_preferences.dart';
final authUserStateProvider =
StateNotifierProvider<AuthUserState, AuthUser>((ref) {
return AuthUserState();
});
class AuthUserState extends StateNotifier<AuthUser> {
AuthUserState() : super(AuthUser());
Future<void> saveToDevice(String val) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString("user_token", val);
}
Future<String?> tokenFromDevice() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getString("user_token");
}
Future<void> addLogin(AuthUser user) async {
state = user;
await saveToDevice(user.auth_token!);
}
void removeLogin() {
state = AuthUser();
}
}
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:rogapp/model/map_state_instance.dart';
final mapStateNotifierProvider =
StateNotifierProvider<MapState, MapStateInstance>((ref) {
return MapState();
});
class MapState extends StateNotifier<MapStateInstance> {
MapState() : super(MapStateInstance());
@override
MapStateInstance get state => super.state;
void startGame(MapStateInstance mi) {
state = mi;
}
}
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:rogapp/model/game_state_instance.dart';
final gameStateNotifierProvider =
StateNotifierProvider<GameStaticState, GameInsStatetance>((ref) {
return GameStaticState();
});
class GameStaticState extends StateNotifier<GameInsStatetance> {
GameStaticState() : super(GameInsStatetance());
@override
GameInsStatetance get state => super.state;
void startGame(GameInsStatetance gi) {
state = gi;
}
void doCheckin() {}
void makeGoal() {}
}
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:rogapp/model/destination.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import '../model/rog.dart';
class DatabaseHelper {
DatabaseHelper._privateConstructor();
static final DatabaseHelper instance = DatabaseHelper._privateConstructor();
static Database? _database;
Future<Database> get database async => _database ??= await _initDatabase();
Future<Database> _initDatabase() async {
Directory documentDirectory = await getApplicationDocumentsDirectory();
String path = join(documentDirectory.path, 'rog.db');
// return await openDatabase(
// path,
// version: 1,
// onCreate: _onCreate,
// );
return openDatabase(
join(
await getDatabasesPath(),
'rog.db',
),
version: 1,
onCreate: _onCreate);
}
Future _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE destination(
location_id INTEGER PRIMARY KEY,
name TEXT,
address TEXT,
phone TEXT,
email TEXT,
webcontents TEXT,
videos TEXT,
category TEXT,
series INTEGER,
lat REAL,
lon REAL,
list_order INTEGER,
photos TEXT,
checkin_radious REAL,
sub_loc_id TEXT,
auto_checkin INTEGER,
selected INTEGER,
checkedin INTEGER,
cp REAL,
checkin_point REAL,
buy_point REAL,
hidden_location INTEGER,
checkin_image TEXT,
buypoint_image TEXT,
forced_checkin INTEGER,
recipt_times INTEGER,
tags TEXT
)
''');
await db.execute('''
CREATE TABLE rogaining(
rog_id INTEGER PRIMARY KEY AUTOINCREMENT,
course_id INTEGER,
location_id INTEGER,
user_id INTEGER,
lat REAL,
lon REAL,
time_stamp INTEGER,
image TEXT
)
''');
await db.execute('''
CREATE TABLE rog(
id INTEGER PRIMARY KEY AUTOINCREMENT,
team_name TEXT,
event_code TEXT,
user_id INTEGER,
cp_number INTEGER,
checkintime INTEGER,
image TEXT,
rog_action_type INTEGER
)
''');
}
Future<List<Rog>> allRogianing() async {
Database db = await instance.database;
var rog = await db.query('rog');
List<Rog> roglist =
rog.isNotEmpty ? rog.map((e) => Rog.fromMap(e)).toList() : [];
//print("--------- $rog");
return roglist;
}
Future<List<Rog>> getRogainingByLatLon(double lat, double lon) async {
Database db = await instance.database;
var rog = await db.query('rog', where: "lat = $lat and lon= $lon");
List<Rog> roglist =
rog.isNotEmpty ? rog.map((e) => Rog.fromMap(e)).toList() : [];
return roglist;
}
Future clearSelection() async {
Database db = await instance.database;
Map<String, dynamic> rowClear = {"selected": false};
return await db.update("destination", rowClear);
}
Future<int> toggleSelecttion(Destination dest) async {
Database db = await instance.database;
bool val = !dest.selected!;
Map<String, dynamic> rowTarget = {"selected": val};
await clearSelection();
return await db.update("destination", rowTarget,
where: 'location_id = ?', whereArgs: [dest.location_id!]);
}
Future<int> deleteRogaining(int id) async {
Database db = await instance.database;
var rog = await db.delete('rog', where: "id = $id");
int ret = rog > 0 ? rog : -1;
return ret;
}
Future<void> deleteAllRogaining() async {
Database db = await instance.database;
await db.delete('rog');
}
Future<bool> isRogAlreadyAvailable(int id) async {
Database db = await instance.database;
var rog = await db.query('rog', where: "id = $id");
return rog.isNotEmpty ? true : false;
}
Future<int?> latestGoal() async {
Database db = await instance.database;
return Sqflite.firstIntValue(
await db.rawQuery('SELECT MAX(checkintime) FROM rog'));
}
Future<int> insertRogaining(Rog rog) async {
Database db = await instance.database;
int? nextOrder =
Sqflite.firstIntValue(await db.rawQuery('SELECT MAX(id) FROM rog'));
nextOrder = nextOrder ?? 0;
nextOrder = nextOrder + 1;
rog.id = nextOrder;
int res = await db.insert(
'rog',
rog.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
//print("------ database helper insert $res-----------::::::::");
return res;
}
Future<List<Destination>> getDestinations() async {
Database db = await instance.database;
var dest = await db.query('destination', orderBy: 'list_order');
List<Destination> destList =
dest.isNotEmpty ? dest.map((e) => Destination.fromMap(e)).toList() : [];
//print("--------- $destList");
return destList;
}
Future<List<Destination>> getDestinationById(int id) async {
Database db = await instance.database;
var rog = await db.query('destination', where: "location_id = $id");
List<Destination> deslist =
rog.isNotEmpty ? rog.map((e) => Destination.fromMap(e)).toList() : [];
return deslist;
}
Future<List<Destination>> getDestinationByLatLon(
double lat, double lon) async {
Database db = await instance.database;
var dest = await db.query('destination',
where: "lat = $lat and lon= $lon", orderBy: 'list_order');
List<Destination> destList =
dest.isNotEmpty ? dest.map((e) => Destination.fromMap(e)).toList() : [];
return destList;
}
Future<int> deleteDestination(int locationId) async {
Database db = await instance.database;
var dest =
await db.delete('destination', where: "location_id = $locationId");
int ret = dest > 0 ? dest : -1;
//after deleting set correct order
await setOrder();
return ret;
}
Future<void> setOrder() async {
Database db = await instance.database;
var byOrder = await db.query('destination', orderBy: 'list_order');
List<Destination> desDb = byOrder.isNotEmpty
? byOrder.map((e) => Destination.fromMap(e)).toList()
: [];
int order = 1;
for (Destination d in desDb) {
Map<String, dynamic> rowTarget = {"list_order": order};
await db.update("destination", rowTarget,
where: 'location_id = ?', whereArgs: [d.location_id]);
order += 1;
}
}
Future<void> deleteAllDestinations() async {
Database db = await instance.database;
await db.delete('destination');
}
Future<bool> isAlreadyAvailable(int locationId) async {
Database db = await instance.database;
var dest =
await db.query('destination', where: "location_id = $locationId");
return dest.isNotEmpty ? true : false;
}
Future<int> insertDestination(Destination dest) async {
await deleteDestination(dest.location_id!);
Database db = await instance.database;
int? nextOrder = Sqflite.firstIntValue(
await db.rawQuery('SELECT MAX(list_order) FROM destination'));
nextOrder = nextOrder ?? 0;
nextOrder = nextOrder + 1;
dest.list_order = nextOrder;
int res = await db.insert(
'destination',
dest.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
//print("------ database helper insert ${dest.toMap()}-----------::::::::");
return res;
}
Future<int> updateCancelBuyPoint(Destination destination) async {
//print("---- updating puypint image in db -----");
Database db = await instance.database;
Map<String, dynamic> row = {"buy_point": 0};
return await db.update("destination", row,
where: 'location_id = ?', whereArgs: [destination.location_id!]);
}
Future<int> updateBuyPoint(Destination destination, String imageUrl) async {
//print("---- updating puypint image in db -----");
Database db = await instance.database;
Map<String, dynamic> row = {"buypoint_image": imageUrl};
return await db.update("destination", row,
where: 'location_id = ?', whereArgs: [destination.location_id!]);
}
Future<int> updateAction(Destination destination, bool checkin) async {
Database db = await instance.database;
int act = checkin == false ? 0 : 1;
Map<String, dynamic> row = {"checkedin": act};
return await db.update("destination", row,
where: 'location_id = ?', whereArgs: [destination.location_id!]);
}
Future<void> updateOrder(Destination d, int dir) async {
Database db = await instance.database;
var target = await db.query('destination',
where: "list_order = ${d.list_order! + dir}");
var dest =
await db.query('destination', where: "location_id = ${d.location_id}");
// print("--- target in db is $target");
// print("--- destine in db is $dest");
if (target.isNotEmpty) {
List<Destination> targetIndb = target.isNotEmpty
? target.map((e) => Destination.fromMap(e)).toList()
: [];
List<Destination> destIndb = dest.isNotEmpty
? dest.map((e) => Destination.fromMap(e)).toList()
: [];
Map<String, dynamic> rowTarget = {"list_order": d.list_order};
Map<String, dynamic> rowDes = {
"list_order": destIndb[0].list_order! + dir
};
// print("--- target destination is ${target_indb[0].location_id}");
// print("--- destine destination is is ${dest_indb[0].location_id}");
await db.update("destination", rowTarget,
where: 'location_id = ?', whereArgs: [targetIndb[0].location_id]);
await db.update("destination", rowDes,
where: 'location_id = ?', whereArgs: [destIndb[0].location_id]);
}
}
// Future<int?> getPending() async{
// Database db = await instance.database;
// return await Sqflite.firstIntValue(await db.rawQuery("SELECT COUNT(*) FROM incidents"));
// }
}
import 'package:get/get.dart';
class StringValues extends Translations{
@override
Map<String, Map<String, String>> get keys => {
'en_US': {
'drawer_title':'Rogaining participants can view checkpoints by logging in',
'app_title': '- Rogaining -',
'address':'address',
'bib':'Bib number',
'email':'Email address',
'password':'Password',
'web':'Web',
'wikipedia':'Wikipedia',
'video':'video',
'description':'Description',
'telephone':'Tel',
'how_nice': 'How nice',
'want_to_go': "Want to Go",
'schedule_point': "Schedule point",
'login': 'Login',
'logout':'Logout',
'sign_up':'Sign up',
'change_password': 'Change Password',
'profile': 'Profile',
'recommended_route': 'Recommended Route',
'point_rank': 'Point Rank',
'game_rank': 'Game Rank',
'my_route': 'My Route',
'visit_history': 'Visit History',
'rog_web': 'rog website',
'no_values': 'No Values',
'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',
'walking':'Walking',
'driving': 'Driving',
'transit': 'Transit',
'are_you_sure_want_to_delete_all' : 'Are you sure want to delete all',
'all_added_destination_will_be_deleted' : 'All added destination will be deleted',
'confirm': 'Confirm',
'cancel': 'Cancel',
'all_destinations_are_deleted_successfully' : 'All destinations are deleted successfully',
'deleted': 'Deleted',
'remarks' : 'Remarks',
'old_password' : 'Old Password',
'new_password' : 'New Password',
'values_required' : 'Values Required',
'failed' : 'Failed',
'password_change_failed_please_try_again' : 'Password change failed, please try again.',
'user_registration_failed_please_try_again' : 'User registration failed, please try again.',
'all': 'All',
'sight_seeing': 'Sight seeing',
'rogaining' : 'Rogaining',
'finishing_rogaining' : 'Finishing Rogaining',
'cp_pls_take_photo' : "CP please take a photo",
'take_photo of the clock' : 'Take photo of the clock',
'finish_goal': 'finish Goal',
'goal_saved': "Goal Saved",
'goal_added_successfuly' : 'Goal added successfully',
'goal_not_added' : 'Goal not added',
'please_try_again' : 'Please try again',
"Click start to start rogaining":"Click start to start rogaining",
"you are at roganing point, start rogaining":"you are at roganing point, start rogaining",
"Start":"Start",
"Rogaining Started":"Rogaining Started",
"Rogaining session started":"Rogaining session started",
"Not started yet":"Not started yet",
"You have not started rogaining yet.":"You have not started rogaining yet.",
"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",
"app_developed_by_gifu_dx": "This app was developed by the Gifu Prefecture DX subsidy project.",
'location_permission_title': 'Location Permission',
'location_permission_content': 'Gifu Navi app collects location data to provide better services.\nLocation data is used for automatic check-in at checkpoints and delivery of notifications.\nLocation data may be collected even when the app is closed or not in use.\nCollected location data is only used as statistical information that cannot identify individuals and is never linked to personal information.\nIf you do not allow the use of location data, please select "Do not allow" on the next screen.',
'location_disabled_title': 'Location Service Disabled',
'location_disabled_content': 'Location information is disabled.\nTo continue, please enable location services for Gifu Navi in Settings > Privacy and Security > Location Services.',
'drawer_title': 'Rogaining participants can view checkpoints by logging in',
'app_title': 'Travel Itinerary',
'want_to_go': 'Want to Go',
'schedule_point': 'Schedule Point',
'rog_web': 'Rogaining Website',
'rogaining_user_need_tosign_up': "Rogaining participants do not need to sign up.",
'add_location': 'Gifu',
'finish': 'Finish',
'my_route': 'My Route',
'visit_history': 'Visit History',
'search': 'Search',
'login': 'Login',
'password': 'Password',
'already_have_account': 'Already have an account?',
'sign_up': 'Sign Up',
'create_account': 'Create an account, it\'s free',
'confirm_password': 'Confirm Password',
'cancel_checkin': 'Cancel Check-in',
'go_here': 'Show route',
'cancel_route':'Clear route',
'start_rogaining': 'Start Rogaining',
'in_game': 'In Game',
'finish_rogaining': 'Finish Rogaining',
'checkin': 'Check-in',
'rogaining_not_started': 'Rogaining not started',
'confirm': 'Confirm',
'clear_rog_data_message': 'Starting rogaining will clear all previous rogaining data. Are you sure you want to start?',
'no': 'No',
'yes': 'Yes',
'retake': 'Retake',
'take_photo': 'Take Photo',
'take_receipt_photo': 'Please take a photo of the receipt',
'buypoint_added': 'Buypoint added.',
'no_purchase': 'No Purchase',
'complete': 'Complete',
'movement_history': 'Movement History',
'pass_history': 'Pass History',
'no_checkin_yet': 'No check-in yet',
'game_status': 'Game Status',
'take_cp_photo': 'This is a CP. Please take a photo.',
'save_goal_success': 'Goal saved successfully',
'save_goal_failed': 'Goal not added',
'please_try_again': 'Please try again',
'click_start_to_start_rogaining': 'Click start to start rogaining',
'at_rogaining_point_start': 'You are at a rogaining point, start rogaining',
'start': 'Start',
'rogaining_started': 'Rogaining Started',
'rogaining_session_started': 'Rogaining session started',
'not_started_yet': 'Not started yet',
'not_started_rogaining_yet': 'You have not started rogaining yet.',
'not_reached_goal_yet': 'Not reached the goal yet',
'not_reached_goal_yet_message': 'You have not reached the goal yet.',
'reload_qr': 'Reload QR',
'read_qr': 'Read QR',
'read_qr_code': 'Please read the QR code',
'canceled': 'Canceled',
'checkin_failed_try_again': 'Check-in failed. Please tap the checkpoint again if necessary.',
'rogaining_not_started': 'Rogaining not started',
'need_to_start_rogaining': 'You need to tap the start button to begin rogaining',
'no_destination': 'No destination',
'near_cp_not_checkin': 'Near a CP or distance-ignored CP, in-game but not checked in yet.',
'auto_checkin_case': 'Auto check-in case',
'non_auto_checkin_case': 'Non-auto check-in case',
'normal_cp_case': 'Normal CP case',
'non_normal_cp_case': 'Non-normal CP case... what case?',
'goal_clock_photo_case': 'Goal clock photo case',
'start_case_24_hours_passed': 'Start case and 24 hours have passed since the last goal',
'start_cp_24_hours_passed': 'At the start CP, and 24 hours have passed since the last goal,',
'standard_cp_not_checkin': 'Standard CP not checked in yet.',
'after_checkin_buypoint_case': 'After check-in, buypoint case.',
'goal_case': 'Goal case',
'start_case': 'Start case',
'no_match_skip_process': 'Does not match any conditions, skipping process',
'server_error_occurred': 'A server error occurred',
'could_not_communicate_with_server': 'Could not communicate with the server',
'communication_error_occurred': 'A communication error occurred',
'checked_in': 'Checked in.',
'cancel_checkin': 'Cancel Check-in',
'checkin_canceled_for': 'Check-in canceled for',
'error': 'Error',
'failed_to_cancel_checkin': 'Failed to cancel check-in.',
'buypoint_added': 'Buypoint added',
'error_occurred': 'An error occurred',
'failed_to_process_checkpoint': 'Failed to process the checkpoint.',
'start_rogaining': 'Start Rogaining',
'in_competition': 'In Competition',
'map_auto_return_message': 'If there is no map operation, it will automatically return to the current location. Please enter the timer seconds. If you check the checkbox, auto-return will not be performed.',
'no_auto_return': 'No Auto Return',
'failed_to_load_markers': 'Failed to load markers',
'screen_switching_error': 'Screen switching error',
'failed_to_switch_screen': 'Failed to switch the screen',
'timer_duration': 'Timer Duration',
'user_data_deletion': 'User Data Deletion',
'user_consent_set_for_data_deletion': 'User consent is set for data deletion. User data has been deleted from the app and server',
'go_to_gps_signal_area': 'Please go to an area with GPS signal.',
'location_service_disabled': 'Location service is disabled. Please enable location service from the settings screen. If you are unsure, please contact the engineering staff.',
'location_permission_not_granted': 'Location permission is not granted. Please allow location service for Gifu Navi from the settings screen. If you are unsure, please contact the engineering staff.',
'location_service_issue_occurred': 'An issue occurred with the location service. The location service is being restarted, please wait a moment.',
'login_failed': 'Login Failed',
'check_login_id_or_password': 'Please check your login ID or password.',
'communication_error_occurred': 'A communication error occurred',
'could_not_communicate_with_server': 'Could not communicate with the server',
'before_game': 'Before Game',
'location_permission_denied_title': 'Location Permission Denied',
'location_permission_denied_message': 'This app requires location permission to function properly. Please grant location permission to continue.',
'location_permission_permanently_denied_title': 'Location Permission Permanently Denied',
'location_permission_permanently_denied_message': 'Location permission has been permanently denied. Please open app settings to grant location permission.',
'open_settings': 'Open Settings',
'location_permission_needed_title': 'Location Permission Needed',
'location_permission_needed_main': 'Location permissions have been permanently denied. Please open app settings to enable location permissions.',
'open_settings': 'Open Settings',
'location_services_disabled_title': 'Location Services Disabled',
'location_service_disabled_main': 'Please enable location services to continue using the app.',
'location_permission_denied_title': 'Location Permission Denied',
'location_permission_denied_main': 'This app requires location permissions to function properly. Please enable location permissions in your device settings.',
'home': 'Home',
'welcome': 'Welcome to Gifu Navi',
'location_disabled_message': 'Location services are disabled. Some features may not work properly.',
'enable_location_service': 'Enable Location Service',
'start_app': 'Start App',
'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',
'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.',
},
'ja_JP': {
'drawer_title':'ロゲイニング参加者はログイン するとチェックポイントが参照 できます',
'app_title': '旅行工程表',
'address':'住所',
'bib':'ゼッケン番号',
'email':'メールアドレス',
'password':'パスワード',
'web':'ウェブ',
'wikipedia':'ウィキペディア',
'video':'ビデオ',
'description':'説明',
'telephone':'電話',
'how_nice':'いいね',
'want_to_go': '行きたい',
'like': 'お気に入り',
'checkin': 'チェックイン',
'schedule_point': '予定地点',
'login': 'ログイン',
'logout':'ログアウト',
'sign_up': 'サインアップ',
'change_password': 'パスワード変更',
'profile': 'プロフィール',
'recommended_route': '推奨ルート',
'point_rank': '地点ランキング',
'game_rank': 'ゲームランキング',
'my_route': 'マイルート',
'visit_history': '訪問履歴',
'rog_web': 'ロゲイニングウェブサイト',
'no_values': '値なし',
'email_and_password_required': 'メールとパスワードの入力が必要です',
'rogaining_user_need_tosign_up': "ロゲイニング参加者はサインアップの必要はありません。",
'add_location': '岐阜',
'select_travel_mode':'移動モードを選択してください',
'walking':'歩行',
'driving': '自動車利用',
'transit': '公共交通機関利用',
'are_you_sure_want_to_delete_all' : '本当にすべて削除しますか',
'all_added_destination_will_be_deleted' : '追加したすべての宛先が削除されます',
'confirm': '確認',
'cancel': 'キャンセル',
'all_destinations_are_deleted_successfully' : 'すべての宛先が正常に削除されました',
'deleted': "削除されました",
'remarks' : '備考',
'old_password' : '以前のパスワード',
'new_password' : '新しいパスワード',
'values_required' : '必要な値',
'failed' : '失敗',
'password_change_failed_please_try_again' : 'パスワードの変更に失敗しました。もう一度お試しください',
'user_registration_failed_please_try_again' : 'ユーザー登録に失敗しました。もう一度お試しください',
'all': '全て',
'sight_seeing': '観光',
'rogaining' : 'ロゲイニング',
'finishing_rogaining' : 'ロゲイニングを終えて',
'cp_pls_take_photo' : "CPです。撮影してください。",
'take_photo of the clock' : '時計の写真を撮る',
'finish_goal': 'ゴール完了',
'goal_saved': "目標を保存しました",
'goal_added_successfuly' : '目標が正常に追加されました',
'goal_not_added' : '目標が追加されていません',
'please_try_again' : 'もう一度お試しください',
"Click start to start rogaining":"開始をクリックして、ロゲイニングを開始します",
"you are at roganing point, start rogaining":"あなたはロゲイニングポイントにいます、ロゲイニングを始めてください",
"Start":"始める",
"Rogaining Started":"ロゲイニング開始",
"Rogaining session started":"ロゲイニングセッション開始",
"Not started yet":"まだ開始されていません",
"You have not started rogaining yet.":"あなたはまだロゲイニングを始めていません。",
"Not reached the goal yet": "まだ目標に達していない",
"You have not reached the goal yet.":"あなたはまだゴールに達していません。",
"delete_account": "アカウントを削除します",
"delete_account_title": "アカウントを削除しますがよろしいですか?",
"delete_account_middle": "これにより、アカウント情報とすべてのゲーム データが削除され、すべての状態が削除されます",
"accounted_deleted": "アカウントが削除されました",
"account_deleted_message": "あなたのアカウントは正常に削除されました",
"privacy": "プライバシーポリシー",
"app_developed_by_gifu_dx": "※このアプリは令和年度岐阜県DX補助金事業で開発されました。",
'location_permission_title': 'ロケーション許可',
'location_permission_content': 'このアプリでは、位置情報の収集を行います。\n岐阜ナビアプリではチェックポイントの自動チェックインの機能を可能にするために、現在地のデータが収集されます。アプリを閉じている時や、使用していないときにも収集されます。位置情報は、個人を特定できない統計的な情報として、ユーザーの個人情報とは一切結びつかない形で送信されます。お知らせの配信、位置情報の利用を許可しない場合は、この後表示されるダイアログで「許可しない」を選択してください。',
'location_disabled_title': '位置情報サービスが無効です',
'location_disabled_content': '位置情報が無効になっています\nこのアプリケーションへの位置情報アクセスが無効になっています。続行するには設定>プライバシーとセキュリティ>位置情報サービス>岐阜ナビ で有効にしてください。',
'drawer_title': 'ロゲイニング参加者はログイン するとチェックポイントが参照 できます',
'app_title': '旅行工程表',
'want_to_go': '行きたい',
'schedule_point': '予定地点',
'rog_web': 'ロゲイニングウェブサイト',
'rogaining_user_need_tosign_up': "ロゲイニング参加者はサインアップの必要はありません。",
'add_location': '岐阜',
'finish': '終了する',
'my_route': 'マイルート',
'visit_history': '訪問履歴',
'search': '検索',
'login': 'ログイン',
'password': 'パスワード',
'already_have_account': 'すでにアカウントをお持ちですか?',
'sign_up': 'サインアップ',
'create_account': 'アカウントを無料で作成します',
'confirm_password': 'パスワードを認証する',
'cancel_checkin': 'チェックイン取消',
'go_here': 'ルート表示',
'cancel_route':'ルート消去',
'start_rogaining': 'ロゲ開始',
'in_game': '競技中',
'finish_rogaining': 'ロゲゴール',
'checkin': 'チェックイン',
'rogaining_not_started': 'ロゲは始まっていません',
'confirm': '確認',
'clear_rog_data_message': 'ロゲを開始すると、今までのロゲデータが全てクリアされます。本当に開始しますか?',
'no': 'いいえ',
'yes': 'はい',
'retake': '再撮影',
'take_photo': '撮影',
'take_receipt_photo': 'レシートの写真を撮ってください',
'buypoint_added': 'お買い物加点を行いました。',
'no_purchase': '買い物なし',
'complete': '完了',
'movement_history': '移動履歴',
'pass_history': '通過履歴',
'no_checkin_yet': 'チェックインはまだされてません',
'game_status': 'ゲームステータス',
'take_cp_photo': 'CPです。撮影してください。',
'save_goal_success': '目標が保存されました',
'save_goal_failed': '目標が追加されていません',
'please_try_again': 'もう一度お試しください',
'click_start_to_start_rogaining': 'ロゲを開始するには開始をクリックしてください',
'at_rogaining_point_start': 'あなたはロガニングポイントにいます、ロガニングを始めてください',
'start': '開始',
'rogaining_started': 'ロゲイニングを開始しました',
'rogaining_session_started': 'ロゲイニングセッションを開始しました',
'not_started_yet': 'まだ開始されていません',
'not_started_rogaining_yet': 'あなたはまだロゲイニングを始めていません。',
'not_reached_goal_yet': 'まだゴールに達していません',
'not_reached_goal_yet_message': 'あなたはまだゴールに達していません。',
'reload_qr': '再QR読込',
'read_qr': 'QR読込',
'read_qr_code': 'QRコードを読み取ってください',
'canceled': 'キャンセルされました',
'checkin_failed_try_again': 'チェックインしていません。必要ならもう一度チェックポイントをタップして下さい。',
'rogaining_not_started': 'ロゲが始まっていません',
'need_to_start_rogaining': 'ロゲ開始ボタンをタップして、ロゲイニングを始める必要があります',
'no_destination': '目的地がない場合',
'near_cp_not_checkin': '検知範囲または距離無視CPで、ゲーム中でまだチェックインしていない。',
'auto_checkin_case': '自動チェックインの場合',
'non_auto_checkin_case': '自動チェックイン以外の場合',
'normal_cp_case': '通常CPの場合',
'non_normal_cp_case': '通常CP以外の場合....どんな場合?',
'goal_clock_photo_case': 'ゴールで時計撮影の場合',
'start_case_24_hours_passed': 'スタートの場合で最後のゴールから24時間経過している場合',
'start_cp_24_hours_passed': '開始CPで、最後にゴールしてから時間経過していれば、',
'standard_cp_not_checkin': '標準CP まだチェックインしていない。',
'after_checkin_buypoint_case': 'チェックイン後で買い物ポイントの場合。',
'goal_case': 'ゴールの場合',
'start_case': 'スタートの場合',
'no_match_skip_process': 'いずれにも当てはまらないので、処理スキップ',
'server_error_occurred': 'サーバーエラーがおきました',
'could_not_communicate_with_server': 'サーバーと通信できませんでした',
'communication_error_occurred': '通信エラーがおきました',
'checked_in': 'チェックインしました。',
'cancel_checkin': 'チェックイン取消',
'checkin_canceled_for': 'のチェックインは取り消されました',
'error': 'エラー',
'failed_to_cancel_checkin': 'チェックイン取り消しに失敗しました。',
'buypoint_added': 'お買い物加点を行いました',
'error_occurred': 'エラーがおきました',
'failed_to_process_checkpoint': 'チェックポイントの処理に失敗しました。',
'start_rogaining': 'ロゲ開始',
'in_competition': '競技中',
'map_auto_return_message': 'マップ操作がなければ自動的に現在地に復帰します。そのタイマー秒数を入れて下さい。チェックボックスをチェックすると、自動復帰は行われなくなります。',
'no_auto_return': '自動復帰なし',
'failed_to_load_markers': 'マーカーの読み込みに失敗しました',
'screen_switching_error': '画面切り替えでエラー',
'failed_to_switch_screen': '画面の切り替えができませんでした',
'timer_duration': 'タイマーの長さ',
'user_data_deletion': 'ユーザーデータを削除する',
'user_consent_set_for_data_deletion': 'データを削除するためにユーザーの同意が設定されています アプリとサーバーでユーザーデータが削除されました',
'go_to_gps_signal_area': 'GPSの届く場所に行って、信号を拾ってください。',
'location_service_disabled': '位置情報サービスが無効になっています。設定画面から位置情報サービスを有効にして下さい。不明な場合にはエンジニアスタッフにお問い合わせください。',
'location_permission_not_granted': '位置情報サービスが許可されていません。設定画面から岐阜ナビの位置情報サービスを許可して下さい。不明な場合にはエンジニアスタッフにお問い合わせください。',
'location_service_issue_occurred': '位置情報サービスに問題が発生しました。位置情報サービスを再起動していますので少しお待ちください。',
'login_failed': 'ログイン失敗',
'check_login_id_or_password': 'ログインIDかパスワードを確認して下さい。',
'communication_error_occurred': '通信エラーがおきました',
'could_not_communicate_with_server': 'サーバーと通信できませんでした',
'before_game': 'ゲーム前',
'location_permission_denied_title': '位置情報の許可が拒否されました',
'location_permission_denied_message': 'このアプリを適切に機能させるには、位置情報の許可が必要です。続行するには、位置情報の許可を付与してください。',
'location_permission_permanently_denied_title': '位置情報の許可が永久に拒否されました',
'location_permission_permanently_denied_message': '位置情報の許可が永久に拒否されました。位置情報の許可を付与するには、アプリ設定を開いてください。',
'open_settings': '設定を開く',
'storage_permission_needed_title': '写真ライブラリへの許可が必要です',
'storage_permission_needed_main': '岐阜ロゲでは、写真ライブラリを使用してスタート・チェックイン・ゴール等の通過照明写真の記録のために、写真ライブラリへの書き込みを行なっています。このためチェックイン時に写真をライブラリに保存する権限が必要です。設定画面で、「岐阜ナビ」に対して、ライブラリに写真の保存を許可するように設定してください。',
'location_permission_needed_title': '位置情報への許可が必要です',
'location_permission_needed_main': '岐阜ロゲでは、位置情報を使用してスタート・チェックイン・ゴール等の通過照明及び移動手段の記録のために、位置情報のトラッキングを行なっています。このためバックグラウンドでもトラッキングができるように位置情報の権限が必要です。設定画面で、「岐阜ナビ」に対して、常に位置情報を許可するように設定してください。',
'open_settings': '設定を開く',
'location_permission_denied_title': '位置情報へのアクセスが拒否されています。',
'location_permission_denied_main': 'この岐阜ナビアプリは正常に動かすには位置情報への許可が必要です。「設定」画面で位置情報の許可を指定してください。',
'location_services_disabled_title': '位置情報サービスが拒否されています',
'location_service_disabled_main': '岐阜ナビアプリを使用するには位置情報サービスを許可してください。',
'home': 'ホーム',
'welcome': '岐阜ナビへようこそ',
'location_disabled_message': '位置情報サービスが無効になっています。一部の機能が正しく動作しない可能性があります。',
'enable_location_service': '位置情報サービスを有効にする',
'start_app': 'アプリを開始する',
'location_permission_required_title': '位置情報の許可が必要です',
'location_permission_required_message': 'このアプリを使用するには、位置情報へのアクセスが必要です。続行するには許可を付与してください。',
'cancel': 'キャンセル',
'checkins': 'チェックイン',
'reset_button': 'リセット',
'reset_title': 'リセットしますがよろしいですか?',
'reset_message': 'これにより、すべてのゲーム データが削除され、すべての状態が削除されます',
'reset_done': 'リセット完了',
'reset_explain': 'すべてリセットされました。ロゲ開始から再開して下さい。',
'no_match': '不一致',
'password_does_not_match':'入力したパスワードが一致しません',
},
};
}
import 'package:geojson_vi/geojson_vi.dart';
import 'package:rogapp/model/destination.dart';
class TextUtils {
static String getDisplayTextFeture(GeoJSONFeature f) {
RegExp regex = RegExp(r'([.]*0)(?!.*\d)');
String txt = "";
if (f.properties!["sub_loc_id"] != null) {
txt = "${f.properties!["sub_loc_id"]}";
}
// if(f.properties!["cp"] > 0){
// //print("-- sub-- ${f.properties!["cp"]} ----");
// txt = "${f.properties!["cp"].toString().replaceAll(regex, '')}";
// }
//if(f.properties!["buy_point"] != null && f.properties!["buy_point"] > 0){
//txt = "$txt${f.properties!["sub_loc_id"]}";
//}
//print("Text = ${txt}");
return txt;
}
static String getDisplayText(Destination dp) {
RegExp regex = RegExp(r'([.]*0)(?!.*\d)');
String txt = "";
if (dp.cp! > 0) {
txt = dp.cp.toString().replaceAll(regex, '');
if (dp.checkin_point != null && dp.checkin_point! > 0) {
txt = "$txt{${dp.checkin_point.toString().replaceAll(regex, '')}}";
}
if (dp.buy_point != null && dp.buy_point! > 0) {
//print("^^^^^^^^^ ${dp.sub_loc_id}^^^^^^^^^^");
txt =
"#${dp.cp.toString().replaceAll(regex, '')}(${dp.checkin_point.toString().replaceAll(regex, '')}+${dp.buy_point.toString().replaceAll(regex, '')})";
}
}
return txt;
}
// static String getDisplayText(String num){
// RegExp regex = RegExp(r'([.]*0)(?!.*\d)');
// return "${num.replaceAll(regex, '')}";
// }
}
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:geolocator/geolocator.dart';
import 'package:latlong2/latlong.dart';
//import 'package:rogapp/widgets/debug_widget.dart';
import 'package:flutter_map_location_marker/flutter_map_location_marker.dart';
import 'package:rogapp/pages/destination/destination_controller.dart';
import 'package:rogapp/pages/permission/permission.dart';
// LocationControllerクラスは、GetxControllerを継承したクラスであり、位置情報の管理を担当しています。
// LocationControllerは以下の機能を提供しています。
// LocationControllerは、アプリ全体で位置情報を一元管理するための重要なコンポーネントです。
// 他のコンポーネントは、LocationControllerから位置情報を取得し、位置情報に関連する機能を実装することができます。
//
// Features:
// * 現在の位置情報を保持し、他のコンポーネントからアクセスできるようにします。
// * 位置情報のストリームを管理し、位置情報の更新を監視します。
// * 位置情報サービスの有効性と権限の確認を行い、適切な処理を行います。
// * 位置情報のストリームを開始、停止、再開する機能を提供します。
// * 位置マーカーの位置情報をStreamControllerを通じて他のコンポーネントに通知します。
//
// Logic:
// 1. startPositionStreamメソッドで、Geolocator.getPositionStreamを使用して位置情報のストリームを開始します。
// 2. ストリームから位置情報を受信すると、LocationMarkerPositionオブジェクトを作成し、locationMarkerPositionStreamControllerに追加します。
// 3. 位置情報が取得できなかった場合や、エラーが発生した場合は、適切な処理を行います。
// 4. stopPositionStreamメソッドで、位置情報のストリームを一時停止することができます。
// 5. resumePositionStreamメソッドで、一時停止中の位置情報のストリームを再開することができます。
// 6. onCloseメソッドで、コントローラーのクローズ時に位置情報のストリームをキャンセルします。
//
class LocationController extends GetxController {
// Reactive variable to hold the current position
Rx<Position?> currentPosition = Rx<Position?>(null);
// 現在の位置情報を保持するReactive変数です。Rx<Position?>型で宣言されています。
// Subscription to the position stream
StreamSubscription<Position>? positionStream;
// 位置情報のストリームを保持する変数です。StreamSubscription<Position>型で宣言されています。
LatLng? lastValidLocation;
DateTime lastGPSDataReceivedTime = DateTime.now(); // 最後にGPSデータを受け取った時刻
bool gpsDebugMode = true;
/*
// GPSシミュレーション用のメソッドを追加
void setSimulationMode(bool value) {
isSimulationMode = value;
}
// ====== Akira , GPS信号強度をシミュレート ==== ここから
//
//===== Akira Added 2024-4-9 start
// GPSシミュレーション用の変数を追加 ===> 本番では false にする。
bool isSimulationMode = false;
// GPS信号強度をシミュレートするための変数
final Rx<String> _simulatedSignalStrength = Rx<String>('high');
// GPS信号強度をシミュレートするための関数
void setSimulatedSignalStrength(String strength) {
if( strength!='real') {
isSimulationMode = true;
_simulatedSignalStrength.value = strength;
latestSignalStrength.value = strength;
}else{
isSimulationMode = false;
_simulatedSignalStrength.value = strength;
}
}
// シミュレートされた信号強度を取得するための関数
String getSimulatedSignalStrength() {
//debugPrint("strength : ${_simulatedSignalStrength.value}");
return _simulatedSignalStrength.value;
}
*/
//
// ====== Akira , GPS信号強度をシミュレート ==== ここまで
// GPS信号が弱い場合のフラグ. 本番では、false,high にする。
bool isGpsSignalWeak = false;
RxString latestSignalStrength = 'high'.obs;
//final _latestSignalStrength = 'low'.obs; // 初期値を設定
//String get latestSignalStrength => _latestSignalStrength.value;
Stream<String> get gpsSignalStrengthStream => latestSignalStrength.stream;
bool isRunningBackgroundGPS=false;
int activeEngineCount = 0;
// GPS信号の強弱を判断するメソッドを追加. 10m 以内強、30m以内中、それ以上
//
String getGpsSignalStrength(Position? position) {
if (isSimulationMode.value) {
return getSimulatedSignalStrength();
}
if (position == null) {
//gpsDebugMode ? debugPrint("getGpsSignalStrength position is null.") : null;
latestSignalStrength.value = "low";
isGpsSignalWeak = true;
return 'low';
}
final accuracy = position.accuracy;
//gpsDebugMode ? debugPrint("getGpsSignalStrength : ${accuracy}") : null;
/*
if(isSimulationMode){
return _simulatedSignalStrength.value; // GPS信号強度シミュレーション
}else {
*/
if (accuracy <= 10) {
latestSignalStrength.value = "high";
isGpsSignalWeak = false;
return 'high';
} else if (accuracy <= 50) {
latestSignalStrength.value = "medium";
isGpsSignalWeak = false;
return 'medium';
} else {
latestSignalStrength.value = "low";
isGpsSignalWeak = true;
return 'low';
}
// }
}
// 現在位置を調整するメソッドを追加
LatLng? adjustCurrentLocation(Position? position) {
if (position == null) {
if( lastValidLocation!=null ) {
//debugPrint("=== adjustCurrentLocation (Position:Null and using LastValidLocation ${lastValidLocation})===");
return LatLng(lastValidLocation!.latitude, lastValidLocation!.longitude);
}else {
print("=== adjustCurrentLocation (Position:Null and No LastValidLocation ... )===");
return null;
}
//return lastValidLocation ?? LatLng(0, 0);
}
final signalStrength = getGpsSignalStrength(position);
if (signalStrength == 'high' || signalStrength == 'medium') {
//debugPrint("=== adjustCurrentLocation (Position:Get and return Valid location:${position} ... )===");
lastValidLocation = LatLng(position.latitude, position.longitude);
}
return lastValidLocation ?? LatLng(lastValidLocation!.latitude, lastValidLocation!.longitude);
}
//===== Akira Added 2024-4-9 end
final locationMarkerPositionStreamController =
StreamController<LocationMarkerPosition?>.broadcast();
// 位置マーカーの位置情報を送信するためのStreamControllerです。
// StreamController<LocationMarkerPosition?>型で宣言されています。
bool isStreamPaused = false; // 位置情報のストリームが一時停止中かどうかを示すフラグです。bool型で宣言されています。
// 位置マーカーの位置情報のストリームを取得するゲッター関数です。
// locationMarkerPositionStreamController.streamを返します。
//
Stream<LocationMarkerPosition?> get locationMarkerPositionStream =>
locationMarkerPositionStreamController.stream;
// コントローラーの初期化時に呼び出されるライフサイクルメソッドです。
// startPositionStreamメソッドを呼び出して、位置情報のストリームを開始します。
//
@override
void onInit() {
super.onInit();
// Start listening to location updates when the controller is initialized
startPositionStream();
}
// 位置情報のストリームを開始するメソッドです。
// 位置情報サービスが有効か確認し、無効な場合はダイアログを表示します。
// 位置情報の権限を確認し、必要な権限がない場合は権限をリクエストします。
// 既存の位置情報のストリームをキャンセルします。
// Geolocator.getPositionStreamを使用して、新しい位置情報のストリームを開始します。
// ストリームから受信した位置情報をlocationMarkerPositionStreamControllerに追加します。
// エラーが発生した場合は、locationMarkerPositionStreamControllerにエラーを追加します。
// ストリームが一時停止中の場合は、ストリームを再開します。
//
// 2024-4-8 Akira : See 2809
// stopPositionStreamメソッドを追加して、既存のストリームをキャンセルするようにしました。また、ストリームが完了したらnullに設定し、エラー発生時にストリームをキャンセルするようにしました。
//
void startPositionStream() async {
// Check for location service and permissions before starting the stream
// 位置情報サービスの有効性をチェックし、無効な場合はエラーハンドリングを行います。
//
await PermissionController.checkAndRequestPermissions();
/*
bool isPermissionGranted = await PermissionController.checkLocationPermissions();
if (!isPermissionGranted) {
PermissionController.showPermissionDeniedDialog();
return;
}
*/
/*
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
// Use GetX's context to show a dialog
Get.dialog(
AlertDialog(
title: Text('location_services_disabled_title'.tr),
content: Text('location_services_disabled_title'.tr),
actions: <Widget>[
TextButton(
child: const Text('OK'),
onPressed: () {
// Close the dialog
Get.back();
// Optionally, you can direct the user to the settings page
// Geolocator.openLocationSettings();
},
),
],
),
barrierDismissible: false, // User must tap button to close dialog
);
return;
}
// 位置情報の権限をチェックし、拒否されている場合はエラーハンドリングを行います。
//
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
// Show a dialog if permissions are still denied
Get.dialog(
AlertDialog(
title: Text('location_permission_denied_title'.tr),
content: Text('location_permission_denied_main'.tr),
actions: <Widget>[
TextButton(
child: const Text('OK'),
onPressed: () {
// Close the dialog
Get.back();
// Optionally, direct the user to the app settings
// Geolocator.openAppSettings();
},
),
],
),
barrierDismissible: false,
);
return;
}
}
if (permission == LocationPermission.deniedForever) {
// Show a dialog if permissions are permanently denied
Get.dialog(
AlertDialog(
title: Text('location_permission_needed_title'.tr),
content: Text( 'location_permission_needed_main'.tr),
actions: <Widget>[
TextButton(
child: Text('open_settings'.tr),
onPressed: () {
// Close the dialog and open app settings
Get.back();
Geolocator.openAppSettings();
},
),
],
),
barrierDismissible: false,
);
return;
}
*/
// 位置情報の設定を行います。z11
// Set up the location options
const locationOptions =
LocationSettings(accuracy: LocationAccuracy.medium, distanceFilter: 0);
// 既存の位置情報のストリームをキャンセルします。
await positionStream?.cancel();
// 新しい位置情報のストリームを開始します。
//
positionStream = Geolocator.getPositionStream(locationSettings: locationOptions).listen(
(Position? position) {
//gpsDebugMode ? debugPrint("Position = ${position}"):null;
final signalStrength = getGpsSignalStrength(position);
if (signalStrength == 'low') {
isGpsSignalWeak = true;
//gpsDebugMode ? debugPrint("LocationController.getPositionStream : isGpsSignalWeak = ${isGpsSignalWeak}"):null;
} else {
isGpsSignalWeak = false;
//gpsDebugMode ? debugPrint("LocationController.getPositionStream : isGpsSignalWeak = ${isGpsSignalWeak}"):null;
}
final DestinationController destinationController = Get.find<DestinationController>();
// ロゲ開始前、終了後、GPS=low の場合は更新しない。
//
if (isGpsSignalWeak == false) {
//if (destinationController.isInRog.value && isGpsSignalWeak == false) {
final adjustedLocation = adjustCurrentLocation(position);
if (adjustedLocation != null) {
final locationMarkerPosition = LocationMarkerPosition(
latitude: adjustedLocation.latitude,
longitude: adjustedLocation.longitude,
accuracy: position?.accuracy ?? 0,
);
handleLocationUpdate(locationMarkerPosition);
//locationMarkerPositionStreamController.add(locationMarkerPosition); // 位置データ送信
} else {
// 位置情報が取得できなかった場合、
// locationMarkerPositionStreamControllerにnullを追加します。
locationMarkerPositionStreamController.add(null); // null 送信?
//forceUpdateLocation(Position? position);
}
//}else{
// debugPrint("GPS処理対象外");
}
},
onError: (e) {
// エラーが発生した場合、locationMarkerPositionStreamControllerにエラーを追加します。
locationMarkerPositionStreamController.addError(e);
},
onDone: () {
positionStream = null; // ストリームが完了したらnullに設定
},
cancelOnError: true // エラー発生時にストリームをキャンセル
);
// Resume stream if it was paused previously
// ストリームが一時停止中の場合、ストリームを再開します。
//
if (isStreamPaused) {
isStreamPaused = false;
positionStream!.resume();
}
}
// Method to stop the position stream
// 位置情報のストリームを停止するメソッドです。
// positionStreamが存在する場合、ストリームを一時停止します。
// isStreamPausedフラグをtrueに設定します。
//
void stopPositionStream() async {
if (positionStream != null) {
// updated Akira 2024-4-8
await positionStream!.cancel();
positionStream = null;
//positionStream!.pause();
//isStreamPaused = true;
}
}
// Method to resume the position stream
// 位置情報のストリームを再開するメソッドです。
// positionStreamが存在し、ストリームが一時停止中の場合、ストリームを再開します。
// isStreamPausedフラグをfalseに設定します。
//
void resumePositionStream() {
if (positionStream != null && isStreamPaused) {
positionStream!.resume();
isStreamPaused = false;
}
}
void handleLocationUpdate(LocationMarkerPosition? position) async {
//debugPrint("locationController.handleLocationUpdate");
try {
if (position != null) {
double currentLat = position.latitude;
double currentLon = position.longitude;
//debugPrint("Flutter: Received GPS signal. Latitude: $currentLat, Longitude: $currentLon");
//debugPrint("position = ${position}");
/*
currentPosition.value = position;
final locationMarkerPosition = LocationMarkerPosition(
latitude: position.latitude,
longitude: position.longitude,
accuracy: position.accuracy,
);
*/
lastGPSDataReceivedTime = DateTime.now(); // 最後にGPS信号を受け取った時刻
locationMarkerPositionStreamController.add(position);
}else{
gpsDebugMode ? debugPrint("Flutter: No GPS signal received."):null;
}
} catch( e ) {
debugPrint("Flutter: Error in handleLocationUpdate: $e");
}
}
// このメソッドは、現在の位置情報を locationMarkerPositionStreamController に送信します。
//
void forceUpdateLocation(Position? position) {
if (position != null) {
final adjustedLocation = adjustCurrentLocation(position);
if (adjustedLocation != null) {
final locationMarkerPosition = LocationMarkerPosition(
latitude: adjustedLocation.latitude,
longitude: adjustedLocation.longitude,
accuracy: position.accuracy,
);
locationMarkerPositionStreamController.add(locationMarkerPosition);
}
}
}
// コントローラーのクローズ時に呼び出されるライフサイクルメソッドです。
// positionStreamをキャンセルします。
//
@override
void onClose() {
// Cancel the position stream subscription when the controller is closed
positionStream?.cancel();
super.onClose();
}
// シミュレーションモードのフラグ
RxBool isSimulationMode = RxBool(false);
// シミュレーションモードを切り替えるための関数
void setSimulationMode(bool value) {
isSimulationMode.value = value;
}
// GPS信号強度をシミュレートするための変数
final Rx<String> _simulatedSignalStrength = Rx<String>('high');
// GPS信号強度をシミュレートするための関数
void setSimulatedSignalStrength(String strength) {
_simulatedSignalStrength.value = strength;
}
// シミュレートされた信号強度を取得するための関数
String getSimulatedSignalStrength() {
return _simulatedSignalStrength.value;
}
}
import 'dart:io';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:rogapp/model/gps_data.dart';
import 'package:sqflite/sqflite.dart';
class GpsDatabaseHelper {
GpsDatabaseHelper._privateConstructor();
static final GpsDatabaseHelper instance =
GpsDatabaseHelper._privateConstructor();
static Database? _database;
Future<Database> get database async => _database ??= await _initDatabase();
Future<Database> _initDatabase() async {
Directory documentDirectory = await getApplicationDocumentsDirectory();
String path = join(documentDirectory.path, 'rog.db');
// return await openDatabase(
// path,
// version: 1,
// onCreate: _onCreate,
// );
return openDatabase(
join(
await getDatabasesPath(),
'gps.db',
),
version: 1,
onCreate: _onCreate);
}
Future _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE gps(
id INTEGER PRIMARY KEY AUTOINCREMENT,
team_name TEXT,
event_code TEXT,
lat REAL,
lon REAL,
is_checkin int,
created_at INTEGER,
is_synced INTEGER DEFAULT 0
)
''');
}
Future<int> insertGps(GpsData gps) async {
try {
//print("---- try insering ${gps.toMap()}");
Database db = await instance.database;
int? nextOrder =
Sqflite.firstIntValue(await db.rawQuery('SELECT MAX(id) FROM gps'));
nextOrder = nextOrder ?? 0;
nextOrder = nextOrder + 1;
gps.id = nextOrder;
//print("---- insering ${gps.toMap()}");
int res = await db.insert(
'gps',
gps.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
//print("------ database helper insert $res-----------::::::::");
return res;
} catch (err) {
print("------ error $err-----------::::::::");
return -1;
}
}
Future<List<GpsData>> getGPSData(String team_name, String event_code) async {
Database db = await instance.database;
var gpss = await db.query('gps',
where: "team_name = ? and event_code = ?",
whereArgs: [team_name, event_code],
orderBy: 'created_at');
List<GpsData> gpsDatas =
gpss.isNotEmpty ? gpss.map((e) => GpsData.fromMap(e)).toList() : [];
//print("--------- db list $gpsDatas");
return gpsDatas;
}
Future<List<GpsData>> getUnsyncedGPSData(
String team_name, String event_code) async {
Database db = await instance.database;
var gpss = await db.query('gps',
where: 'team_name = ? and event_code = ? and is_synced = 0',
whereArgs: [team_name, event_code],
orderBy: 'created_at');
return gpss.isNotEmpty ? gpss.map((e) => GpsData.fromMap(e)).toList() : [];
}
Future<void> setSyncData(List<GpsData> data) async {
Database db = await instance.database;
for (var record in data) {
await db.update(
'gps',
{'is_synced': 1},
where: 'id = ?',
whereArgs: [record.id],
);
}
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class UtilController extends GetxController{
void changeLanguage(var lang, var cnty){
var locale = Locale(lang, cnty);
Get.updateLocale(locale);
}
}
class ConstValues{
static const container_svr = "http://container.intranet.sumasen.net:8100";
static const server_uri = "https://rogaining.intranet.sumasen.net";
static const dev_server = "http://localhost:8100";
static const dev_ip_server = "http://192.168.8.100:8100";
static const dev_home_ip_server = "http://172.20.10.9:8100";
static const dev_home_ip_mserver = "http://192.168.1.10:8100";
static String currentServer(){
//return dev_ip_server;
return server_uri;
}
}
import 'package:get/get.dart';
import 'package:rogapp/spa/spa_controller.dart';
class SpaBinding extends Bindings {
@override
void dependencies() {
Get.put<SpaController>(SpaController());
}
}import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rogapp/spa/spa_controller.dart';
class SpaPage extends GetView<SpaController> {
const SpaPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
throw UnimplementedError();
}
}
import 'package:get/get.dart';
class SpaController extends GetxController {
}import 'dart:async';
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/utils/database_gps.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
//import 'package:vm_service/vm_service.dart';
//import 'package:dart_vm_info/dart_vm_info.dart';
import 'package:rogapp/pages/settings/settings_controller.dart';
import 'package:rogapp/pages/destination/destination_controller.dart';
import 'package:rogapp/pages/index/index_binding.dart';
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';
import 'package:rogapp/services/device_info_service.dart';
import 'package:rogapp/services/error_service.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
//import 'dart:async';
//import 'package:get/get.dart';
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 = {};
/*
void saveGameState() async {
DestinationController destinationController =
Get.find<DestinationController>();
SharedPreferences pref = await SharedPreferences.getInstance();
pref.setBool("is_in_rog", destinationController.isInRog.value);
pref.setBool(
"rogaining_counted", destinationController.rogainingCounted.value);
pref.setBool("ready_for_goal", DestinationController.ready_for_goal);
}
*/
// 現在のユーザーのIDも一緒に保存するようにします。
void saveGameState() async {
DestinationController destinationController =
Get.find<DestinationController>();
IndexController indexController = Get.find<IndexController>();
SharedPreferences pref = await SharedPreferences.getInstance();
debugPrint("indexController.currentUser = ${indexController.currentUser}");
if(indexController.currentUser.isNotEmpty) {
pref.setInt("user_id", indexController.currentUser[0]["user"]["id"]);
}else{
debugPrint("User is empty....");
}
pref.setBool("is_in_rog", destinationController.isInRog.value);
pref.setBool(
"rogaining_counted", destinationController.rogainingCounted.value);
pref.setBool("ready_for_goal", DestinationController.ready_for_goal);
}
/*
void restoreGame() async {
SharedPreferences pref = await SharedPreferences.getInstance();
DestinationController destinationController =
Get.find<DestinationController>();
destinationController.skipGps = false;
destinationController.isInRog.value = pref.getBool("is_in_rog") ?? false;
destinationController.rogainingCounted.value =
pref.getBool("rogaining_counted") ?? false;
DestinationController.ready_for_goal =
pref.getBool("ready_for_goal") ?? false;
//print(
// "--restored -- destinationController.isInRog.value ${pref.getBool("is_in_rog")} -- ${pref.getBool("rogaining_counted")}");
}
*/
void restoreGame() async {
SharedPreferences pref = await SharedPreferences.getInstance();
IndexController indexController = Get.find<IndexController>();
int? savedUserId = pref.getInt("user_id");
//int? currUserId = indexController.currentUser[0]['user']['id'];
//debugPrint("savedUserId=${savedUserId}, currentUser=${currUserId}");
if (indexController.currentUser.isNotEmpty &&
indexController.currentUser[0]["user"]["id"] == savedUserId) {
DestinationController destinationController =
Get.find<DestinationController>();
destinationController.skipGps = false;
destinationController.isInRog.value = pref.getBool("is_in_rog") ?? false;
destinationController.rogainingCounted.value =
pref.getBool("rogaining_counted") ?? false;
DestinationController.ready_for_goal =
pref.getBool("ready_for_goal") ?? false;
await Get.putAsync(() => ApiService().init());
}
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlutterMapTileCaching.initialise();
final StoreDirectory instanceA = FMTC.instance('OpenStreetMap (A)');
await instanceA.manage.createAsync();
await instanceA.metadata.addAsync(
key: 'sourceURL',
value: 'https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png',
);
await instanceA.metadata.addAsync(
key: 'validDuration',
value: '14',
);
await instanceA.metadata.addAsync(
key: 'behaviour',
value: 'cacheFirst',
);
deviceInfo = await DeviceInfoService.getDeviceInfo();
FlutterError.onError = (FlutterErrorDetails details) {
FlutterError.presentError(details);
Get.log('Flutter error: ${details.exception}');
Get.log('Stack trace: ${details.stack}');
ErrorService.reportError(details.exception, details.stack ?? StackTrace.current, deviceInfo, LogManager().operationLogs);
};
//Get.put(LocationController());
//await PermissionController.checkAndRequestPermissions();
//requestLocationPermission();
// startMemoryMonitoring(); // 2024-4-8 Akira: メモリ使用量のチェックを開始 See #2810
Get.put(SettingsController()); // これを追加
/*
runZonedGuarded(() {
runApp(const ProviderScope(child: MyApp()));
}, (error, stackTrace) {
ErrorService.reportError(error, stackTrace, deviceInfo);
});
*/
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();
if (status == PermissionStatus.granted) {
print('Location permission granted');
} else {
print('Location permission denied');
//await showLocationPermissionDeniedDialog(); // 追加
}
} catch (e) {
print('Error requesting location permission: $e');
}
}
// メモリ使用量の解説https://qiita.com/hukusuke1007/items/e4e987836412e9bc73b9
/*
// 2024-4-8 Akira: メモリ使用量のチェックのため追加 See #2810
// startMemoryMonitoring関数が5分ごとに呼び出され、メモリ使用量をチェックします。
// メモリ使用量が閾値ここでは500MBを超えた場合、エラーメッセージとスタックトレースをレポートします。
//
void startMemoryMonitoring() {
const threshold = 500 * 1024 * 1024; // 500MB
// メモリ使用量情報を取得
final memoryUsage = MemoryUsage.fromJson(DartVMInfo.getAllocationProfile());
if (memoryUsage.heapUsage > threshold) {
final now = DateTime.now().toIso8601String();
final message = 'High memory usage detected at $now: ${memoryUsage.heapUsage} bytes';
print(message);
reportError(message, StackTrace.current);
showMemoryWarningDialog();
}
Timer(const Duration(minutes: 5), startMemoryMonitoring);
}
class MemoryUsage {
final int heapUsage;
MemoryUsage({required this.heapUsage});
factory MemoryUsage.fromJson(Map<String, dynamic> json) {
return MemoryUsage(
heapUsage: json['heapUsage'] as int,
);
}
}
*/
// 2024-4-8 Akira: メモリ使用量のチェックのため追加 See #2810
// reportError関数でエラーレポートを送信します。具体的な実装は、利用するエラー報告サービスによって異なります。
void reportError(String message, StackTrace stackTrace) async {
// エラーレポートの送信処理を実装
// 例: SentryやFirebase Crashlyticsなどのエラー報告サービスを利用
print("ReportError : ${message} . stacktrace : ${stackTrace}");
}
// 2024-4-8 Akira: メモリ使用量のチェックのため追加 See #2810
// showMemoryWarningDialog関数で、メモリ使用量が高い場合にユーザーに警告ダイアログを表示します。
//
void showMemoryWarningDialog() {
if (Get.context != null) {
showDialog(
context: Get.context!,
builder: (context) => AlertDialog(
title: const Text('メモリ使用量の警告'),
content: const Text('アプリのメモリ使用量が高くなっています。アプリを再起動することをお勧めします。'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('OK'),
),
],
),
);
}
}
StreamSubscription<Position>? positionStream;
bool background=false;
DateTime lastGPSCollectedTime=DateTime.now();
String team_name="";
String event_code="";
Future<void> startBackgroundTracking() async {
if (Platform.isIOS && background==false) {
final IndexController indexController = Get.find<IndexController>();
if(indexController.currentUser.length>0) {
team_name = indexController.currentUser[0]["user"]['team_name'];
event_code = indexController.currentUser[0]["user"]["event_code"];
}
background = true;
debugPrint("バックグラウンド処理を開始しました。");
final LocationSettings locationSettings = LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 100,
);
try {
positionStream = Geolocator.getPositionStream(locationSettings: locationSettings)
.listen((Position? position) async {
if (position != null) {
final lat = position.latitude;
final lng = position.longitude;
//final timestamp = DateTime.now();
final accuracy = position.accuracy;
// GPS信号強度がlowの場合はスキップ
if (accuracy > 100) {
debugPrint("GPS signal strength is low. Skipping data saving.");
return;
}
Duration difference = lastGPSCollectedTime.difference(DateTime.now())
.abs();
// 最後にGPS信号を取得した時刻から10秒以上経過、かつ10m以上経過普通に歩くスピード
//debugPrint("時間差:${difference}");
if (difference.inSeconds >= 10 ) {
debugPrint("バックグラウンドでのGPS取得時の処理(10secおき) count=${difference.inSeconds}, time=${DateTime.now()}");
// DBにGPSデータを保存 pages/destination/destination_controller.dart
await addGPStoDB(lat, lng);
lastGPSCollectedTime = DateTime.now();
}
}
}, onError: (error) {
if (error is LocationServiceDisabledException) {
print('Location services are disabled');
} else if (error is PermissionDeniedException) {
print('Location permissions are denied');
} else {
print('Location Error: $error');
}
});
} catch (e) {
print('Error starting background tracking: $e');
}
}else if (Platform.isAndroid && background == false) {
background = true;
debugPrint("バックグラウンド処理を開始しました。");
try {
// 位置情報の権限が許可されているかを確認
await PermissionController.checkAndRequestPermissions();
}catch(e){
print('Error starting background tracking: $e');
}
}
}
Future<void> addGPStoDB(double la, double ln) async {
//debugPrint("in addGPStoDB ${indexController.currentUser}");
GpsDatabaseHelper db = GpsDatabaseHelper.instance;
try {
GpsData gps_data = GpsData(
id: 0,
team_name: team_name,
event_code: event_code,
lat: la,
lon: ln,
is_checkin: 0,
created_at: DateTime.now().millisecondsSinceEpoch);
var res = await db.insertGps(gps_data);
debugPrint("バックグラウンドでのGPS保存");
} catch (err) {
print("errr ready gps ${err}");
return;
}
}
Future<void> stopBackgroundTracking() async {
if (Platform.isIOS && background==true) {
background=false;
debugPrint("バックグラウンド処理:停止しました。");
await positionStream?.cancel();
positionStream = null;
}else if(Platform.isAndroid && background==true){
background=false;
debugPrint("バックグラウンド処理:停止しました。");
const platform = MethodChannel('location');
try {
await platform.invokeMethod('stopLocationService');
} on PlatformException catch (e) {
print("Failed to stop location service: '${e.message}'.");
}
}
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
// This widget is the root of your application.
@override
void initState() {
super.initState();
if (context.mounted) {
restoreGame();
}
WidgetsBinding.instance.addObserver(this);
// ウィジェットが構築された後に権限をチェック
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() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
// void saveGameState() async {
// DestinationController destinationController = Get.find<DestinationController>();
// SharedPreferences pref = await SharedPreferences.getInstance();
// pref.setBool("is_in_rog", destinationController.is_in_rog.value);
// pref.setBool("rogaining_counted", destinationController.rogaining_counted.value);
// }
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
LocationController locationController = Get.find<LocationController>();
DestinationController destinationController = Get.find<DestinationController>();
//DestinationController destinationController =
// Get.find<DestinationController>();
switch (state) {
case AppLifecycleState.resumed:
// バックグラウンド処理を停止
if (Platform.isIOS && destinationController.isRunningBackgroundGPS) {
// Foreground に戻った時の処理
debugPrint(" ==(Status Changed)==> RESUMED. フォアグラウンドに戻りました");
locationController.resumePositionStream();
//print("RESUMED");
restoreGame();
stopBackgroundTracking();
destinationController.isRunningBackgroundGPS=false;
destinationController.restartGPS();
} else if (Platform.isAndroid ) {
if( destinationController.isRunningBackgroundGPS ){
const platform = MethodChannel('location');
platform.invokeMethod('stopLocationService');
destinationController.isRunningBackgroundGPS=false;
destinationController.restartGPS();
debugPrint("stopped android location service..");
}
debugPrint("==(Status Changed)==> RESUMED. android フォアグラウンドに戻りました");
locationController.resumePositionStream();
//print("RESUMED");
restoreGame();
}else{
debugPrint("==(Status Changed)==> RESUMED 不明状態");
}
break;
case AppLifecycleState.inactive:
// アプリが非アクティブになったときに発生します。
if (Platform.isIOS && !destinationController.isRunningBackgroundGPS) { // iOSはバックグラウンドでもフロントの処理が生きている。
// これは、別のアプリやシステムのオーバーレイ(着信通話やアラームなど)によって一時的に中断された状態です。
debugPrint(" ==(Status Changed)==> INACTIVE. 非アクティブ処理。");
//locationController.resumePositionStream();
// 追加: フロントエンドのGPS信号のlistenを停止
locationController.stopPositionStream();
destinationController.isRunningBackgroundGPS=true;
startBackgroundTracking();
}else if(Platform.isAndroid && !destinationController.isRunningBackgroundGPS){
debugPrint(" ==(Status Changed)==> INACTIVE. 非アクティブ処理。");
}else{
debugPrint("==(Status Changed)==> INACTIVE 不明状態");
}
saveGameState();
break;
case AppLifecycleState.paused:
// バックグラウンドに移行したときの処理
//locationController.resumePositionStream();
debugPrint(" ==(Status Changed)==> PAUSED. バックグラウンド処理。");
if (Platform.isIOS && !destinationController.isRunningBackgroundGPS) {
debugPrint("iOS already running background GPS processing when it's inactive");
} else if(Platform.isAndroid && !destinationController.isRunningBackgroundGPS) {
debugPrint(
" ==(Status Changed)==> PAUSED. Android バックグラウンド処理。");
locationController.stopPositionStream();
const platform = MethodChannel('location');
platform.invokeMethod('startLocationService');
//platform.invokeMethod('stopLocationService');
destinationController.isRunningBackgroundGPS = true;
//startBackgroundTracking();
}
saveGameState();
break;
case AppLifecycleState.detached:
// アプリが終了する直前に発生します。この状態では、アプリはメモリから解放される予定です。
//locationController.resumePositionStream();
debugPrint(" ==(Status Changed)==> DETACHED アプリは終了します。");
saveGameState();
break;
case AppLifecycleState.hidden:
// Web用の特殊な状態で、モバイルアプリでは発生しません。
//locationController.resumePositionStream();
debugPrint(" ==(Status Changed)==> Hidden アプリが隠れた");
saveGameState();
break;
}
}
@override
Widget build(BuildContext context) {
return GetMaterialApp(
translations: StringValues(),
locale: const Locale('ja', 'JP'),
//locale: const Locale('en', 'US'),
fallbackLocale: const Locale('en', 'US'),
title: 'ROGAINING',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color.fromARGB(255, 36, 135, 221)),
useMaterial3: true,
),
debugShowCheckedModeBanner: false,
defaultTransition: Transition.cupertino,
opaqueRoute: Get.isOpaqueRouteDefault,
popGesture: Get.isPopGestureEnable,
transitionDuration: const Duration(milliseconds: 230),
initialBinding: IndexBinding(), //HomeBinding(),
initialRoute: AppPages.PERMISSION,
getPages: AppPages.routes,
enableLog: true,
);
}
}
class GpsData {
int id;
String team_name;
String event_code;
double lat;
double lon;
int is_checkin;
int created_at;
int is_synced;
GpsData({
required this.id,
required this.team_name,
required this.event_code,
required this.lat,
required this.lon,
required this.created_at,
this.is_checkin = 0,
this.is_synced = 0,
});
factory GpsData.fromMap(Map<String, dynamic> json) {
return GpsData(
id: json["id"],
team_name: json["team_name"],
event_code: json["event_code"],
lat: json["lat"],
lon: json["lon"],
is_checkin: json["is_checkin"],
created_at: json["created_at"],
is_synced: json["is_synced"] ?? 0,
);
}
Map<String, dynamic> toMap() {
return {
'id': id,
'team_name': team_name,
'event_code': event_code,
'lat': lat,
'lon': lon,
'is_checkin': is_checkin,
'created_at': created_at,
'is_synced': is_synced,
};
}
}
// lib/models/team.dart
import 'category.dart';
import 'user.dart';
class Team {
final int id;
final String zekkenNumber;
final String teamName;
final Category 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'],
zekkenNumber: json['zekken_number'],
teamName: json['team_name'],
category: Category.fromJson(json['category']),
owner: User.fromJson(json['owner']),
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'zekken_number': zekkenNumber,
'team_name': teamName,
'category': category.toJson(),
'owner': owner.toJson(),
};
}
}// プロパティの型がString?やint?などのオプショナル型になっています。
// これらのプロパティが常に値を持つことが保証されている場合は、非オプショナル型を使用することで、不要なnullチェックを回避できます。
//
class AuthUser {
AuthUser();
//AuthUser.from({required this.id, required this.email, required this.is_rogaining, required this.group, required this.zekken_number, required this.event_code, required this.team_name});
AuthUser.fromMap(Map<String, dynamic> map)
: id = int.parse(map["id"].toString()),
email = map["email"].toString(),
is_rogaining = bool.parse(map["is_rogaining"].toString()),
group = map["group"].toString(),
zekken_number = map["zekken_number"].toString(),
event_code = map["event_code"].toString(),
team_name = map["team_name"].toString(),
auth_token = map["token"];
int? id;
String? email;
bool? is_rogaining;
String? group;
String? zekken_number;
String? event_code;
String? team_name;
String? auth_token;
}
// 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'],
lastname: json['lastname'],
dateOfBirth: json['date_of_birth'] != null ? DateTime.parse(json['date_of_birth']) : null,
female: json['female'],
isActive: json['is_active'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'email': email,
'firstname': firstname,
'lastname': lastname,
'date_of_birth': dateOfBirth?.toIso8601String(),
'female': female,
'is_active': isActive,
};
}
}// プロパティの型がString?やint?などのオプショナル型になっています。
// これらのプロパティが常に値を持つことが保証されている場合は、非オプショナル型を使用することで、不要なnullチェックを回避できます。
//
class Destination {
String? name;
String? address;
String? phone;
String? email;
String? webcontents;
String? videos;
String? category;
int? series;
double? lat;
double? lon;
String? sub_loc_id;
int? location_id;
int? list_order;
String? photos;
double? checkin_radious;
int? auto_checkin;
bool? selected = false;
bool? checkedin = false;
double? cp;
double? checkin_point;
double? buy_point;
int? hidden_location;
String? checkin_image;
String? buypoint_image;
bool forced_checkin = false;
int recipt_times = 0;
String? tags;
bool use_qr_code = false; // QR code を使用するかどうか。default=false
Destination(
{this.name,
this.address,
this.phone,
this.email,
this.webcontents,
this.videos,
this.category,
this.series,
this.lat,
this.lon,
this.sub_loc_id,
this.location_id,
this.list_order,
this.photos,
this.checkin_radious,
this.auto_checkin,
this.selected,
this.checkedin,
this.cp,
this.checkin_point,
this.buy_point,
this.hidden_location,
this.checkin_image,
this.buypoint_image,
this.forced_checkin = false,
this.recipt_times = 0,
this.tags}); //, ... use_qr_code をDBに追加したらオープン
// this.use_qr_code = false});
factory Destination.fromMap(Map<String, dynamic> json) {
bool selec = json['selected'] == 0 ? false : true;
bool checkin = json['checkedin'] == 0 ? false : true;
bool forcedCheckin = json['forced_checkin'] == 0 ? false : true;
bool useQrCode = json['use_qr_code'] == 1 ? true : false;
//print("-----tags model----- ${json}");
return Destination(
name: json['name'],
address: json['address'],
phone: json['phone'],
email: json['email'],
webcontents: json['webcontents'],
videos: json['videos'],
category: json['category'],
series: json['series'],
lat: json['lat'],
lon: json['lon'],
sub_loc_id: json['sub_loc_id'],
location_id: json['location_id'],
list_order: json['list_order'],
photos: json['photos'],
checkin_radious: json['checkin_radious'],
auto_checkin: json['auto_checkin'],
selected: selec,
checkedin: checkin,
cp: json['cp'],
checkin_point: json['checkin_point'],
buy_point: json['buy_point'],
hidden_location: json['hidden_location'],
checkin_image: json['checkin_image'],
buypoint_image: json["buypoint_image"],
forced_checkin: forcedCheckin,
recipt_times: json["recipt_times"],
tags: json["tags"] ); //,
// use_qr_code: useQrCode);
}
Map<String, dynamic> toMap() {
int sel = selected == false ? 0 : 1;
int check = checkedin == false ? 0 : 1;
int forcedCheckin = forced_checkin == false ? 0 : 1;
return {
'name': name,
'address': address,
'phone': phone,
'email': email,
'webcontents': webcontents,
'videos': videos,
'category': category,
'series': series,
'lat': lat,
'lon': lon,
'sub_loc_id': sub_loc_id,
'location_id': location_id,
'list_order': list_order,
'photos': photos,
'checkin_radious': checkin_radious,
'auto_checkin': auto_checkin,
'selected': sel,
'checkedin': check,
'cp': cp,
'checkin_point': checkin_point,
'buy_point': buy_point,
'hidden_location': hidden_location,
'checkin_image': checkin_image,
'buypoint_image': buypoint_image,
'forced_checkin': forcedCheckin,
'recipt_times': recipt_times,
'tags': tags //,
//'use_qr_code': use_qr_code
};
}
}
// lib/models/category.dart
class Category {
final int id;
final String categoryName;
final int categoryNumber;
final Duration duration;
final int numOfMember;
final bool family;
final bool female;
Category({
required this.id,
required this.categoryName,
required this.categoryNumber,
required this.duration,
required this.numOfMember,
required this.family,
required this.female,
});
factory Category.fromJson(Map<String, dynamic> json) {
return Category(
id: json['id'],
categoryName: json['category_name'],
categoryNumber: json['category_number'],
duration: Duration(seconds: json['duration']),
numOfMember: json['num_of_member'],
family: json['family'],
female: json['female'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'category_name': categoryName,
'category_number': categoryNumber,
'duration': duration.inSeconds,
'num_of_member': numOfMember,
'family': family,
'female': female,
};
}
}// 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(),
};
}
}
class Rogaining {
int? rog_id;
int? course_id;
int? user_id;
int? location_id;
double? lat;
double? lon;
int? time_stamp;
Rogaining({
this.rog_id,
this.course_id,
this.user_id,
this.location_id,
this.lat,
this.lon,
this.time_stamp
});
factory Rogaining.fromMap(Map<String, dynamic> json) {
return Rogaining(
rog_id: json['rog_id'],
course_id: json['course_id'],
user_id: json['user_id'],
location_id: json['location_id'],
lat: json['lat'],
lon: json['lon'],
time_stamp: json['time_stamp']
);
}
Map<String, dynamic> toMap(){
return {
'rog_id':rog_id,
'course_id': course_id,
'user_id': user_id,
'location_id': location_id,
'lat': lat,
'lon': lon,
'time_stamp': time_stamp
};
}
}
import 'package:flutter_map/flutter_map.dart';
class MapStateInstance {
MapController? mapController;
LatLngBounds? currentBounds;
}
enum LocationState { noGps, notInCheckin, withinCheckin }
enum GameState { notStarted, startedNotCounted, startedCounted, nodeGoal }
class GameInsStatetance {
LocationState locationState = LocationState.noGps;
GameState gameState = GameState.notStarted;
}
class Rog {
int? id;
String? team_name;
String? event_code;
int? user_id;
int? cp_number;
int? checkintime;
String? image;
int? rog_action_type;
Rog({
this.id,
this.team_name,
this.event_code,
this.user_id,
this.cp_number,
this.checkintime,
this.image,
this.rog_action_type
});
factory Rog.fromMap(Map<String, dynamic> json){
return Rog(
id: json['id'],
team_name: json['team_name'],
event_code: json['event_code'],
user_id: json['user_id'],
cp_number: json['cp_number'],
checkintime: json['checkintime'],
image: json['image'],
rog_action_type: json['rog_action_type']
);
}
Map<String, dynamic> toMap(){
return {
'id': id,
'team_name' : team_name,
'event_code': event_code,
'user_id': user_id,
'cp_number': cp_number,
'checkintime': checkintime,
'image': image,
'rog_action_type': rog_action_type
};
}
}// 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 Category category;
final DateTime date;
final String owner;
Entry({
required this.id,
required this.team,
required this.event,
required this.category,
required this.date,
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: Category.fromJson(json['category']),
date: DateTime.parse(json['date']),
owner: json['owner'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'team': team.toJson(),
'event': event.toJson(),
'category': category.toJson(),
'date': date.toIso8601String(),
'owner': owner,
};
}
}import 'package:flutter/material.dart';
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:rogapp/nrog/pages/permission_page.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlutterMapTileCaching.initialise();
final StoreDirectory instanceA = FMTC.instance('OpenStreetMap (A)');
await instanceA.manage.createAsync();
await instanceA.metadata.addAsync(
key: 'sourceURL',
value: 'https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png',
);
await instanceA.metadata.addAsync(
key: 'validDuration',
value: '14',
);
await instanceA.metadata.addAsync(
key: 'behaviour',
value: 'cacheFirst',
);
runApp(
const ProviderScope(child: MyApp()),
);
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color.fromARGB(255, 124, 156, 9)),
useMaterial3: true,
),
home: const PermissionPage());
}
}
// ignore: non_constant_identifier_names
// 不要
String location_line_date = """
{
"type": "FeatureCollection",
"features": [
{
"id": 1,
"type": "Feature",
"geometry": {
"type": "MultiLineString",
"coordinates": [
[
[
135.495586,
34.701824
],
[
136.491222,
35.447526
],
[
137.184305,
35.265174
],
[
137.50248,
35.859283
]
]
]
},
"properties": {
"location_id": -1,
"location_name": "fdsfsd",
"category": "dsfds",
"zip": "74120",
"address": "567 Bridge Street Tulsa Oklahoma 74120",
"prefecture": "fdfs",
"area": "fsdfs",
"city": "Ho",
"photos": "dfs",
"videos": "fsdfsd",
"webcontents": "fsdfsd",
"status": "sdfs",
"portal": "fdsfsd",
"group": "fdsfsd",
"phone": "+94773051841",
"fax": "fdsfds",
"email": "info@initappz.com",
"facility": "fdsfds",
"remark": "fdfs",
"tags": null,
"parammeters": "fsdfsd",
"created_at": "2022-03-10T01:23:44.717905+09:00",
"last_updated_at": "2022-03-10T01:23:44.717922+09:00",
"last_updated_user": 1
}
},
{
"id": 2,
"type": "Feature",
"geometry": {
"type": "MultiLineString",
"coordinates": [
[
[
135.928001,
34.233306
],
[
135.998211,
34.649591
],
[
136.167984,
35.085641
]
]
]
},
"properties": {
"location_id": 2,
"location_name": "fdsfsd",
"category": "dsfds",
"zip": "32400",
"address": "Akkaraipattu 02",
"prefecture": "fdfs",
"area": "fsdfs",
"city": "Akkaraipattu",
"photos": "dfs",
"videos": "fsdfsd",
"webcontents": "fsdfsd",
"status": "sdfs",
"portal": "fdsfsd",
"group": "fdsfsd",
"phone": "773051841",
"fax": "fdsfds",
"email": "admin@front.lk",
"facility": "fdsfds",
"remark": "fdfs",
"tags": "fdsfsd",
"parammeters": "fsdfsd",
"created_at": "2022-03-10T01:46:58.157320+09:00",
"last_updated_at": "2022-03-10T01:46:58.157336+09:00",
"last_updated_user": 1
}
}
]
}""";// lib/pages/settings/settings_controller.dart
import 'package:get/get.dart';
import 'package:rogapp/widgets/map_widget.dart';
class SettingsController extends GetxController {
var timerDuration = Duration(seconds: 10).obs;
var autoReturnDisabled = false.obs;
final MapResetController mapResetController = Get.put(MapResetController());
void updateTimerDuration(int seconds) {
timerDuration.value = Duration(seconds: seconds);
}
void setAutoReturnDisabled(bool value) {
autoReturnDisabled.value = value;
if (!value) {
resetIdleTimer();
}
}
void resetIdleTimer() {
mapResetController.resetIdleTimer!();
}
}
// lib/pages/settings/settings_binding.dart
import 'package:get/get.dart';
import 'package:rogapp/pages/settings/settings_controller.dart';
class SettingsBinding extends Bindings {
@override
void dependencies() {
Get.put<SettingsController>(SettingsController()); // これを修正
//Get.lazyPut<SettingsController>(() => SettingsController());
}
}
// lib/pages/settings/settings_page.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rogapp/pages/settings/settings_controller.dart';
class SettingsPage extends GetView<SettingsController> {
const SettingsPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('設定'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'timer_duration'.tr,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Obx(
() => controller.autoReturnDisabled.value
? Container()
: Slider(
value: controller.timerDuration.value.inSeconds.toDouble(),
min: 5,
max: 30,
divisions: 5,
label: '${controller.timerDuration.value.inSeconds}秒',
onChanged: (value) {
controller.updateTimerDuration(value.toInt());
},
),
),
const SizedBox(height: 8),
const Text(
'マップ操作がなければ自動的に現在地に復帰します。そのタイマー秒数を入れて下さい。チェックボックスをチェックすると、自動復帰は行われなくなります。',
style: TextStyle(fontSize: 14),
),
const SizedBox(height: 16),
Obx(
() => CheckboxListTile(
title: const Text('自動復帰なし'),
value: controller.autoReturnDisabled.value,
onChanged: (value) {
controller.setAutoReturnDisabled(value!);
},
),
),
],
),
),
);
}
}
import 'package:get/get.dart';
import 'package:get/get_state_manager/get_state_manager.dart';
class HomeController extends GetxController{
}
import 'package:get/get.dart';
import 'package:rogapp/pages/home/home_controller.dart';
class HomeBinding extends Bindings{
@override
void dependencies() {
Get.put<HomeController>(HomeController());
}
}import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:rogapp/routes/app_pages.dart';
import 'package:rogapp/utils/string_values.dart';
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
bool _isLocationServiceEnabled = true;
@override
void initState() {
super.initState();
/*
WidgetsBinding.instance.addPostFrameCallback((_) {
_checkLocationService(); // 非同期的に呼び出す
});
*/
_checkLocationService();
}
Future<void> _checkLocationService() async {
final serviceEnabled = await Permission.location.serviceStatus.isEnabled;
setState(() {
_isLocationServiceEnabled = serviceEnabled;
});
}
void _showLocationDisabledDialog() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('location_disabled_title'.tr),
content: Text('location_disabled_message'.tr),
actions: [
TextButton(
child: Text('ok'.tr),
onPressed: () => Navigator.of(context).pop(),
),
TextButton(
child: Text('open_settings'.tr),
onPressed: () {
Navigator.of(context).pop();
openAppSettings();
},
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('home'.tr),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('welcome'.tr),
SizedBox(height: 20),
ElevatedButton(
onPressed: _isLocationServiceEnabled
? () => Get.offNamed(AppPages.INDEX)
: () => _showLocationDisabledDialog(),
child: Text('start_app'.tr),
),
],
),
),
);
}
}import 'package:get/get.dart';
class LayerDrawerController extends GetxController {
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rogapp/pages/destination/destination_controller.dart';
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';
// SafeAreaウィジェットを使用して、画面の安全領域内にメニューを表示しています。
// Columnウィジェットを使用して、メニューアイテムを縦に並べています。
//
class DrawerPage extends StatelessWidget {
DrawerPage({Key? key}) : super(key: key);
final IndexController indexController = Get.find<IndexController>();
LogManager logManager = LogManager();
// 要検討URLの起動に失敗した場合のエラーハンドリングが不十分です。適切なエラーメッセージを表示するなどの処理を追加してください。
//
/*
void _launchURL(url) async {
if (!await launchUrl(url)) throw 'Could not launch $url';
}
*/
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);
} else {
// URLを開けない場合のフォールバック動作
// 例えば、WebViewを使用してアプリ内でURLを開く
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => WebViewPage(url: urlString),
),
);
}
}catch(e){
// エラーメッセージを表示する
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('URLを開けませんでした: $e')),
);
}
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: Drawer(
child: Column(
children: [
Container(
height: 100,
color: Colors.amber,
child: Obx(() => Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: indexController.currentUser.isEmpty
? Flexible(
child: Text(
"drawer_title".tr,
style: const TextStyle(
color: Colors.black, fontSize: 20),
))
: Text(
indexController.currentUser[0]['user']['email'],
style: const TextStyle(
color: Colors.black, fontSize: 20),
),
),
)),
),
Obx(() => indexController.currentUser.isEmpty
? ListTile(
leading: const Icon(Icons.login),
title: Text("login".tr),
onTap: () {
Get.toNamed(AppPages.LOGIN);
},
)
: ListTile(
leading: const Icon(Icons.login),
title: Text("logout".tr),
onTap: () {
indexController.logout();
Get.toNamed(AppPages.LOGIN);
},
)),
indexController.currentUser.isNotEmpty
? ListTile(
leading: const Icon(Icons.password),
title: Text("change_password".tr),
onTap: () {
Get.toNamed(AppPages.CHANGE_PASSWORD);
},
)
: const SizedBox(
width: 0,
height: 0,
),
indexController.currentUser.isEmpty
? ListTile(
leading: const Icon(Icons.person),
title: Text("sign_up".tr),
onTap: () {
Get.toNamed(AppPages.REGISTER);
},
)
: const SizedBox(
width: 0,
height: 0,
),
indexController.currentUser.isNotEmpty
? ListTile(
leading: const Icon(Icons.password),
title: Text('reset_button'.tr),
onTap: () {
logManager.addOperationLog('User clicked RESET button on the drawer');
// 要検討:リセット操作の確認メッセージをローカライズすることを検討してください。
//
Get.defaultDialog(
title: "reset_title".tr,
middleText: "reset_message".tr,
textConfirm: "confirm".tr,
textCancel: "cancel".tr,
onCancel: () => Get.back(),
onConfirm: () async {
DestinationController destinationController =
Get.find<DestinationController>();
DatabaseHelper databaseHelper = DatabaseHelper.instance;
// ゲーム中のデータを削除
await databaseHelper.deleteAllRogaining();
await databaseHelper.deleteAllDestinations();
destinationController.resetRogaining();
//destinationController.resetRogaining();
//destinationController.deleteDBDestinations();
Get.back();
Get.snackbar(
"reset_done".tr,
"reset_explain".tr,
backgroundColor: Colors.green,
colorText: Colors.white,
duration: const Duration(seconds: 3),
);
},
);
},
)
: const SizedBox(
width: 0,
height: 0,
),
indexController.currentUser.isNotEmpty
? ListTile(
leading: const Icon(Icons.delete_forever),
title: Text("delete_account".tr),
onTap: () {
Get.defaultDialog(
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) {
indexController.logout();
Get.toNamed(AppPages.TRAVEL);
Get.snackbar("accounted_deleted".tr,
"account_deleted_message".tr,
backgroundColor: Colors.green,
colorText: Colors.white
);
}
});
},
);
},
)
: const SizedBox(
width: 0,
height: 0,
),
indexController.currentUser.isNotEmpty
? ListTile(
leading: const Icon(Icons.featured_video),
title: Text("rog_web".tr),
onTap: () {
_launchURL(context, "https://www.gifuai.net/?page_id=60043");
},
)
: const SizedBox(
width: 0,
height: 0,
),
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: const Icon(Icons.privacy_tip),
title: Text("privacy".tr),
onTap: () {
_launchURL(context, "https://rogaining.sumasen.net/api/privacy/");
},
),
ListTile(
leading: const Icon(Icons.settings),
title: Text('open_settings'.tr),
onTap: () {
Get.back(); // ドロワーを閉じる
Get.toNamed(Routes.SETTINGS);
},
),
ListTile(
leading: const Icon(Icons.developer_mode),
title: const Text('open_settings'),
onTap: () {
Get.back(); // ドロワーを閉じる
Get.toNamed('/debug'); // デバッグ画面に遷移
},
),
// ListTile(
// leading: const Icon(Icons.router),
// title: Text("my_route".tr),
// onTap: (){},
// ),
// ListTile(
// leading: const Icon(Icons.history_sharp),
// title: Text("visit_history".tr),
// onTap: (){},
// ),
],
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rogapp/routes/app_pages.dart';
// 要検討:ログインボタンとサインアップボタンの配色を見直すことを検討してください。現在の配色では、ボタンの役割がわかりにくい可能性があります。
// ボタンのテキストをローカライズすることを検討してください。
//
class LandingPage extends StatefulWidget {
const LandingPage({ Key? key }) : super(key: key);
@override
State<LandingPage> createState() => _LandingPageState();
}
class _LandingPageState extends State<LandingPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
width: double.infinity,
height: MediaQuery.of(context).size.height,
padding: const EdgeInsets.symmetric(horizontal: 30,vertical: 30),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Text(
"こんにちは!",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 40),
),
const SizedBox(height: 30,),
Text("ログインを有効にして本人確認を行うと、サーバーが改善されます",
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.grey[700],
fontSize: 15
),
),
Container(
height: MediaQuery.of(context).size.height/3,
decoration: const BoxDecoration(
image:DecorationImage(image: AssetImage('assets/images/gradient_japanese_temple.jpg'))
),
),
const SizedBox(height: 20.0,),
MaterialButton(
minWidth: double.infinity,
height:60,
onPressed: (){
Get.toNamed(AppPages.LOGIN);
},
color: Colors.indigoAccent[400],
shape: RoundedRectangleBorder(
side: const BorderSide(
color: Colors.black,
),
borderRadius: BorderRadius.circular(40)
),
child: const Text("ログイン",style: TextStyle(
fontWeight: FontWeight.w600,fontSize: 16,color: Colors.white70
),
),
),
const SizedBox(height: 15.0,),
MaterialButton(
minWidth: double.infinity,
height:60,
onPressed: (){
Get.toNamed(AppPages.REGISTER);
},
color: Colors.redAccent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(40)
),
child: const Text("サインアップ",style: TextStyle(
fontWeight: FontWeight.w600,fontSize: 16,
),),
),
],
)
],
),
),
),
);
}
}import 'package:flutter/material.dart';
class CityPage extends StatelessWidget {
const CityPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container();
}
}import 'package:flutter/material.dart';
class LoadingPage extends StatelessWidget {
const LoadingPage({Key? key}) : super(key: key);
// 要検討ローディングインジケーターの値を固定値0.8)にしていますが、実際のローディング進捗に合わせて動的に変更することを検討してください。
//
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
margin: const EdgeInsets.only(top: 20),
child: const CircularProgressIndicator(
value: 0.8,
));
}
}
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';
// 要検討:ログインボタンとサインアップボタンの配色を見直すことを検討してください。現在の配色では、ボタンの役割がわかりにくい可能性があります。
// エラーメッセージをローカライズすることを検討してください。
// ポップアップを閉じるボタンを追加することを検討してください。
//
class LoginPopupPage extends StatelessWidget {
LoginPopupPage({Key? key}) : super(key: key);
final IndexController indexController = Get.find<IndexController>();
TextEditingController emailController = TextEditingController();
TextEditingController passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
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,
)),
),
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 / 5,
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),
makeInput(
label: "password".tr,
controller: passwordController,
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),
),
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: 60,
onPressed: () {
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.login(
emailController.text,
passwordController.text,
context);
},
color: Colors.indigoAccent[400],
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(40)),
child: const Text(
"ログイン",
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
color: Colors.white70),
),
),
const SizedBox(
height: 19.0,
),
MaterialButton(
minWidth: double.infinity,
height: 50,
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: 19.0,
),
MaterialButton(
minWidth: double.infinity,
height: 50,
onPressed: () {
Get.toNamed(AppPages.TRAVEL);
},
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: 20,
),
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,
),
),
),
),
],
)
],
),
],
),
)
: TextButton(
onPressed: () {
indexController.currentUser.clear();
},
child: const Text("Already Logged in, Click to logout"),
),
);
}
}
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,
)
],
);
}
import 'package:flutter/material.dart';
class ProgressPage extends StatelessWidget {
const ProgressPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
color: Colors.transparent,
child: const Center(
child: CircularProgressIndicator(),
),
);
}
}import 'package:flutter/material.dart';
class CategoryPage extends StatelessWidget {
const CategoryPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container();
}
}import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:get/get.dart';
import 'package:latlong2/latlong.dart';
import 'package:rogapp/model/gps_data.dart';
import 'package:rogapp/pages/destination/destination_controller.dart';
import 'package:rogapp/pages/index/index_controller.dart';
import 'package:rogapp/utils/database_gps.dart';
import 'package:rogapp/widgets/base_layer_widget.dart';
class GpsPage extends StatefulWidget {
const GpsPage({super.key});
@override
State<GpsPage> createState() => _GpsPageState();
}
class _GpsPageState extends State<GpsPage> {
var gpsData = [].obs;
MapController? mapController;
StreamSubscription? subscription;
final IndexController indexController = Get.find<IndexController>();
final DestinationController destinationController =
Get.find<DestinationController>();
@override
void initState() {
super.initState();
loadGpsData();
}
// 要検討GPSデータの読み込みに失敗した場合のエラーハンドリングが不十分です。適切なエラーメッセージを表示するなどの処理を追加してください。
//
void loadGpsData() async {
final team_name = indexController.currentUser[0]["user"]['team_name'];
final event_code = indexController.currentUser[0]["user"]["event_code"];
GpsDatabaseHelper db = GpsDatabaseHelper.instance;
var data = await db.getGPSData(team_name, event_code);
gpsData.value = data;
//print("--- gps data ${data} ----");
}
// 要検討:マーカーの形状を決定する際に、マジックナンバーが使用されています。定数を使用するなどして、コードの可読性を向上させることを検討してください。
//
Widget getMarkerShape(GpsData i) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
InkWell(
onTap: () {},
child: Container(
height: 22,
width: 22,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.transparent,
border: Border.all(
color:
i.is_checkin == 0 ? Colors.blueAccent : Colors.green,
width: i.is_checkin == 0 ? 0.4 : 2,
style: BorderStyle.solid)),
child: const Stack(
alignment: Alignment.center,
children: [
Icon(
Icons.circle,
size: 6.0,
),
],
)),
),
/*
Container(
color: Colors.transparent,
child: i.is_checkin == 1
? Text(
DateTime.fromMicrosecondsSinceEpoch(i.created_at)
.hour
.toString() +
":" +
DateTime.fromMicrosecondsSinceEpoch(i.created_at)
.minute
.toString(),
// ":" +
// DateTime.fromMicrosecondsSinceEpoch(i.created_at)
// .second
// .toString(),
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black,
))
: Container()),
*/
],
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("movement_history".tr),
),
body: Container(
child: Obx(
() => FlutterMap(
mapController: mapController,
options: MapOptions(
maxZoom: 18.4,
onMapReady: () {},
//center: LatLng(37.15319600454702, 139.58765950528198),
bounds: indexController.currentBound.isNotEmpty
? indexController.currentBound[0]
: LatLngBounds.fromPoints([
LatLng(35.03999881162295, 136.40587119778962),
LatLng(36.642756778706904, 137.95226720406063)
]),
zoom: 1,
interactiveFlags: InteractiveFlag.pinchZoom | InteractiveFlag.drag,
onPositionChanged: (MapPosition pos, bool hasGesture) {
if (hasGesture) {
indexController.currentBound = [pos.bounds!];
}
},
onTap: (tapPos, cord) {}, // Hide popup when the map is tapped.
),
children: [
const BaseLayer(),
MarkerLayer(
markers: gpsData.map((i) {
return Marker(
width: 30.0, // Fixed width
height: 30.0, // Fixed height
point: LatLng(i.lat, i.lon),
child: getMarkerShape(i),
alignment: Alignment.center);
}).toList(),
),
// MarkerLayer(
// markers: gpsData.map((i) {
// return Marker(
// alignment: Alignment.center,
// height: 32.0,
// width: 120.0,
// point: LatLng(i.lat, i.lon),
// child: getMarkerShape(i));
// }).toList(),
// )
],
),
)),
);
}
}
// 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';
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'];
return Scaffold(
appBar: AppBar(
title: Text(mode == 'new' ? 'エントリー登録' : 'エントリー詳細'),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Obx(() => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DropdownButtonFormField(
decoration: InputDecoration(labelText: 'イベント'),
value: controller.selectedEvent.value,
items: controller.events.map((event) => DropdownMenuItem(
value: event,
child: Text(event.eventName),
)).toList(),
onChanged: (value) => controller.updateEvent(value),
),
DropdownButtonFormField(
decoration: InputDecoration(labelText: 'チーム'),
value: controller.selectedTeam.value,
items: controller.teams.map((team) => DropdownMenuItem(
value: team,
child: Text(team.teamName),
)).toList(),
onChanged: (value) => controller.updateTeam(value),
),
DropdownButtonFormField(
decoration: InputDecoration(labelText: 'カテゴリ'),
value: controller.selectedCategory.value,
items: controller.categories.map((category) => DropdownMenuItem(
value: category,
child: Text(category.categoryName),
)).toList(),
onChanged: (value) => controller.updateCategory(value),
),
// 日付選択ウィジェットを追加
if (mode == 'edit' && controller.isOwner())
ElevatedButton(
child: Text('エントリーを削除'),
onPressed: () => controller.deleteEntry(),
),
],
),
),
)
);
}
}// lib/pages/entry/entry_list_page.dart
import 'package:flutter/material.dart';
import 'package:get/get.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('エントリー管理'),
actions: [
IconButton(
icon: Icon(Icons.add),
onPressed: () => Get.toNamed(AppPages.ENTRY_DETAIL, arguments: {'mode': 'new'}),
),
],
),
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.team?.teamName ?? 'チーム未設定'} - ${entry.category?.categoryName ?? 'カテゴリ未設定'}'),
trailing: Text(entry.date?.toString().substring(0, 10) ?? '日付未設定'),
onTap: () => Get.toNamed(AppPages.ENTRY_DETAIL, arguments: {'mode': 'edit', 'entry': entry}),
);
},
)),
);
}
}// lib/entry/entry_controller.dart
import 'package:get/get.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';
class EntryController extends GetxController {
late final ApiService _apiService;
final entries = <Entry>[].obs;
final events = <Event>[].obs;
final teams = <Team>[].obs;
final categories = <Category>[].obs;
final selectedEvent = Rx<Event?>(null);
final selectedTeam = Rx<Team?>(null);
final selectedCategory = Rx<Category?>(null);
final selectedDate = Rx<DateTime?>(null);
final currentEntry = Rx<Entry?>(null);
@override
void onInit() async{
super.onInit();
await Get.putAsync(() => ApiService().init());
_apiService = Get.find<ApiService>();
try {
await Future.wait([
fetchEntries(),
fetchEvents(),
fetchTeams(),
fetchCategories(),
]);
if (Get.arguments != null && Get.arguments['entry'] != null) {
currentEntry.value = Get.arguments['entry'];
_initializeEntryData();
}
Get.putAsync(() => ApiService().init());
}catch(e){
print('Error initializing data: $e');
}
}
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');
}
}
Future<void> fetchEvents() async {
try {
final fetchedEvents = await _apiService.getEvents();
events.assignAll(fetchedEvents);
} catch (e) {
print('Error fetching events: $e');
}
}
Future<void> fetchTeams() async {
try {
final fetchedTeams = await _apiService.getTeams();
teams.assignAll(fetchedTeams);
} catch (e) {
print('Error fetching teams: $e');
}
}
Future<void> fetchCategories() async {
try {
final fetchedCategories = await _apiService.getCategories();
categories.assignAll(fetchedCategories);
} catch (e) {
print('Error fetching categories: $e');
}
}
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 {
final newEntry = await _apiService.createEntry(
selectedTeam.value!.id,
selectedEvent.value!.id,
selectedCategory.value!.id,
selectedDate.value!,
);
entries.add(newEntry);
Get.back();
} catch (e) {
print('Error creating entry: $e');
Get.snackbar('Error', 'Failed to create entry');
}
}
Future<void> updateEntry() async {
if (currentEntry.value == null) return;
try {
final updatedEntry = await _apiService.updateEntry(
currentEntry.value!.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');
}
}
Future<void> deleteEntry() async {
if (currentEntry.value == null) return;
try {
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');
}
}
void updateEvent(Event? value) => selectedEvent.value = value;
void updateTeam(Team? value) => selectedTeam.value = value;
void updateCategory(Category? value) => selectedCategory.value = value;
void updateDate(DateTime value) => selectedDate.value = value;
bool isOwner() {
// Implement logic to check if the current user is the owner of the entry
return true; // Placeholder
}
}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());
}
}import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:rogapp/model/destination.dart';
import 'package:rogapp/pages/destination/destination_controller.dart';
import 'package:rogapp/pages/index/index_controller.dart';
import 'package:rogapp/services/external_service.dart';
import 'package:rogapp/utils/const.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';
// 関数 getTagText は、特定の条件に基づいて文字列から特定の部分を抽出し、返却するためのものです。
// 関数は2つのパラメータを受け取り、条件分岐を通じて結果を返します。
//
// この関数は、タグのリスト(空白を含む文字列)を適切に解析し、条件に応じて特定のタグを抽出するために設計されています。
// 異なる種類の空白文字半角、全角で異なる分割を行い、与えられた条件isReceptに応じて適切なタグを選択して返却します。
//
String getTagText(bool isRecept, String? tags) {
// bool isRecept: 真偽値を受け取り、この値によって処理の分岐が行われます。
// String? tags: オプショナルな文字列null が許容される)。空白文字を含む可能性のあるタグのリストを表します。
// 空のチェック:
// tags が null または空文字列 ("") の場合、何も含まれていないことを意味し、関数はただちに空文字列を返します。
//
if (tags == null || tags.isEmpty) {
return "";
}
// タグの分割:
// tags が空ではない場合、文字列を空白文字で分割します。
// ここで2種類の空白文字半角 " " と全角 " "に対応するため、2回分割を行っています。
// tts: 半角スペース " " で分割した結果のリスト。
// ttt: 全角スペース " " で分割した結果のリスト。
//
List<String> tts = tags.split(" ");
List<String> ttt = tags.split(" ");
// 条件分岐:
// isRecept の値によって、処理が分岐します。
//
if (isRecept) {
// isRecept が true の場合:
// 全角スペースで分割した結果 (ttt) の長さが半角スペースで分割した結果 (tts) の長さより大きく、
// かつ ttt が1つ以上の要素を持つ場合、ttt[1] 全角スペースで分割後の2番目の要素を返します。
if (ttt.length > tts.length && ttt.length > 1) {
return ttt[1];
}
}
if (!isRecept) {
// isRecept が false の場合:
// 全角スペースで分割したリストが半角スペースで分割したリストよりも長い場合、ttt[0] (全角スペースで分割後の最初の要素)を返します。
// 上記の条件に当てはまらない場合、半角スペースで分割したリストの最初の要素 tts[0] を返します。
//
if (ttt.length > tts.length && ttt.length > 1) {
return ttt[0];
}
}
if (!isRecept) {
// 最終的な返却:
// 上記の条件に何も該当しない場合(主に isRecept が true であり、全角スペースの条件に該当しない場合)、空文字列 "" を返します。
return tts[0];
}
return "";
}
// 要修正:画像の読み込みエラーが発生した場合のエラーハンドリングが不十分です。エラーメッセージを表示するなどの処理を追加してください。
// getDisplayImage は、Destination オブジェクトを受け取り、特定の条件に基づいて表示する画像を返す機能を持っています。
// Flutterの Image ウィジェットを使用して、適切な画像を表示します。
//
// この関数は、提供された Destination オブジェクトに基づいて適切な画像を動的に選択し、
// その画像を表示するための Image ウィジェットを生成します。
// デフォルトの画像、完全なURL、またはサーバーURLと組み合わされた画像パスを使用して、条件に応じた画像の取得を試みます。
// また、エラー発生時にはデフォルト画像にフォールバックすることでユーザー体験を向上させます。
//
Image getDisplayImage(Destination destination) {
// Destination destination: これは Destination クラスのインスタンスで、
// CheckPointのデータを持っているオブジェクトです。
// このクラスには少なくとも phone と photos というプロパティが含まれている
//
// サーバーURLの取得:
// serverUrl 変数には ConstValues.currentServer() メソッドから現在のサーバーのURLが取得されます。
// これは画像を取得する際の基本URLとして使用される可能性があります。
//
String serverUrl = ConstValues.currentServer();
// デフォルト画像の設定:
// img 変数にはデフォルトの画像が設定されます。
// これは、アセットから "assets/images/empty_image.png" をロードするための Image.asset コンストラクタを使用しています。
//
Image img = Image.asset("assets/images/empty_image.png");
// 電話番号のチェック:
// destination.phone が null の場合、関数は imgデフォルト画像を返します。
// これは、phone プロパティが画像URLの代用として何らかの形で使用されることを示唆していますが、
// それが null であればデフォルト画像を使用するという意味です。
//
if (destination.phone == null) {
return img;
}
// 画像URLの構築と画像の返却:
// destination.photos が http を含む場合、これはすでに完全なURLとして提供されていることを意味します。
// このURLを NetworkImage コンストラクタに渡し、Image ウィジェットを生成して返します。
// そうでない場合は、serverUrl と destination.photos を組み合わせたURLを生成して NetworkImage に渡し、画像を取得します。
//
if (destination.photos!.contains('http')) {
return Image(
image: NetworkImage(
destination.phone!,
),
errorBuilder:
(BuildContext context, Object exception, StackTrace? stackTrace) {
return Image.asset("assets/images/empty_image.png");
},
);
} else {
return Image(
image: NetworkImage(
'$serverUrl/media/compressed/${destination.photos}',
),
errorBuilder:
(BuildContext context, Object exception, StackTrace? stackTrace) {
return Image.asset("assets/images/empty_image.png");
},
);
}
}
// getFinishImage は、ImageProvider 型のオブジェクトを返す関数で、Flutterアプリケーションで使用される画像を提供します。
// この関数は、DestinationController というクラスのインスタンスに依存しており、特定の状態に基づいて適切な画像を返します。
//
// この関数は、アプリケーションの現在の状態に依存して動的に画像を提供します。
// DestinationController の photos リストに基づいて画像を選択し、リストが空の場合はデフォルトの画像を提供します。
// これにより、画像の動的な管理が可能になり、ユーザーインターフェースの柔軟性が向上します。
// また、ImageProvider クラスを使用することで、
// 画像の具体的な取得方法(ファイルからの読み込みやアセットからのロードなど)を抽象化し、
// Flutterの Image ウィジェットで直接使用できる形式で画像を返します。
//
ImageProvider getFinishImage() {
// DestinationControllerの取得:
// destinationController は Get.find<DestinationController>() を使用して取得されます。
// これは、GetXというFlutterの状態管理ライブラリの機能を使用して、
// 現在のアプリケーションコンテキストから DestinationController タイプのインスタンスを取得するものです。
// これにより、アプリケーションの他の部分で共有されている DestinationController の現在のインスタンスにアクセスします。
//
DestinationController destinationController =
Get.find<DestinationController>();
// 画像の決定:
// destinationController.photos リストが空でないかどうかをチェックします。
// このリストは、ファイルパスまたは画像リソースへの参照を含む可能性があります。
//
if (destinationController.photos.isNotEmpty) {
// リストが空でない場合、最初の要素 (destinationController.photos[0]) が使用されます。
// FileImage コンストラクタを使用して、このパスから ImageProvider を生成します。
// これは、ローカルファイルシステム上の画像ファイルを参照するためのものです。
//
return FileImage(destinationController.photos[0]);
} else {
// destinationController.photos が空の場合、
// AssetImage を使用してアプリケーションのアセットからデフォルトの画像('assets/images/empty_image.png')を
// ロードします。これはビルド時にアプリケーションに組み込まれる静的なリソースです。
//
return const AssetImage('assets/images/empty_image.png');
}
}
// getReceiptImage は、ImageProvider 型を返す関数です。
// この関数は DestinationController オブジェクトに依存しており、条件に応じて特定の画像を返します。
// この関数は getFinishImage 関数と非常に似ており、ほぼ同じロジックを使用していますが、返されるデフォルトの画像が異なります。
//
ImageProvider getReceiptImage() {
DestinationController destinationController =
Get.find<DestinationController>();
if (destinationController.photos.isNotEmpty) {
return FileImage(destinationController.photos[0]);
} else {
return const AssetImage('assets/images/money.png');
}
}
// CameraPageクラスは、目的地に応じて適切なカメラ機能とアクションボタンを提供します。
// 手動チェックイン、ゴール撮影、購入ポイント撮影など、様々なシナリオに対応しています。
// また、ロゲイニングが開始されていない場合は、StartRogainingウィジェットを表示して、ユーザーにロゲイニングの開始を促します。
// CameraPageクラスは、IndexControllerとDestinationControllerを使用して、
// 現在の状態や目的地の情報を取得し、適切なUIを構築します。
// また、写真の撮影や購入ポイントの処理など、様々な機能を提供しています。
//
class CameraPage extends StatelessWidget {
bool? manulaCheckin = false; // 手動チェックインを示すブール値デフォルトはfalse
bool? buyPointPhoto = false; // 購入ポイントの写真を示すブール値デフォルトはfalse
Destination destination; // 目的地オブジェクト
Destination? dbDest; // データベースから取得した目的地オブジェクト(オプショナル)
String? initImage; // 初期画像のパス(オプショナル)
bool? buyQrCode = false;
CameraPage(
{Key? key,
required this.destination,
this.dbDest,
this.manulaCheckin,
this.buyPointPhoto,
this.initImage})
: super(key: key);
DestinationController destinationController =
Get.find<DestinationController>();
IndexController indexController = Get.find<IndexController>();
var settingGoal = false.obs;
Timer? timer;
// 現在の状態に基づいて、適切なアクションボタンを返します。
// 要修正:エラーハンドリングが不十分です。例外が発生した場合の処理を追加することをお勧めします。
//
Widget getAction(BuildContext context) {
if (manulaCheckin == true) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Wrap(
spacing: 16.0,
runSpacing: 8.0,
children: [
Obx(() => ElevatedButton(
onPressed: () {
destinationController.openCamera(context, destination);
},
style: ElevatedButton.styleFrom(
shape: const CircleBorder(),
padding: const EdgeInsets.all(20),
backgroundColor: destinationController.photos.isEmpty
? Colors.red
: Colors.grey[300],
),
child: destinationController.photos.isEmpty
? const Text("撮影", style: TextStyle(color: Colors.white))
: const Text("再撮影", style: TextStyle(color: Colors.black)),
)),
Obx(() => destinationController.photos.isNotEmpty
? ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
onPressed: () async {
await destinationController.makeCheckin(destination, true,
destinationController.photos[0].path);
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,
duration: const Duration(seconds: 2),
);
await Future.delayed(const Duration(seconds: 2));
Navigator.of(context).pop(true);
},
child: const Text("チェックイン", style: TextStyle(color: Colors.white)),
)
: Container())
],
),
);
}
if (destinationController.isAtGoal.value &&
destinationController.isInRog.value &&
destination.cp == -1) {
// isAtGoalがtrueで、isInRogがtrue、destination.cpが-1の場合は、ゴール用の撮影ボタンとゴール完了ボタンを表示します。
//goal
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
onPressed: () {
if (settingGoal.value == false) {
destinationController.openCamera(context, destination);
}
},
child: Text("take_photo of the clock".tr)),
Obx(() => destinationController.photos.isNotEmpty
? settingGoal.value == false
? ElevatedButton(
style:
ElevatedButton.styleFrom(backgroundColor: Colors.red),
onPressed: () async {
// print(
// "----- user isss ${indexController.currentUser[0]} -----");
settingGoal.value = true;
try {
int userId =
indexController.currentUser[0]["user"]["id"];
//print("--- Pressed -----");
String team = indexController.currentUser[0]["user"]
['team_name'];
//print("--- _team : ${_team}-----");
String eventCode = indexController.currentUser[0]
["user"]["event_code"];
//print("--- _event_code : ${_event_code}-----");
String token =
indexController.currentUser[0]["token"];
//print("--- _token : ${_token}-----");
DateTime now = DateTime.now();
String formattedDate =
DateFormat('yyyy-MM-dd HH:mm:ss').format(now);
await ExternalService()
.makeGoal(
userId,
token,
team,
destinationController.photos[0].path,
formattedDate,
eventCode)
.then((value) {
// print(
// "---called ext api ${value['status']} ------");
if (value['status'] == 'OK') {
Get.back();
destinationController.skipGps = false;
Get.snackbar("目標が保存されました", "目標が正常に追加されました",
backgroundColor: Colors.green,
colorText: Colors.white
);
destinationController.resetRogaining(
isgoal: true);
} else {
//print("---- status ${value['status']} ---- ");
Get.snackbar("目標が追加されていません", "please_try_again",
backgroundColor: Colors.green,
colorText: Colors.white
);
}
});
} on Exception catch (_) {
settingGoal.value = false;
} finally {
settingGoal.value = false;
}
},
child: Text("finish_goal".tr))
: const Center(
child: CircularProgressIndicator(),
)
: Container())
],
);
} else if (destinationController.isInRog.value &&
dbDest?.checkedin != null &&
destination.cp != -1 &&
dbDest?.checkedin == true) {
// isInRogがtrueで、dbDest?.checkedinがtrue、destination.cpが-1以外の場合は、購入ポイントの撮影ボタンと完了ボタンを表示します。
//make buypoint image
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Obx(() => ElevatedButton(
onPressed: () {
destinationController.openCamera(context, destination);
},
child: destinationController.photos.isNotEmpty
? const Text("再撮影")
: const Text("撮影"))),
Obx(() => destinationController.photos.isNotEmpty
? ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
onPressed: () async {
// print(
// "##### current destination ${indexController.currentDestinationFeature[0].sub_loc_id} #######");
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
);
Navigator.of(context).pop(true); // ここを修正
},
child: const Text("レシートの写真を撮ってください"))
: Container())
],
);
} else if (destinationController.isInRog.value &&
dbDest?.checkedin != null &&
destination.cp != -1 &&
destination.use_qr_code == true &&
dbDest?.checkedin == true) {
// isInRogがtrueで、dbDest?.checkedinがtrue、destination.cpが-1以外、qrCode == true の場合は、
// QRCode 撮影ボタンを表示
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Obx(() => ElevatedButton(
onPressed: () {
destinationController.openCamera(context, destination);
},
child: destinationController.photos.isNotEmpty
? const Text("再QR読込")
: const Text("QR読込"))),
Obx(() => destinationController.photos.isNotEmpty
? ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
onPressed: () async {
// print(
// "##### current destination ${indexController.currentDestinationFeature[0].sub_loc_id} #######");
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("QRコードを読み取ってください"))
: Container())
],
);
} else {
// それ以外の場合は、撮影ボタンとチェックインボタンを表示します。
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Obx(() => ElevatedButton(
onPressed: () {
destinationController.openCamera(context, destination);
},
child: destinationController.photos.isNotEmpty
? const Text("再撮影")
: const Text("撮影"))),
Obx(() => destinationController.photos.isNotEmpty
? ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
onPressed: () async {
// print(
// "##### current destination ${indexController.currentDestinationFeature[0].sub_loc_id} #######");
await destinationController.makeCheckin(
indexController.currentDestinationFeature[0],
true,
destinationController.photos[0].path);
//Get.back();
destinationController.rogainingCounted.value = true;
destinationController.skipGps = false;
destinationController.isPhotoShoot.value = false;
Get.snackbar(
"チェックインしました",
indexController.currentDestinationFeature[0].name ??
"",
backgroundColor: Colors.green,
colorText: Colors.white,
duration: const Duration(seconds: 2), // 表示時間を1秒に設定
);
// SnackBarの表示が終了するのを待ってからCameraPageを閉じる
await Future.delayed(const Duration(seconds: 2));
Navigator.of(context).pop(true); // ここを修正
},
child: const Text("チェックイン"))
: Container())
],
);
}
}
// void finishRog(){
// destinationController.addToRogaining(destinationController.current_lat, destinationController.current_lon, destination_id)
// }
@override
Widget build(BuildContext context) {
//print("---- photos ${destination.photos} ----");
if (buyPointPhoto == true) {
// buyPointPhotoがtrueの場合は、BuyPointCameraウィジェットを返します。
//print("--- buy point camera ${destination.toString()}");
return BuyPointCamera(destination: destination);
//}else if(destination.use_qr_code){
// return QRCodeScannerPage(destination: destination);
} else if (destinationController.isInRog.value) {
// isInRogがtrueの場合は、カメラページのUIを構築します。
// AppBarには、目的地の情報を表示します。
// ボディには、目的地の画像、タグ、アクションボタンを表示します。
//print("-----tags camera page----- ${destination.tags}");
//print("--- in normal camera ${destination.toString()}");
return Scaffold(
appBar: destinationController.isInRog.value &&
destinationController.rogainingCounted.value == true
? AppBar(
automaticallyImplyLeading: false,
title: destination.cp == -1
? Text("finishing_rogaining".tr)
: Text("${destination.sub_loc_id} : ${destination.name}"),
leading: IconButton(
icon: Text("cancel".tr),
onPressed: () {
Navigator.of(context).pop();
destinationController.skip_10s = true;
timer =
Timer.periodic(const Duration(seconds: 10), (Timer t) {
destinationController.skip_10s = false;
});
},
),
centerTitle: true,
)
: AppBar(
automaticallyImplyLeading: false,
title: Text("${destination.sub_loc_id} : ${destination.name}"),
),
body: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Obx(
() => Container(
width: MediaQuery.of(context).size.width,
height: 370,
decoration: BoxDecoration(
image: DecorationImage(
image: destinationController.photos.isEmpty
? getDisplayImage(destination).image
: getFinishImage(),
fit: BoxFit.cover)),
),
),
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text(getTagText(
false,
destination.tags,
))
// child: Obx(() => destinationController.photos.isEmpty == true
// ? const Text("撮影してチェックインしてください。")
// : const Text("チェックインをタップしてください。")),
),
getAction(context),
],
),
),
);
} else {
// isInRogがfalseの場合は、StartRogainingウィジェットを返します。
return StartRogaining();
}
}
}
// ロゲイニングが開始されていない場合に表示されるウィジェットです。
// "You have not started rogaining yet."というメッセージと、戻るボタンを表示します。
//
class StartRogaining extends StatelessWidget {
StartRogaining({Key? key}) : super(key: key);
DestinationController destinationController =
Get.find<DestinationController>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(
"Not started yet".tr,
),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("You have not started rogaining yet.".tr,
style: const TextStyle(fontSize: 24)),
const SizedBox(
height: 40.0,
),
ElevatedButton(
onPressed: () {
Get.back();
destinationController.skipGps = false;
},
child: const Text("Back"),
),
],
),
),
);
}
}
// 購入ポイントの写真撮影用のウィジェットです。
// 目的地の画像、タグ、撮影ボタン、完了ボタン、購入なしボタンを表示します。
// 撮影ボタンをタップすると、カメラが起動します。
// 完了ボタンをタップすると、購入ポイントの処理が行われます。
// 購入なしボタンをタップすると、購入ポイントがキャンセルされます。
//
class BuyPointCamera extends StatelessWidget {
BuyPointCamera({Key? key, required this.destination}) : super(key: key);
Destination destination;
DestinationController destinationController =
Get.find<DestinationController>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(
"${destination.sub_loc_id} : ${destination.name}",
),
),
body: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Obx(
() => Container(
width: MediaQuery.of(context).size.width,
height: 370,
decoration: BoxDecoration(
image: DecorationImage(
// 要修正getReceiptImage関数の戻り値がnullの場合のエラーハンドリングが不十分です。適切なデフォルト画像を表示するなどの処理を追加してください。
//
image: getReceiptImage(), fit: BoxFit.cover)),
),
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(getTagText(true, destination.tags)),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Wrap(
spacing: 16.0,
runSpacing: 8.0,
children: [
Obx(() => ElevatedButton(
onPressed: () {
destinationController.openCamera(context, destination);
},
style: ElevatedButton.styleFrom(
shape: const CircleBorder(),
padding: const EdgeInsets.all(20),
backgroundColor: destinationController.photos.isEmpty
? Colors.red
: Colors.grey[300],
),
child: destinationController.photos.isEmpty
? const Text("撮影",
style: TextStyle(color: Colors.white))
: const Text("再撮影",
style: TextStyle(color: Colors.black)),
)),
ElevatedButton(
onPressed: () async {
await destinationController.cancelBuyPoint(destination);
Navigator.of(Get.context!).pop();
destinationController.rogainingCounted.value = true;
destinationController.skipGps = false;
destinationController.isPhotoShoot.value = false;
},
child: const Text("買い物なし")),
Obx(() => destinationController.photos.isNotEmpty
? ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red),
onPressed: () async {
await destinationController.makeBuyPoint(
destination,
destinationController.photos[0].path);
Get.back();
destinationController.rogainingCounted.value = true;
destinationController.skipGps = false;
destinationController.isPhotoShoot.value = false;
Get.snackbar("お買い物加点を行いました",
"${destination.sub_loc_id} : ${destination.name}",
backgroundColor: Colors.green,
colorText: Colors.white);
},
child: const Text("完了",
style: TextStyle(color: Colors.white)))
: Container())
],
),
),
],
),
),
);
}
}
/*
class BuyPointCamera extends StatelessWidget {
BuyPointCamera({Key? key, required this.destination}) : super(key: key);
Destination destination;
DestinationController 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(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Obx(
() => Container(
width: MediaQuery.of(context).size.width,
height: 370,
decoration: BoxDecoration(
image: DecorationImage(
// 要修正getReceiptImage関数の戻り値がnullの場合のエラーハンドリングが不十分です。適切なデフォルト画像を表示するなどの処理を追加してください。
//
image: getReceiptImage(), fit: BoxFit.cover)),
),
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(getTagText(true, destination.tags)),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
mainAxisSize: MainAxisSize.min,
children: [
Obx(() => Row(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
onPressed: () {
// print(
// "in camera purchase 2 ${destinationController.isInRog.value}");
destinationController.openCamera(
context, destination);
},
child: destinationController.photos.isNotEmpty
? const Text("再撮影")
: const Text("撮影")),
const SizedBox(
width: 10,
),
ElevatedButton(
onPressed: () async {
await destinationController
.cancelBuyPoint(destination);
Navigator.of(Get.context!).pop();
//Get.back();
destinationController.rogainingCounted.value = true;
destinationController.skipGps = false;
destinationController.isPhotoShoot.value = false;
},
child: const Text("買い物なし"))
],
)),
Obx(() => destinationController.photos.isNotEmpty
? Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// ElevatedButton(
// style: ElevatedButton.styleFrom(
// backgroundColor: Colors.red),
// onPressed: () async {},
// child: const Text("買物なし")),
// const SizedBox(
// width: 10,
// ),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red),
onPressed: () async {
// print(
// "in camera purchase 3 ${destinationController.isInRog.value}");
await destinationController.makeBuyPoint(
destination,
destinationController.photos[0].path);
Get.back();
// print(
// "in camera purchase 4 ${destinationController.isInRog.value}");
destinationController.rogainingCounted.value =
true;
destinationController.skipGps = false;
destinationController.isPhotoShoot.value = false;
Get.snackbar("お買い物加点を行いました",
"${destination.sub_loc_id} : ${destination.name}",
backgroundColor: Colors.green,
colorText: Colors.white
);
},
child: const Text("完了"))
],
)
: Container())
],
),
],
),
);
}
}
*/
class QRCodeScannerPage extends StatefulWidget {
QRCodeScannerPage({Key? key, required this.destination}) : super(key: key);
Destination destination;
@override
_QRCodeScannerPageState createState() => _QRCodeScannerPageState();
}
class _QRCodeScannerPageState extends State<QRCodeScannerPage> {
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
QRViewController? controller;
@override
void dispose() {
controller?.dispose();
super.dispose();
}
void _onQRViewCreated(QRViewController controller) {
this.controller = controller;
controller.scannedDataStream.listen((scanData) {
// QRコードのデータを処理する
debugPrint("scan data = ${scanData}");
String? qrCodeData = scanData.code;
// qrCodeDataを使用してチェックポイントの処理を行う
// 例えば、qrCodeDataからCPのIDと店名を取得し、加点処理を行う
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: QRView(
key: qrKey,
onQRViewCreated: _onQRViewCreated,
),
);
}
}import 'dart:io';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import 'package:rogapp/model/destination.dart';
class CustomCameraView extends StatefulWidget {
final Function(String) onImageCaptured;
final Destination? destination;
const CustomCameraView({Key? key, required this.onImageCaptured, required this.destination}) : super(key: key);
@override
_CustomCameraViewState createState() => _CustomCameraViewState();
}
class _CustomCameraViewState extends State<CustomCameraView> {
CameraController? _controller;
late List<CameraDescription> _cameras;
int _selectedCameraIndex = 0;
double _currentScale = 1.0;
FlashMode _currentFlashMode = FlashMode.off;
Destination? destination;
@override
void initState() {
super.initState();
_initializeCamera();
destination = widget.destination;
}
Future<void> _initializeCamera() async {
_cameras = await availableCameras();
_controller = CameraController(_cameras[_selectedCameraIndex], ResolutionPreset.medium);
await _controller!.initialize();
setState(() {});
}
@override
void dispose() {
_controller?.dispose();
super.dispose();
}
Future<void> _toggleCameraLens() async {
final newIndex = (_selectedCameraIndex + 1) % _cameras.length;
await _controller!.dispose();
setState(() {
_controller = null;
_selectedCameraIndex = newIndex;
});
_controller = CameraController(_cameras[_selectedCameraIndex], ResolutionPreset.medium);
await _controller!.initialize();
setState(() {});
}
void _toggleFlashMode() {
setState(() {
_currentFlashMode = (_currentFlashMode == FlashMode.off) ? FlashMode.torch : FlashMode.off;
});
_controller!.setFlashMode(_currentFlashMode);
}
void _zoomIn() {
setState(() {
_currentScale += 0.1;
if (_currentScale > 5.0) _currentScale = 5.0;
});
_controller!.setZoomLevel(_currentScale);
}
void _zoomOut() {
setState(() {
_currentScale -= 0.1;
if (_currentScale < 1.0) _currentScale = 1.0;
});
_controller!.setZoomLevel(_currentScale);
}
void _captureImage() async {
if (_controller!.value.isInitialized) {
final Directory appDirectory = await getApplicationDocumentsDirectory();
final String imagePath = path.join(appDirectory.path, '${DateTime.now()}.jpg');
final XFile imageFile = await _controller!.takePicture();
await imageFile.saveTo(imagePath);
widget.onImageCaptured(imagePath);
Navigator.pop(context);
}
}
@override
Widget build(BuildContext context) {
if (_controller == null || !_controller!.value.isInitialized) {
return Container();
}
return Stack(
children: [
Padding(
padding: const EdgeInsets.only(top: 60.0), // 上部に60ピクセルのパディングを追加
child: CameraPreview(_controller!),
),
Positioned(
bottom: 120.0,
left: 16.0,
right: 16.0,
child: Center(
child: Text(
destination?.tags ?? '',
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
),
Positioned(
bottom: 16.0,
left: 16.0,
right: 16.0,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: _toggleFlashMode,
icon: Icon(
(_currentFlashMode == FlashMode.off) ? Icons.flash_off : Icons.flash_on,
color: Colors.white,
),
iconSize: 32,
color: Colors.orange,
),
GestureDetector(
onTap: _captureImage,
child: Container(
height: 80,
width: 80,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
border: Border.all(color: Colors.red, width: 4),
),
child: const Icon(Icons.camera_alt, color: Colors.red, size: 40),
),
),
IconButton(
onPressed: _toggleCameraLens,
icon: const Icon(Icons.flip_camera_ios, color: Colors.white),
iconSize: 32,
color: Colors.blue,
),
],
),
),
Positioned(
top: 16.0,
right: 16.0,
child: Column(
children: [
IconButton(
onPressed: _zoomIn,
icon: const Icon(Icons.zoom_in, color: Colors.white),
iconSize: 32,
color: Colors.green,
),
IconButton(
onPressed: _zoomOut,
icon: const Icon(Icons.zoom_out, color: Colors.white),
iconSize: 32,
color: Colors.green,
),
],
),
),
],
);
}
}import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_location_marker/flutter_map_location_marker.dart';
//import 'package:flutter_map_marker_popup/flutter_map_marker_popup.dart';
import 'package:flutter_polyline_points/flutter_polyline_points.dart';
import 'package:get/get.dart';
import 'package:latlong2/latlong.dart';
import 'package:rogapp/model/destination.dart';
import 'package:rogapp/pages/destination/destination_controller.dart';
import 'package:rogapp/pages/index/index_controller.dart';
import 'package:rogapp/utils/text_util.dart';
import 'package:rogapp/widgets/base_layer_widget.dart';
import 'package:rogapp/widgets/bottom_sheet_new.dart';
//import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_start.dart';
//import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_goal.dart';
//import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_normal_point.dart';
// FlutterMapウィジェットを使用して、地図を表示します。
// IndexControllerから目的地のリストを取得し、マーカーとしてマップ上に表示します。
// マーカーがタップされると、BottomSheetウィジェットを表示します。
// 現在地の表示、ルートの表示、ベースレイヤーの表示などの機能を提供します。
// 主なロジック:
// FlutterMapウィジェットを使用して、地図を表示します。
// IndexControllerから目的地のリストを取得し、MarkerLayerを使用してマーカーを表示します。
// getMarkerShapeメソッドを使用して、マーカーの見た目をカスタマイズします。目的地の種類に応じて、異なるマーカーを表示します。
// マーカーがタップされると、festuretoDestinationメソッドを使用してGeoJSONFeatureをDestinationオブジェクトに変換し、showModalBottomSheetを使用してBottomSheetウィジェットを表示します。
// CurrentLocationLayerを使用して、現在地をマップ上に表示します。
// PolylineLayerを使用して、ルートをマップ上に表示します。getPointsメソッドを使用して、ルートの座標を取得します。
// BaseLayerを使用して、マップのベースレイヤーを表示します。
//
class DestinationMapPage extends StatelessWidget {
DestinationMapPage({Key? key}) : super(key: key);
final IndexController indexController = Get.find<IndexController>();
final DestinationController destinationController =
Get.find<DestinationController>();
StreamSubscription? subscription;
//final PopupController _popupLayerController = PopupController();
List<LatLng>? getPoints() {
//print("##### --- route point ${indexController.routePoints.length}");
List<LatLng> pts = [];
for (PointLatLng p in indexController.routePoints) {
LatLng l = LatLng(p.latitude, p.longitude);
pts.add(l);
}
return pts;
}
// 要検討:マーカーのタップイベントを処理する際に、エラーハンドリングが不十分です。例外が発生した場合の処理を追加することをお勧めします。
//
List<Marker>? getMarkers() {
List<Marker> pts = [];
//int index = -1;
for (int i = 0; i < destinationController.destinations.length; i++) {
Destination d = destinationController.destinations[i];
//print("^^^^ $d ^^^^");
Marker m = Marker(
point: LatLng(d.lat!, d.lon!),
alignment: Alignment.center,
child: InkWell(
onTap: () {
//print("-- Destination is --- ${d.name} ------");
if (indexController.currentDestinationFeature.isNotEmpty) {
indexController.currentDestinationFeature.clear();
}
indexController.currentDestinationFeature.add(d);
//indexController.getAction();
Widget bottomSheet = BottomSheetNew(destination: d);
/*
if (d.cp == -1 || d.cp == 0) {
bottomSheet = BottomSheetStart(destination: d);
} else if (d.cp == -2 || d.cp == 0) {
bottomSheet = BottomSheetGoal(destination: d);
} else {
bottomSheet = BottomSheetNormalPoint(destination: d);
}
*/
showModalBottomSheet(
context: Get.context!,
isScrollControlled: true,
constraints:
BoxConstraints.loose(Size(Get.width, Get.height * 0.85)),
builder: ((context) => bottomSheet ),
).whenComplete(() {
//print("---- set skip gps to false -----");
destinationController.skipGps = false;
});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
border: Border.all(
color: Colors.white,
width: d.checkin_radious != null ? d.checkin_radious! : 1,
),
),
child: Center(
child: Text(
(i + 1).toString(),
style: const TextStyle(color: Colors.white),
),
),
),
Container(
color: Colors.yellow,
child: Text(
TextUtils.getDisplayText(d),
style: const TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold,
overflow: TextOverflow.visible),
)),
],
),
));
pts.add(m);
}
return pts;
}
@override
Widget build(BuildContext context) {
return Obx((() => Stack(
children: [
// indexController.is_rog_mapcontroller_loaded.value == false ?
// Center(child: CircularProgressIndicator())
// :
// Padding(
// padding: const EdgeInsets.only(left:8.0),
// child: BreadCrumbWidget(mapController:indexController.rogMapController),
// ),
Padding(
padding: const EdgeInsets.only(top: 0.0),
//child: TravelMap(),
child: travelMap(),
),
],
)));
}
// 要検討MapOptionsのboundsプロパティにハードコードされた座標が使用されています。これを動的に設定できるようにすることを検討してください。
//
FlutterMap travelMap() {
return FlutterMap(
mapController: indexController.rogMapController,
options: MapOptions(
onMapReady: () {
indexController.isRogMapcontrollerLoaded.value = true;
subscription = indexController.rogMapController.mapEventStream
.listen((MapEvent mapEvent) {
if (mapEvent is MapEventMoveStart) {}
if (mapEvent is MapEventMoveEnd) {
//destinationController.is_gps_selected.value = true;
//indexController.mapController!.move(c.center, c.zoom);
LatLngBounds bounds = indexController.rogMapController.bounds!;
indexController.currentBound.clear();
indexController.currentBound.add(bounds);
if (indexController.currentUser.isEmpty) {
indexController.loadLocationsBound();
}
}
});
},
bounds: indexController.currentBound.isNotEmpty
? indexController.currentBound[0]
: LatLngBounds.fromPoints([
const LatLng(35.03999881162295, 136.40587119778962),
const LatLng(36.642756778706904, 137.95226720406063)
]),
initialZoom: 1,
maxZoom: 42,
interactiveFlags: InteractiveFlag.pinchZoom | InteractiveFlag.drag,
),
children: [
const BaseLayer(),
Obx(
() => indexController.routePointLenght > 0
? PolylineLayer(
polylines: [
Polyline(
points: getPoints()!,
strokeWidth: 6.0,
color: Colors.indigo),
],
)
: Container(),
),
CurrentLocationLayer(),
MarkerLayer(markers: getMarkers()!),
],
);
}
}
import 'package:get/get.dart';
import 'package:rogapp/main.dart';
import 'package:rogapp/pages/destination/destination_controller.dart';
class DestinationBinding extends Bindings {
@override
void dependencies() {
Get.put<DestinationController>(DestinationController());
restoreGame();
}
}
import 'dart:io';
import 'dart:typed_data';
import 'package:camera_camera/camera_camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_location_marker/flutter_map_location_marker.dart';
import 'package:geojson_vi/geojson_vi.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:latlong2/latlong.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:rogapp/main.dart';
import 'package:rogapp/model/destination.dart';
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/routes/app_pages.dart';
import 'package:rogapp/services/DatabaseService.dart';
import 'package:rogapp/services/destination_service.dart';
import 'package:rogapp/services/external_service.dart';
import 'package:rogapp/services/location_service.dart';
import 'package:rogapp/services/maxtrix_service.dart';
import 'package:rogapp/services/perfecture_service.dart';
import 'package:rogapp/utils/database_gps.dart';
import 'package:rogapp/utils/database_helper.dart';
import 'package:rogapp/utils/location_controller.dart';
import 'package:rogapp/widgets/bottom_sheet_new.dart';
import 'dart:async';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'package:rogapp/widgets/debug_widget.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:rogapp/utils/const.dart';
import 'package:logger/logger.dart';
import 'package:rogapp/pages/permission/permission.dart';
// 目的地に関連する状態管理とロジックを担当するクラスです。
//
class DestinationController extends GetxController {
late LocationSettings locationSettings; // 位置情報の設定を保持する変数です。
//Timer? _GPStimer; // GPSタイマーを保持する変数です。
var destinationCount = 0.obs; // 目的地の数を保持するReactive変数です。
List<Destination> destinations = <Destination>[].obs; // 目的地のリストを保持するObservable変数です。
double currentLat = 0.0; // 現在の緯度と経度を保持する変数です。
double currentLon = 0.0;
double lastValidLat = 0.0; // 最後に中・強信号で拾ったGPS位置。
// ロゲ開始を屋内でやったら 0 のままなので、屋外で行うこと。
double lastValidLon = 0.0;
DateTime lastGPSCollectedTime = DateTime.now(); // 最後にGPSデータが収集された時刻を保持する変数です。
bool shouldShowBottomSheet = true; // ボトムシートを表示すべきかどうかを示すフラグです。
static bool gps_push_started = false; // ゲームの状態を示す静的変数です。
static bool game_started = false;
static bool ready_for_goal = false;
bool skip_10s = false; // 10秒間のスキップフラグを示す変数です。
List<Destination> currentSelectedDestinations = <Destination>[].obs; // 現在選択されている目的地のリストを保持するObservable変数です。
var isInCheckin = false.obs; // ゲームの状態を示すReactive変数です。
var isInRog = false.obs;
var isAtStart = false.obs;
var isAtGoal = false.obs;
var isPhotoShoot = false.obs;
DateTime lastGoalAt = DateTime.now().subtract(const Duration(days: 1)); // 最後にゴールした時刻を保持する変数です。
//List<Rogaining> rogainings = <Rogaining>[].obs;
bool checkingIn = false; // チェックイン中かどうかを示すフラグです。
var isGpsSelected = true.obs; // GPSが選択されているかどうかを示すReactive変数です。
BuildContext? context; // ビルドコンテキストを保持する変数です。
List<String> gps = <String>["-- stating --"].obs; // GPSと位置情報の許可に関する情報を保持するObservable変数です。
List<String> locationPermission = <String>[" -- starting -- "].obs;
var travelMode = 0.obs; // 移動モードを保持するReactive変数です。
bool skipGps = false; // GPSをスキップするかどうかを示すフラグです。
bool okToUseGPS = false; // 最新のGPS情報を使用して良いかを示すフラグ。
Map<String, dynamic> matrix = {}; // 行列データを保持する変数です。
final photos = <File>[].obs; // 写真のリストを保持するReactive変数です。
final IndexController indexController = Get.find<IndexController>(); // IndexControllerのインスタンスを保持する変数です。
final LocationController locationController = Get.put(LocationController()); // LocationControllerのインスタンスを保持する変数です。
final DatabaseService dbService = DatabaseService(); // DatabaseServiceのインスタンスを保持する変数です。
int _start = 0; // 開始時刻を保持する変数です。
int chekcs = 0; // チェックポイントの数を保持する変数です。
var rogainingCounted = false.obs; // ロゲイニングがカウントされたかどうかを示すReactive変数です。
// destinationController.rogainingCountedは、現在のロゲイニングセッションでポイントがカウントされたかどうかを管理するフラグです。
//
// このフラグは以下のような状況で使用されます:
//
// ロゲイニングを開始したとき、rogainingCountedはfalseに初期化されます。これは、まだポイントがカウントされていないことを示します。
// チェックポイントに到着し、チェックインが成功したとき、rogainingCountedはtrueに設定されます。これは、そのセッションでポイントがカウントされたことを示します。
// ロゲイニングを終了したとき、rogainingCountedは再びfalseに設定されます。これは、次のセッションに備えてフラグをリセットするためです。
// このフラグは、主に以下の目的で使用されます:
//
// ゴール地点でのロジックの制御rogainingCountedがtrueの場合、つまりポイントがカウントされている場合にのみ、ゴール処理を実行できます。
// UI の更新rogainingCountedの状態に基づいて、適切なメッセージやボタンを表示することができます。
bool isMapControllerReady = false;
LatLng lastValidGPSLocation = LatLng(0, 0);
DateTime lastGPSDataReceivedTime = DateTime.now();
DateTime lastPopupShownTime = DateTime.now().subtract(Duration(minutes: 10));
bool isPopupShown = false;
bool hasReceivedGPSData = true;
var isCheckingIn = false.obs; // チェックイン操作中はtrueになり、重複してポップアップが出ないようにするもの。
var isRouteShowing = false.obs; // ルートが表示されているかどうかを示すReactive変数
/*
//==== Akira .. GPS信号シミュレーション用 ===== ここから、2024-4-5
//
bool kDebugMode = true;
// シミュレーションモードのフラグ
RxBool isSimulationMode = RxBool(true);
// シミュレーションモードを切り替えるための関数
void toggleSimulationMode(bool value) {
isSimulationMode.value = value;
}
// 現在位置の取得メソッドを追加
LatLng getCurrentLocation() {
return LatLng(lastValidLat, lastValidLon);
}
//
// GPS信号の強弱を判断するメソッドを追加します。
//
String getGpsSignalStrength() {
// デバッグモードかつシミュレーションモードの場合は、シミュレートされた信号強度を返す
print("kDebugMode : ${kDebugMode}, isSimulationMode : ${isSimulationMode.value}");
if (kDebugMode && isSimulationMode.value) {
return locationController.getSimulatedSignalStrength();
}
// 通常モードの場合は、実際の信号強度を返す
final accuracy = locationController.currentPosition.value?.accuracy ?? double.infinity;
if (accuracy <= 10) {
return 'high';
} else if (accuracy <= 30) {
return 'medium';
} else {
return 'low';
}
}
//
//==== Akira .. GPS信号シミュレーション用 ======= ここまで
*/
// ルートをクリアする関数です。
void clearRoute() {
indexController.routePoints.clear();
indexController.routePointLenght.value = 0;
isRouteShowing.value = false;
}
void showGPSDataNotReceivedPopup() {
if (Get.context != null) {
Get.dialog(
AlertDialog(
title: Text('GPS信号が受信できません'),
content: Text('GPS信号が受信できる場所に移動してください。'),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text('OK'),
),
],
),
);
} else {
// Get.contextがnullの場合の処理を追加
print('GPS signal not received, but context is null');
}
}
// 最後に有効なGPSデータを受け取ってから10分以上経過している場合にのみメッセージを表示するようにします。
//
void checkGPSDataReceived() {
if (!hasReceivedGPSData) {
//debugPrint("GPS信号を全く受信していない。");
if (!isPopupShown) {
// ポップアップしていない。
showGPSDataNotReceivedPopup();
lastPopupShownTime = DateTime.now();
isPopupShown = true;
}
} else {
if (DateTime.now().difference(lastGPSDataReceivedTime).inSeconds >= 600) {
// 前回GPS信号を受信してから10分経過。
if (!isPopupShown && DateTime.now().difference(lastPopupShownTime).inMinutes >= 3) {
// 前回ポップアップしてから3分経過してなければ
showGPSDataNotReceivedPopup();
lastPopupShownTime = DateTime.now();
isPopupShown = true;
}
} else {
isPopupShown = false;
}
}
}
// 日時をフォーマットされた文字列に変換する関数です。
//
String getFormatedTime(DateTime datetime) {
return DateFormat('yyyy-MM-dd HH:mm:ss').format(datetime);
}
// 追加Akira 2024-4-5
// GPS信号の精度が一定値以上の場合、GPS信号が弱いと判断する
//
bool isGpsSignalWeak() {
final accuracy = locationController.currentPosition.value?.accuracy;
if (accuracy == null) {
return true; // 位置情報が取得できていない場合、GPS信号が弱いと見なす
}
return accuracy > 60;
//return locationController.currentPosition.value?.accuracy ?? double.infinity > 50;
}
//
Destination festuretoDestination(GeoJSONFeature fs) {
GeoJSONMultiPoint mp = fs.geometry as GeoJSONMultiPoint;
LatLng pt = LatLng(mp.coordinates[0][1], mp.coordinates[0][0]);
//print("----- ${indexController.currentFeature[0].properties} -----");
return Destination(
name: fs.properties!["location_name"],
sub_loc_id: fs.properties!["sub_loc_id"],
address: fs.properties!["address"],
phone: fs.properties!["phone"],
email: fs.properties!["email"],
webcontents: fs.properties!["webcontents"],
videos: fs.properties!["videos"],
category: fs.properties!["category"],
series: 1,
lat: pt.latitude,
lon: pt.longitude,
location_id: fs.properties!["location_id"],
list_order: 1,
photos: fs.properties!["photos"],
checkin_radious: fs.properties!["checkin_radius"],
auto_checkin: fs.properties!["auto_checkin"] == true ? 1 : 0,
cp: fs.properties!["cp"],
checkin_point: fs.properties!["checkin_point"],
buy_point: fs.properties!["buy_point"],
selected: false,
checkedin: false,
hidden_location: fs.properties!["hidden_location"] == true ? 1 : 0,
tags: fs.properties!["tags"]);
}
// 指定された目的地の位置情報に基づいてタイマーを開始する関数です。
// CP情報(fs)と現在位置からCPまでの距離distance を引数として渡します。
//
Future<void> startTimerLocation(GeoJSONFeature fs, double distance) async {
//print("---- in startTimer ----");
// print("---- is in rog is $is_in_rog ----");
double checkinRadious = fs.properties!['checkin_radius'] ?? double.infinity;
// CPのcheckin_radiusを取得し、checkinRadius に代入。値がなければinfinityとする。
if (checkinRadious >= distance) {
// checkinRadious以内に入ったら、
indexController.currentFeature.clear();
// indexController.currentFeatureを空にします。
Destination d = festuretoDestination(fs);
// festuretoDestination(fs)を呼び出し、GeoJSONFeatureオブジェクトfsからDestinationオブジェクトdを作成します。
// print("----- destination lenght is ${destinations.length} -----");
indexController.currentFeature.add(fs);
// indexController.currentFeatureにfsを追加します。
//print("---- before calling startTimer ----");
await startTimer(d, distance);
// startTimer(d, distance)を非同期で呼び出し、その完了を待機します。
return;
}
}
// 指定された目的地に対してタイマーを開始する関数です。
// 目的地の位置情報を取得し、チェックイン半径内にいるかどうかを確認します。
// 写真撮影モードの場合は、ボトムシートを表示して写真撮影を行います。
// 目的地がデータベースに存在しない場合は、新しい目的地としてデータベースに挿入します。
// 目的地に応じて、チェックイン、ゴール、買い物ポイントの処理を行います。
//
// 2024-4-8 akira: GPS信号が弱い場合でも、最後に取得した位置情報を使用してチェックインやゴールの処理を続行できるようになります。また、チェックインやゴールの処理では、GPS信号の精度チェックを緩和することで、GPS信号が弱い場合でもボタンを押せるようになります。
//
// 要検討:エラーが発生した場合のエラーハンドリングを追加し、適切なメッセージを表示することを検討してください。
//
// 引数CPオブジェクトと現在地からCPまでの距離を渡す。
//
Future<void> startTimer(Destination d, double distance) async {
//print("=== passed dest is ${d.location_id} ${d.checkedin} ====");
skipGps = true;
//debugPrint("---- in startTimer ----");
DatabaseHelper db = DatabaseHelper.instance;
List<Destination> ds = await db.getDestinationByLatLon(d.lat!, d.lon!);
//指定位置のオブジェクトのリストを取得。
Destination? dss;
if (ds.isNotEmpty) {
dss = ds.first; // 取得したリストが空でない場合、dss変数に最初の要素を代入します。
}
// 変数を計算
double checkinRadious = d.checkin_radious ?? double.infinity; // 反応半径
bool autoCheckin = d.auto_checkin == 0 ? false : true; // 自動チェックイン
bool buyPoint = dss != null && dss.buy_point != null && dss.buy_point! > 0 // 買い物ポイント
? true
: false;
bool buyPointImageAdded = // 買い物画像
dss != null && dss.buypoint_image != null ? true : false;
bool buyPointCanceled = // 買い物キャンセル
dss != null && dss.buy_point != null && dss.buy_point == 0
? true
: false;
bool locationAlreadyCheckedIn = // チェックイン済みか
ds.isNotEmpty && ds[0].checkedin == true ? true : false;
bool isuserLoggedIn = indexController.currentUser.isNotEmpty ? true : false; // ログイン済みか
/*
// スタートとゴールは除外
debugPrint("startTimer CP=${d.cp}");
if (d.cp == -1 || d.cp == 0 || d.cp == -2) {
skipGps = false;
return;
}
*/
// 初期化。GPS信号が強くても弱くても
if (checkinRadious >= distance || checkinRadious == -1) {
//currentSelectedDestinations.add(d);
// 目的地として登録する。
//debugPrint("目的地の初期化");
indexController.currentDestinationFeature.clear();
indexController.currentDestinationFeature.add(d);
// print(
// "---- checked in as ${indexController.currentDestinationFeature[0].checkedin.toString()} ----");
} else {
// ここには来ないのでは?
debugPrint("検出範囲外...");
// GPS信号が弱い場合でも、チェックインやゴールの処理を続行する
// comment out by Akira, 2024-4-5
// skipGps = false;
// return;
// GPS信号が弱い場合、最後に取得した高いまたは中程度の位置情報を使用
if (okToUseGPS) {
double lastValidDistance = Geolocator.distanceBetween(
lastValidLat, lastValidLon,
d.lat!, d.lon!
);
/*
double lastValidDistance = distance.as(
LengthUnit.Meter,
LatLng(lastValidLat, lastValidLon),
LatLng(d.lat!, d.lon!),
);
*/
if (checkinRadious >= lastValidDistance || checkinRadious == -1) { // 反応半径内か、距離無視CPなら
indexController.currentDestinationFeature.clear();
indexController.currentDestinationFeature.add(d);
} else {
skipGps = false;
return;
}
} else {
skipGps = false;
return;
}
}
if (isPhotoShoot.value == true) { // 写真撮影するなら ... isPhotoShoot=True にしてる場所がない。
debugPrint("isPhotoShoot.value == true ... will camera popup");
photos.clear(); // まず既存の写真をクリア
if (shouldShowBottomSheet) { // ボトムシートを使うべきなら
shouldShowBottomSheet = false;
if (d.cp == -1) return; // CPは開始点なら戻る。
// カメラページをポップアップ
await showModalBottomSheet(
constraints:
BoxConstraints.loose(Size(Get.width, Get.height * 0.75)),
context: Get.context!,
isScrollControlled: true,
builder: ((context) => CameraPage(destination: d)))
.whenComplete(() {
shouldShowBottomSheet = true;
skipGps = false;
chekcs = 0;
isInCheckin.value = false;
});
}
return;
}
// 写真撮影モードでない場合
if (ds.isEmpty) {
debugPrint("* 目的地がない場合 ==> 検知半径=-1の場合");
// print("----- in location popup cp - ${d.cp}----");
if ((d.cp == -1 || d.cp==0 ) && DateTime.now().difference(lastGoalAt).inHours >= 24) {
debugPrint("**1: 開始CPで、最後にゴールしてから時間経過していれば、");
chekcs = 1;
//start
// print("~~~~ calling start ~~~~");
print("---- in start -----");
chekcs = 1; // スタート地点で前のゴールから24時間経過
isInCheckin.value = true;
isAtStart.value = true;
if (shouldShowBottomSheet) {
shouldShowBottomSheet = false; // bottom_sheet を起動させない。
Widget bottomSheet = BottomSheetNew(destination: d);
await showModalBottomSheet(
constraints:
BoxConstraints.loose(Size(Get.width, Get.height * 0.85)),
context: Get.context!,
isScrollControlled: true,
builder: ((context) => bottomSheet)
).whenComplete(() {
shouldShowBottomSheet = true; // bottom_sheet 起動許可
skipGps = false;
chekcs = 0; // ボトムシートモード=1,
isAtStart.value = false;
isInCheckin.value = false;
});
}
return;
// 以下の条件分岐を追加
} else if (ds.isNotEmpty && ds[0].checkedin == true) {
// 目的地がDBに存在し、すでにチェックインしている場合は自動ポップアップを表示しない
debugPrint("チェックイン済み");
return;
} else if (isInRog.value == true &&
indexController.rogMode.value == 1 &&
(locationAlreadyCheckedIn==null || locationAlreadyCheckedIn==false) &&
d.cp != -1 && d.cp != 0 && d.cp != -2) {
debugPrint("**2: 標準CP まだチェックインしていない。");
// print("----- in location popup checkin cp - ${d.cp}----");
chekcs = 2; // 標準CP
isInCheckin.value = true;
if (shouldShowBottomSheet) {
shouldShowBottomSheet = false;
Widget bottomSheet = BottomSheetNew(destination: d);
await showModalBottomSheet(
constraints:
BoxConstraints.loose(Size(Get.width, Get.height * 0.75)),
context: Get.context!,
isScrollControlled: true,
builder: ((context) => bottomSheet)
).whenComplete(() {
shouldShowBottomSheet = true;
skipGps = false;
chekcs = 0;
isInCheckin.value = false;
});
}
return;
}
}
// 以降、検知範囲にある場合。
//debugPrint("検知範囲にある場合");
// print("---- location checkin radious ${d.checkin_radious} ----");
// print("---- already checked in $locationAlreadyCheckedIn ----");
if ((checkinRadious >= distance || checkinRadious == -1) &&
locationAlreadyCheckedIn == false &&
isInRog.value == true &&
!isCheckingIn.value) {
debugPrint("* 検知範囲または距離無視CPで、ゲーム中でまだチェックインしていない。");
if (autoCheckin) { // 自動チェックインなら
if (!checkingIn) {
debugPrint("** 自動チェックインの場合");
//print(
// "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ make checkin ${d.sub_loc_id}@@@@@@@@@@@");
makeCheckin(d, true, ""); // チェックインして
if (d.cp != -1 && d.cp != -2 && d.cp != 0 ) {
rogainingCounted.value = true; // ゴール用チェックイン済み
}
skipGps = false;
}
return; // 戻る
} else {
// それ以外
debugPrint("* 自動チェックイン以外の場合");
// print("--- hidden loc ${d.hidden_location} ----");
// ask for checkin
if (d.hidden_location != null &&
d.hidden_location == 0 && // 隠しCPフラグ==0 ... 通常CP
isInRog.value == true &&
d.cp != -1 && d.cp != -2 && d.cp != 0) {
// 隠しCPの場合、
debugPrint("**3 通常CPの場合");
chekcs = 3;
isInCheckin.value = true;
isCheckingIn.value = true;
photos.clear();
// print("--- calling checkin ---");
if (shouldShowBottomSheet) {
shouldShowBottomSheet = false;
await showModalBottomSheet(
constraints:
BoxConstraints.loose(Size(Get.width, Get.height * 0.75)),
context: Get.context!,
isScrollControlled: true,
builder: ((context) => CameraPage(
destination: d,
))).whenComplete(() {
shouldShowBottomSheet = true;
skipGps = false;
rogainingCounted.value = true;
chekcs = 0;
isInCheckin.value = false;
isCheckingIn.value = false;
});
}
return;
} else if (isInRog.value == true &&
(locationAlreadyCheckedIn==null || locationAlreadyCheckedIn==false) &&
d.cp != -1 && d.cp != -2 && d.cp != 0) {
// 通常CP
debugPrint("**4 通常CP以外の場合....どんな場合?");
chekcs = 4;
isInCheckin.value = true;
if (shouldShowBottomSheet) {
shouldShowBottomSheet = false;
Widget bottomSheet = BottomSheetNew(destination: d);
await showMaterialModalBottomSheet(
expand: true,
context: Get.context!,
backgroundColor: Colors.transparent,
builder: (context) => bottomSheet
).whenComplete(() {
shouldShowBottomSheet = true;
skipGps = false;
chekcs = 0;
isInCheckin.value = false;
});
}
return;
}
}
} else if ((checkinRadious >= distance || checkinRadious == -1) &&
locationAlreadyCheckedIn == true &&
buyPointImageAdded == false &&
ds.isNotEmpty &&
buyPoint == true &&
buyPointCanceled == false &&
isInRog.value == true) {
// チェックイン後で買い物ポイントの場合。
debugPrint("**5 チェックイン後で買い物ポイントの場合");
chekcs = 5;
isInCheckin.value = true;
photos.clear();
//print("--- open buy point $buyPointImageAdded ${d.buypoint_image} ----");
if (shouldShowBottomSheet) {
shouldShowBottomSheet = false;
if (d.cp == -1 && d.cp != -2 && d.cp != 0) return;
await showModalBottomSheet(
constraints:
BoxConstraints.loose(Size(Get.width, Get.height * 0.75)),
context: Get.context!,
isScrollControlled: true,
builder: ((context) => CameraPage(
destination: d,
buyPointPhoto: true,
dbDest: ds.first,
))).whenComplete(() {
shouldShowBottomSheet = true;
skipGps = false;
rogainingCounted.value = true;
chekcs = 0;
isInCheckin.value = false;
});
}
return;
}
// print("---- cp --- ${d.cp} -----");
// print("--- at goal $is_at_goal ---");
// print("--- rog counted $rogaining_counted ---");
// print("--- loc already checked in $locationAlreadyCheckedIn ---");
// print(
// "==== date diff is ${DateTime.now().difference(last_goal_at).inHours} ====");
if (isuserLoggedIn &&
(d.cp == -2 || d.cp == 0 || d.cp == -1 ) && // Goal CP
locationAlreadyCheckedIn &&
skip_10s == false) {
//check for rogaining
if (isAtGoal.value == false && rogainingCounted.value) {
//goal
//print("---- in goal -----");
debugPrint("**5 ゴールで時計撮影の場合");
chekcs = 5; // Goal 時計撮影
isAtGoal.value = true;
photos.clear();
if (shouldShowBottomSheet) {
shouldShowBottomSheet = false;
if (d.cp == -1) return;
await showModalBottomSheet(
constraints:
BoxConstraints.loose(Size(Get.width, Get.height * 0.75)),
context: Get.context!,
isScrollControlled: true,
builder: ((context) => CameraPage(
destination: d,
))).whenComplete(() {
shouldShowBottomSheet = true;
skipGps = false;
chekcs = 0;
isAtGoal.value = false;
});
}
return;
} else if (isInRog.value == false &&
indexController.rogMode.value == 1 &&
DateTime.now().difference(lastGoalAt).inHours >= 24) {
//start
//print("---- in start -----");
debugPrint("**5 スタートの場合で最後のゴールから24時間経過している場合");
chekcs = 6; // start point
isAtStart.value = true;
if (shouldShowBottomSheet) {
shouldShowBottomSheet = false;
if (d.cp != -1 && d.cp != 0) return;
Widget bottomSheet = BottomSheetNew(destination: d);
await showModalBottomSheet(
constraints:
BoxConstraints.loose(Size(Get.width, Get.height * 0.75)),
context: Get.context!,
isScrollControlled: true,
builder: ((context) => bottomSheet)
).whenComplete(() {
shouldShowBottomSheet = true;
//print("----- finished start -------");
skipGps = false;
chekcs = 0;
isAtStart.value = false;
});
}
return;
}
}
//print("==== _chekcs $chekcs ====");
if (chekcs == 0) {
//debugPrint("いずれにも当てはまらないので、処理スキップ");
skipGps = false;
}
return;
}
// ロゲイニングをリセットする関数です。
// ゲームの状態をリセットし、データベースからデータを削除します。
//
Future<void> resetRogaining({bool isgoal = false}) async {
//print("----- resetting --------");
isInCheckin.value = false;
isInRog.value = false;
isAtStart.value = false;
isAtGoal.value = false;
isGpsSelected.value = true;
skipGps = false;
ready_for_goal = false;
_start = 0;
chekcs = 0;
rogainingCounted.value = false;
DatabaseHelper db = DatabaseHelper.instance;
if (isgoal == false) {
await db.deleteAllDestinations();
await db.deleteAllRogaining();
}
int? latgoal = await db.latestGoal();
if (latgoal != null) {
lastGoalAt = DateTime.fromMicrosecondsSinceEpoch(latgoal);
//print("===== last goal : $last_goal_at =====");
}
dbService.updateDatabase();
}
// すべての目的地を削除する関数です。
//
void deleteAllDestinations() {
DatabaseHelper db = DatabaseHelper.instance;
db.deleteAllDestinations().then((value) {
populateDestinations();
});
}
// カメラを開いて写真を撮影する関数です。
//
void openCamera(BuildContext context, Destination? destination) {
photos.clear();
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => CustomCameraView(
onImageCaptured: (imagePath) {
photos.add(File(imagePath));
},
destination: destination,
),
/*
builder: (_) => CameraCamera(
resolutionPreset: ResolutionPreset.medium,
onFile: (file) {
photos.add(file);
Navigator.pop(context);
//print("----image file is : $file----");
//setState(() {});
},
)
*/
),
);
}
// ルートポイントを取得する関数です。
//
void getRoutePoints() {
indexController.routePoints = [];
indexController.routePointLenght.value = 0;
DestinationService.getDestinationLine(destinations, matrix)?.then((value) {
indexController.routePoints = value;
indexController.routePointLenght.value =
indexController.routePoints.length;
});
}
// 指定された緯度と経度に対応する目的地を取得する関数です。
//
Future<Destination?> getDestinationForLatLong(double lat, double long) async {
for (final d in destinations) {
if (lat == d.lat && long == d.lon) {
return d;
}
}
return null;
}
// チェックインの呼び出しを行う関数です。
// 指定された目的地に対してチェックインの処理を行います。
//
Future<void> callforCheckin(Destination d) async {
bool autoCheckin = d.auto_checkin == 0 ? false : true;
print("---- f- checkin ${d.sub_loc_id} ----");
if (autoCheckin) {
if (!checkingIn) {
makeCheckin(d, true, "");
if (d.cp != -1 && d.cp != 0 && d.cp != -2) {
rogainingCounted.value = true;
}
}
} else {
//print("--- hidden loc ${d.hidden_location} ----");
// ask for checkin
//print("is rog ---- ${is_in_rog.value} ----");
if (d.hidden_location != null &&
d.hidden_location == 0 &&
isInRog.value == true &&
d.cp != -1 && d.cp != 0 && d.cp != -2) {
chekcs = 3;
photos.clear();
isInCheckin.value = true;
final result = await showModalBottomSheet(
constraints:
BoxConstraints.loose(Size(Get.width, Get.height * 0.75)),
context: Get.context!,
isScrollControlled: true,
builder: ((context) => CameraPage(
manulaCheckin: true,
destination: d,
)));
if (result ?? false) {
debugPrint("==> Checkin complete....");
if (d.buy_point != null && d.buy_point! > 0) {
skipGps = true;
photos.clear();
DatabaseHelper db = DatabaseHelper.instance;
List<Destination> ds =
await db.getDestinationByLatLon(d.lat!, d.lon!);
Destination? dss;
if (ds.isNotEmpty) {
dss = ds.first;
}
await showModalBottomSheet(
constraints:
BoxConstraints.loose(Size(Get.width, Get.height * 0.75)),
context: Get.context!,
isScrollControlled: true,
builder: ((context) =>
CameraPage(
buyPointPhoto: true,
destination: d,
dbDest: dss,
))).whenComplete(() {
skipGps = false;
rogainingCounted.value = true;
chekcs = 0;
isInCheckin.value = false;
//Get.back();
});
}
} else {
debugPrint("キャンセルされました");
Get.snackbar(
"キャンセルされました",
"チェックインしていません。必要ならもう一度チェックポイントをタップして下さい。",
backgroundColor: Colors.yellow,
colorText: Colors.black,
icon: const Icon(
Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
);
}
} else {
Get.snackbar(
"ロゲが始まっていません",
"ロゲ開始ボタンをタップして、ロゲイニングを始める必要があります",
backgroundColor: Colors.yellow,
colorText: Colors.black,
icon: const Icon(
Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
);
}
/*
await showModalBottomSheet(
constraints:
BoxConstraints.loose(Size(Get.width, Get.height * 0.75)),
context: Get.context!,
isScrollControlled: true,
builder: ((context) => CameraPage(
manulaCheckin: true,
destination: d,
))).whenComplete(() async {
if (d.buy_point != null && d.buy_point! > 0) {
skipGps = true;
photos.clear();
DatabaseHelper db = DatabaseHelper.instance;
List<Destination> ds =
await db.getDestinationByLatLon(d.lat!, d.lon!);
Destination? dss;
if (ds.isNotEmpty) {
dss = ds.first;
}
await showModalBottomSheet(
constraints:
BoxConstraints.loose(Size(Get.width, Get.height * 0.75)),
context: Get.context!,
isScrollControlled: true,
builder: ((context) => CameraPage(
buyPointPhoto: true,
destination: d,
dbDest: dss,
))).whenComplete(() {
skipGps = false;
rogainingCounted.value = true;
chekcs = 0;
isInCheckin.value = false;
//Get.back();
});
} else {
skipGps = false;
chekcs = 0;
isInCheckin.value = false;
}
});
} else {
Get.snackbar(
"ロゲが始まっていません",
"ロゲ開始ボタンをタップして、ロゲイニングを始める必要があります",
backgroundColor: Colors.yellow,
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,
);
}
*/
}
}
// GPSデータをデータベースに追加する関数です。
//
Future<void> addGPStoDB(double la, double ln, {isCheckin = 0}) async {
//debugPrint("in addGPStoDB ${indexController.currentUser}");
try {
GpsDatabaseHelper db = GpsDatabaseHelper.instance;
if(indexController.currentUser.length>0){
final team_name = indexController.currentUser[0]["user"]['team_name'];
final event_code = indexController.currentUser[0]["user"]["event_code"];
GpsData gps_data = GpsData(
id: 0,
team_name: team_name,
event_code: event_code,
lat: la,
lon: ln,
is_checkin: isCheckin,
created_at: DateTime.now().millisecondsSinceEpoch);
var res = await db.insertGps(gps_data);
//debugPrint("Saved GPS data into DB...:${gps_data}");
}
} catch (err) {
print("errr ready gps ${err}");
return;
}
}
// チェックインを確認する関数です。
// ゲームが開始されていない場合は、ゲームを開始します。
// 目的地のリストを走査し、現在位置がチェックイン半径内にある場合は、チェックインの処理を行います。
// GPSデータの送信を開始します。
//
// 2024-4-8 Akira : See 2809
// checkForCheckinメソッドの再帰呼び出しをunawaitedで囲んで、非同期処理の結果を待たずに先に進むようにしました。また、再帰呼び出しの前に一定時間待機するようにしました。
//
Future<void> checkForCheckin() async {
//print("--- Start of checkForCheckin function ---");
dbService.updateDatabase();
await Future.delayed(const Duration(milliseconds: 3000));
game_started = true;
try {
// ここで、エラー
if( indexController.locations.length>0 ) {
indexController.locations[0].features.forEach((fs) async {
GeoJSONMultiPoint mp = fs!.geometry as GeoJSONMultiPoint;
LatLng pt = LatLng(mp.coordinates[0][1], mp.coordinates[0][0]);
double latFs = pt.latitude;
double lonFs = pt.longitude;
var distanceFs = const Distance();
double distFs = distanceFs.as(LengthUnit.Meter, LatLng(latFs, lonFs),
LatLng(currentLat, currentLon));
Destination des = festuretoDestination(fs);
if (distFs <= des.checkin_radious!
&& skipGps == false
//&& des.isCheckedIn == false
&& des.cp!=0 && des.cp!=-1 && des.cp!=-2) {
await startTimerLocation(fs, distFs);
// Note: You cannot break out of forEach. If you need to stop processing, you might have to reconsider using forEach.
}
});
if (gps_push_started == false) {
unawaited(pushGPStoServer());
}
}
//print("--- 123 ---- $skip_gps----");
} catch (e) {
print("An error occurred: $e");
// await checkForCheckin();
} finally {
await Future.delayed(const Duration(seconds: 5)); // 一定時間待機してから再帰呼び出し
//print("--- End of checkForCheckin function, calling recursively ---");
unawaited( checkForCheckin() );
}
}
// GPSデータをサーバーにプッシュする関数です。
//
Future<void> pushGPStoServer() async {
// print(
// "^^^^^^^^ ${DateFormat('kk:mm:ss \n EEE d MMM').format(DateTime.now())}");
try {
gps_push_started = true;
ExternalService().pushGPS();
} catch (e) {
//print("An error occurred: $e");
//await pushGPStoServer();
} finally {
//print("--- End of pushGPStoServer function, calling recursively ---");
await Future.delayed(const Duration(seconds: 5 * 60));
await pushGPStoServer();
}
}
// ロゲイニングにデータを追加する関数です。
//
void addToRogaining(double lat, double lon, int destinationId) async {
DatabaseHelper db = DatabaseHelper.instance;
List<Destination> d = await db.getDestinationById(destinationId);
if (d.isEmpty) {
Destination df = festuretoDestination(indexController.currentFeature[0]);
//print("--- made checkin ${df.location_id} ----");
makeCheckin(df, true, "");
}
isInRog.value = true;
saveGameState();
}
// 買い物ポイントをキャンセルする関数です。
//
Future<void> cancelBuyPoint(Destination destination) async {
DatabaseHelper db = DatabaseHelper.instance;
await db.updateCancelBuyPoint(destination);
populateDestinations();
}
// 指定されたパスの画像をギャラリーに保存する関数です。
//
_saveImageFromPath(String imagePath) async {
try {
// Read the image file from the given path
File imageFile = File(imagePath);
Uint8List imageBytes = await imageFile.readAsBytes();
// Save the image to the gallery
final result = await ImageGallerySaver.saveImage(imageBytes);
//print("--- save result --- ${result}");
} catch(e, stackTrace){
print('エラーが発生しました: $e');
print('スタックトレース: $stackTrace');
}
}
Future<void> _saveImageToGallery(String imagePath) async {
final status = await PermissionController.checkStoragePermission();
if(!status){
await PermissionController.requestStoragePermission();
}
/*
final status = await Permission.storage.status;
if (!status.isGranted) {
final result = await Permission.storage.request();
if (!result.isGranted) {
// ユーザーがストレージの権限を拒否した場合の処理
showDialog(
context: Get.context!,
builder: (BuildContext context) {
return AlertDialog(
title: Text('ストレージの権限が必要です'),
content: Text(
'画像をギャラリーに保存するには、ストレージの権限が必要です。アプリの設定画面で権限を許可してください。'),
actions: [
TextButton(
child: Text('キャンセル'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text('設定'),
onPressed: () {
Navigator.of(context).pop();
openAppSettings(); // アプリの設定画面を開く
},
),
],
);
}
);
return;
}
}
*/
try {
final result = await ImageGallerySaver.saveFile(imagePath);
print('Image saved to gallery: $result');
} catch (e) {
print('Failed to save image to gallery: $e');
}
}
// 買い物ポイントを作成する関数です。 指定された目的地に対して買い物ポイントの処理を行います。
//
// 買い物ポイントの作成に失敗した場合のエラーハンドリングを追加することを検討してください。
//
Future<void> makeBuyPoint(Destination destination, String imageurl) async {
DatabaseHelper db = DatabaseHelper.instance;
await db.updateBuyPoint(destination, imageurl);
populateDestinations();
//await _saveImageFromPath(imageurl);
await _saveImageToGallery(imageurl);
if (indexController.currentUser.isNotEmpty) {
double cpNum = destination.cp!;
int userId = indexController.currentUser[0]["user"]["id"];
//print("--- Pressed -----");
String team = indexController.currentUser[0]["user"]['team_name'];
//print("--- _team : ${_team}-----");
String eventCode = indexController.currentUser[0]["user"]["event_code"];
//print("--- _event_code : ${_event_code}-----");
String token = indexController.currentUser[0]["token"];
//print("--- _token : ${_token}-----");
DateTime now = DateTime.now();
String formattedDate = DateFormat('yyyy-MM-dd HH:mm:ss').format(now);
//print("------ checkin event $eventCode ------");
ExternalService()
.makeCheckpoint(userId, token, formattedDate, team, cpNum.round(),
eventCode, imageurl)
.then((value) {
//print("------Ext service check point $value ------");
});
}
}
// チェックインを行う関数です。 指定された目的地に対してチェックインの処理を行います。
//
// 要検討:チェックインのリクエストが失敗した場合のエラーハンドリングを追加することをお勧めします。
//
Future<void> makeCheckin(
Destination destination, bool action, String imageurl) async {
try {
// print("~~~~ calling checkin function ~~~~");
// print(
// "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ressssss ${destination.sub_loc_id}@@@@@@@@@@@");
DatabaseHelper db = DatabaseHelper.instance;
List<Destination> ddd =
await db.getDestinationByLatLon(destination.lat!, destination.lon!);
if (ddd.isEmpty) {
destination.checkedin = true;
destination.checkin_image = imageurl;
await db.insertDestination(destination);
// print("~~~~ inserted into db ~~~~");
}
if (imageurl == null || imageurl.isEmpty) {
if (photos.isNotEmpty) {
// imageurlが空の場合は、destinationのcheckin_imageプロパティを使用する
debugPrint("photos = ${photos}");
imageurl = photos[0].path;
}
debugPrint("imageurl = ${imageurl}");
//await _saveImageFromPath(imageurl!);
}
if (imageurl.isNotEmpty) {
await _saveImageToGallery(imageurl);
}
populateDestinations();
/// post to NATNAT
if (indexController.currentUser.isNotEmpty) {
double cpNum = destination.cp!;
int userId = indexController.currentUser[0]["user"]["id"];
//print("--- Pressed -----");
String team = indexController.currentUser[0]["user"]['team_name'];
//print("--- _team : ${_team}-----");
String eventCode = indexController.currentUser[0]["user"]["event_code"];
//print("--- _event_code : ${_event_code}-----");
String token = indexController.currentUser[0]["token"];
//print("--- _token : ${_token}-----");
DateTime now = DateTime.now();
String formattedDate = DateFormat('yyyy-MM-dd HH:mm:ss').format(now);
await addGPStoDB(currentLat, currentLon, isCheckin: 1);
// print("------ checkin event $eventCode ------");
ExternalService()
.makeCheckpoint(
userId,
token,
formattedDate,
team,
cpNum.round(),
eventCode,
imageurl)
.then((value) {
// print("------Ext service check point $value ------");
});
}
// dbService.updateDatabase();
}catch(e, stacktrace){
print("エラー:${e}");
//print("stack : ${stacktrace}");
}finally{
dbService.updateDatabase();
}
}
// チェックインを削除する関数です。
//
Future<void> removeCheckin(int cp) {
dbService.updateDatabase();
return ExternalService().removeCheckin(cp);
}
// ゲームを開始する関数です。
//
Future<void> startGame() async {
debugPrint("------ starting game ------");
if (game_started == false) {
await checkForCheckin();
}
}
Timer? gpsCheckTimer; // 一定間隔でGPSデータの受信状態をチェックするタイマー
void startGPSCheckTimer() {
gpsCheckTimer = Timer.periodic(Duration(seconds: 5), (timer) {
checkGPSDataReceived();
});
}
// コントローラーの初期化時に呼び出されるライフサイクルメソッドです。
//
bool inError=false;
bool isRunningBackgroundGPS=false;
int activeEngineCount = 0;
@override
void onInit() async {
super.onInit();
/*
WidgetsBinding.instance.addPostFrameCallback((_) async {
await PermissionController.checkAndRequestPermissions();
});
*/
startGPSCheckTimer();
// MapControllerの初期化完了を待機するフラグを設定
WidgetsBinding.instance.addPostFrameCallback((_) {
//checkGPSDataReceived(); removed 2024-5-4
isMapControllerReady = true;
});
// 要検討:エラーメッセージを表示するなどの適切な処理を追加することを検討してください。
//
// locationController からデバイスの受け取るGPS情報を取得し、
// handleLocationUpdate を呼び出している。
//
locationController.locationMarkerPositionStream.listen(
(locationMarkerPosition) {
//if (locationMarkerPosition != null) {
handleLocationUpdate(locationMarkerPosition);
//}
}, onError: (err) {
if(inError==false){
inError = true;
debugPrint("Location Error: $err");
// エラーが発生した場合、locationMarkerPositionStreamControllerにエラーを追加します。
locationController.locationMarkerPositionStreamController.addError(err);
// ここにエラー発生時の処理を追加します。
if (err is LocationServiceDisabledException) {
// 位置情報サービスが無効になっている場合の処理
print('Location services are disabled');
Get.snackbar(
'エラー',
'位置情報サービスが無効になっています。設定画面から位置情報サービスを有効にして下さい。不明な場合にはエンジニアスタッフにお問い合わせください。',
backgroundColor: Colors.red,
colorText: Colors.white,
duration: Duration(seconds: 3),
);
inError = false;
} else if (err is PermissionDeniedException) {
// 位置情報の権限がない場合の処理
print('Location permissions are denied');
Get.snackbar(
'エラー',
'位置情報サービスが許可されていません。設定画面から岐阜ナビの位置情報サービスを許可して下さい。不明な場合にはエンジニアスタッフにお問い合わせください。',
backgroundColor: Colors.red,
colorText: Colors.white,
duration: Duration(seconds: 3),
);
inError = false;
} else {
// その他のエラーの場合の処理
print('Location Error: $err');
Get.snackbar(
'エラー',
'位置情報サービスに問題が発生しました。位置情報サービスを再起動していますので少しお待ちください。',
backgroundColor: Colors.red,
colorText: Colors.white,
duration: Duration(seconds: 3),
);
// GPSデータのListenを再開する処理を追加
if( isRunningBackgroundGPS==false && inError ) {
restartGPS();
}
}
}
//print("Location Error: $err");
});
startGame();
//checkGPSDataReceived();
}
void restartGPS(){
// GPSデータのListenを再開する処理を追加
Future.delayed(Duration(seconds: 5), () {
locationController.startPositionStream();
inError=false;
});
}
// コントローラーのクローズ時に呼び出されるライフサイクルメソッドです。
//
@override
void onClose() {
gpsCheckTimer?.cancel();
locationController.stopPositionStream();
super.onClose();
}
// 位置情報の更新を処理する関数です。
// 現在位置とスタート地点との距離を計算します。
// 現在位置と前回の位置情報との距離と時間差を確認し、一定の条件を満たす場合はGPSデータをデータベースに追加します。
//
// 要検討GPSデータの追加に失敗した場合のエラーハンドリングを追加することをお勧めします。
//
double prevLat = 0.0; // 直前の位置
double prevLon = 0.0;
bool gpsDebugMode=false;
void handleLocationUpdate(LocationMarkerPosition? position) async {
//debugPrint("DestinationController.handleLocationUpdate");
try {
//final DestinationController destinationController = Get.find<DestinationController>();
//final signalStrength = locationController.getGpsSignalStrength();
okToUseGPS = false;
if (position != null) {
currentLat = position.latitude;
currentLon = position.longitude;
if( prevLat==0.0 ){
prevLat = currentLat;
prevLon = currentLon;
}
lastValidGPSLocation = LatLng(currentLat, currentLon);
lastValidLat = currentLat;
lastValidLon = currentLon;
okToUseGPS = true;
lastGPSDataReceivedTime = DateTime.now();
hasReceivedGPSData = true;
} else {
debugPrint("....position is null....");
checkGPSDataReceived();
// 信号強度が低い場合、最後に取得した高いまたは中程度の位置情報を使用
// 但し、最初から高精度のものがない場合、どうするか?
//
// GPSデータが受信できない場合、最後に有効なGPSデータを使用
position = LocationMarkerPosition(
latitude: lastValidGPSLocation.latitude,
longitude: lastValidGPSLocation.longitude,
accuracy: 0,
);
currentLat = position.latitude;
currentLon = position.longitude;
okToUseGPS = false;
/*
if (lastValidLat != 0.0 && lastValidLon != 0.0) {
currentLat = lastValidLat;
currentLon = lastValidLon;
okToUseGPS = true;
} else {
// GPSの届く場所に行って、信号を拾ってください。とメッセージを出す。
position = null;
print("GPSの届く場所に行って、信号を拾ってください。");
Get.snackbar(
"GPS信号を正確に拾えていません",
"空が大きく見えるところへ行ってGPS信号を拾ってください。",
icon: const Icon(
Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
backgroundColor: Colors.yellow,
);
}
*/
}
if (okToUseGPS && position != null) {
// スタート位置から150m離れたら、ready_for_goal
if (distanceToStart() >= 150) {
ready_for_goal = true;
}
var distance = const Distance();
double distanceToDest = distance.as(
LengthUnit.Meter,
LatLng(position.latitude, position.longitude),
LatLng(prevLat, prevLon)
);
Duration difference = lastGPSCollectedTime.difference(DateTime.now())
.abs();
// 最後にGPS信号を取得した時刻から10秒以上経過、かつ10m以上経過普通に歩くスピード
//debugPrint("時間差:${difference.inSeconds}, 距離差:${distanceToDest}");
if (difference.inSeconds >= 10 || distanceToDest >= 30) {
// print(
// "^^^^^^^^ GPS data collected ${DateFormat('kk:mm:ss \n EEE d MMM').format(DateTime.now())}, ^^^ ${position.latitude}, ${position.longitude}");
LogManager().addLog(
"GPS : $currentLat, $currentLon - ${DateTime
.now()
.hour}:${DateTime
.now()
.minute}:${DateTime
.now()
.second}:${DateTime
.now()
.microsecond}");
if (isInRog.value) {
await addGPStoDB(position.latitude, position.longitude);
lastGPSCollectedTime = DateTime.now();
prevLat = position.latitude;
prevLon = position.longitude;
gpsDebugMode ? debugPrint("フロントエンドでのGPS保存(時間差:${difference.inSeconds}, 距離差:${distanceToDest}) : Time=${lastGPSCollectedTime}"):null;
}
}
}
} catch(e) {
debugPrint("handleLocationUpdate Error: ${e}");
} finally {
/* Akira , 2024-4-5
if (position != null &&
(position.latitude != 0 && position.longitude != 0)) {
currentLat = position.latitude;
currentLon = position.longitude;
}
*/
if (okToUseGPS) {
// 位置情報が取得できた場合、精度に関わらず最後の位置情報を更新
//currentLat = position.latitude;
//currentLon = position.longitude;
}
}
}
// スタート地点までの距離を計算する関数です。
//
double distanceToStart() {
if (indexController.locations.isEmpty) {
return 1000000000;
}
//print("=== gfs len == ${indexController.locations[0].collection.length}");
double distanceToDest = double.infinity;
if (indexController.locations[0].features.isEmpty) {
return distanceToDest;
}
GeoJSONFeature? gfs = indexController.locations[0].features.firstWhere(
(element) => festuretoDestination(element!).cp == -1,
orElse: () => null, // Provide a null value if no element is found
);
//print("gfs : ${gfs}");
if (gfs == null) {
return distanceToDest;
}
//final currentLocation = getCurrentLocation(); // GPS信号中以上での現在位置
Destination des = festuretoDestination(gfs);
//print("=== gfs == ${des.toMap()}");
var distance = const Distance();
distanceToDest = distance.as(LengthUnit.Meter,
LatLng(currentLat,currentLon), LatLng(des.lat!, des.lon!));
// LatLng(currentLat, currentLon), LatLng(des.lat!, des.lon!));
//print("==== dist==${distanceToDest}");
return distanceToDest;
}
// 強制チェックイン距離を取得する関数です。
//
int getForcedChckinDistance(Destination dest) {
if (dest.checkin_radious == -1) {
return 10000000000000000;
}
int _retValue = 100;
if (dest.cp == -1) {
return 500;
}
Destination? ds;
GeoJSONFeature? gfs = indexController.locations[0].features.firstWhere(
(element) => festuretoDestination(element!).cp == -1,
orElse: () => null, // Provide a null value if no element is found
);
if (gfs == null) {
return _retValue;
}
ds = festuretoDestination(gfs);
var distance = const Distance();
double distanceToDest = distance.as(LengthUnit.Meter,
LatLng(dest.lat!, dest.lon!), LatLng(ds.lat!, ds.lon!));
if (distanceToDest <= 500) {
return 500;
}
//print("==== forced dist ==${distanceToDest}");
return _retValue;
}
// ユーザートークンを読み取る関数です。
//
readUserToken() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
indexController.userToken = prefs.getString("user_token");
}
// コントローラーの準備完了時に呼び出されるライフサイクルメソッドです。
//
@override
void onReady() async {
await readUserToken();
final token = indexController.userToken;
if (token != null && token.isNotEmpty) {
await indexController.loadUserDetailsForToken(token);
fixMapBound(token);
}else {
Get.toNamed(AppPages.LOGIN)!.then((value) {
if (indexController.currentUser.isNotEmpty) {
final tk = indexController.currentUser[0]["token"];
fixMapBound(tk);
} else {
Get.toNamed(AppPages.TRAVEL);
PerfectureService.getSubExt("9").then((value) {
if (value != null) {
LatLngBounds bnds = LatLngBounds(
LatLng(value[1], value[0]), LatLng(value[3], value[2]));
indexController.mapController
.fitBounds(bnds); //.centerZoomFitBounds(bnds);
}
});
}
});
}
// 地図のイベントリスナーを設定
indexController.mapController.mapEventStream.listen((MapEvent mapEvent) {
if (mapEvent is MapEventMoveEnd) {
indexController.loadLocationsBound();
}
});
super.onReady();
}
// 地図の境界を修正する関数です。
//
void fixMapBound(String token) {
//String _token = indexController.currentUser[0]["token"];
indexController.switchPage(AppPages.INDEX);
if (isMapControllerReady) {
LocationService.getLocationsExt(token).then((value) {
if (value != null) {
//print("--- loc ext is - $value ----");
LatLngBounds bnds = LatLngBounds(
LatLng(value[1], value[0]), LatLng(value[3], value[2]));
//print("--- bnds is - $bnds ----");
indexController.mapController.fitBounds(
bnds,
);
indexController.currentBound.clear();
indexController.currentBound.add(bnds);
indexController.loadLocationsBound();
centerMapToCurrentLocation();
}
});
} else {
// MapControllerの初期化が完了していない場合は、遅延して再試行
Future.delayed(Duration(milliseconds: 100), () {
fixMapBound(token);
});
}
}
/*
void fixMapBound(String token) {
indexController.switchPage(AppPages.INDEX);
LocationService.getLocationsExt(token).then((value) {
if (value != null) {
LatLngBounds bnds = LatLngBounds(
LatLng(value[1], value[0]),
LatLng(value[3], value[2]),
);
if (indexController.isMapControllerReady.value) {
indexController.mapController.fitBounds(
bnds,
);
indexController.currentBound.clear();
indexController.currentBound.add(bnds);
indexController.loadLocationsBound();
centerMapToCurrentLocation();
} else {
// MapControllerが初期化されるまで待機し、その後fitBoundsを実行
WidgetsBinding.instance.addPostFrameCallback((_) {
indexController.mapController.fitBounds(
bnds,
);
indexController.currentBound.clear();
indexController.currentBound.add(bnds);
indexController.loadLocationsBound();
centerMapToCurrentLocation();
});
}
}
});
}
*/
// 地図を現在位置に中央揃えする関数です。
//
void centerMapToCurrentLocation() {
//print("center is ${currentLat}, ${currentLon}");
// Akira ... 状況によって呼ぶか呼ばないか
if (currentLat != 0 || currentLon != 0) {
indexController.mapController.move(LatLng(currentLat, currentLon), 17.0);
}
}
// 接続状態が変更されたときに呼び出される関数です。
//
void connectionChanged(String val) {
//print('----- %%%%%%%%%%%%%%%%%%%%% ----- $val');
Map<String, dynamic> res = {};
if (val == "wifi" || val == "mobile") {
String token = indexController.currentUser[0]["token"];
DatabaseHelper db = DatabaseHelper.instance;
db.allRogianing().then((value) {
value.forEach((e) async {
if (e.rog_action_type == 0) {
res = await ExternalService().startRogaining();
} else if (e.rog_action_type == 1) {
var datetime = DateTime.fromMicrosecondsSinceEpoch(e.checkintime!);
res = await ExternalService().makeCheckpoint(
e.user_id!,
token,
getFormatedTime(datetime),
e.team_name!,
e.cp_number!,
e.event_code!,
e.image!);
} else if (e.rog_action_type == 2) {
var datetime = DateTime.fromMicrosecondsSinceEpoch(e.checkintime!);
res = await ExternalService().makeGoal(
e.user_id!,
token,
e.team_name!,
e.image!,
getFormatedTime(datetime),
e.event_code!);
}
if (res.isNotEmpty) {
db.deleteRogaining(e.id!);
}
});
});
}
}
/*
// 位置情報の許可を確認する関数です。
//
void checkPermission() async {
LocationPermission permission = await Geolocator.checkPermission();
if (permission != LocationPermission.whileInUse ||
permission != LocationPermission.always) {
locationPermission.clear();
locationPermission.add(permission.name);
permission = await Geolocator.requestPermission();
}
}
*/
// IDに基づいて目的地を取得する関数です。
//
Destination? destinationById(int id) {
Destination? d;
//print("--- target des - $id ----");
for (Destination ss in destinations) {
//print("--- des - ${ss.location_id} ----");
if (ss.location_id == id) {
d = ss;
break;
}
}
return d;
}
// 目的地を削除する関数です。
//
void deleteDestination(Destination d) {
//int id = destinations[index].location_id!;
//print("---- index ${destinations[index].location_id!}-----");
for (Destination ss in destinations) {
if (ss.location_id == d.location_id) {
destinations.remove(ss);
break;
}
}
DatabaseHelper db = DatabaseHelper.instance;
db.deleteDestination(d.location_id!).then((value) {
populateDestinations();
});
dbService.updateDatabase();
}
// データベースからすべての目的地を削除する関数です。
//
void deleteDBDestinations() {
DatabaseHelper db = DatabaseHelper.instance;
db.deleteAllDestinations().then((value) {
populateDestinations();
});
dbService.updateDatabase();
}
// ---------- database ------------------///
// 目的地を追加する関数です。
//
void addDestinations(Destination dest) {
DatabaseHelper db = DatabaseHelper.instance;
db.getDestinationByLatLon(dest.lat!, dest.lon!).then((value) {
if (value.isNotEmpty) {
db.deleteDestination(value[0].location_id!).then((value) {
db.insertDestination(dest).then((value) {
//print(
// "----- destination controller deleted and inserted destination id $value ---- :::::");
populateDestinations();
});
});
} else {
db.insertDestination(dest).then((value) {
//print("----- destination controller added as new $value--- :::::");
populateDestinations();
});
}
});
dbService.updateDatabase();
}
// 目的地の選択状態を切り替える関数です。
//
void toggleSelection(Destination dest) async {
try {
DatabaseHelper db = DatabaseHelper.instance;
await db.toggleSelecttion(dest);
destinations.clear();
db.getDestinations().then((value) {
destinationCount.value = 0;
currentSelectedDestinations.clear();
for (Destination d in value) {
//print("------ destination controller populating destination-------- ${d.checkedin}-------- :::::");
//print("-----populated----- ${d.toMap()}");
if (d.selected!) {
currentSelectedDestinations.add(d);
}
destinations.add(d);
}
destinationCount.value = destinations.length;
});
} catch( e ){
print('Error in toggleSelection: $e');
Get.snackbar(
"画面切り替えでエラー",
"画面の切り替えができませんでした",
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,
);
}
}
// ダイアログを表示する関数です。
//
buildShowDialog(BuildContext context) {
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return const Center(
child: CircularProgressIndicator(),
);
});
}
// 現在地点からの目的地の行列を計算する関数です。
//
void destinationMatrixFromCurrentPoint(List<Destination> points) {
//buildShowDialog(Get.context!);
MatrixService.getDestinations(points).then((mat) {
//print(" matrix is ------- $mat");
matrix = mat;
try {
indexController.routePoints = [];
indexController.routePointLenght.value = 0;
DestinationService.getDestinationLine(points, matrix)?.then((value) {
indexController.routePoints = value;
indexController.routePointLenght.value =
indexController.routePoints.length;
//Get.toNamed(AppPages.TRAVEL);
});
destinationCount.value = destinations.length;
} catch (_) {
skipGps = false;
return;
} finally {
//Get.back();
isRouteShowing.value = true;
}
});
}
// 目的地のリストを取得してObservable変数を更新する関数です。
//
void populateDestinations() {
DatabaseHelper db = DatabaseHelper.instance;
destinations.clear();
db.getDestinations().then((value) {
destinationCount.value = 0;
for (Destination d in value) {
destinations.add(d);
}
if (destinations.isEmpty) {
rogainingCounted.value = false;
}
});
}
// 目的地の順序を変更する関数です。
//
void makeOrder(Destination d, int dir) {
DatabaseHelper db = DatabaseHelper.instance;
db.updateOrder(d, dir).then((value) {
populateDestinations();
});
}
}
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebViewPage extends StatelessWidget {
final String url;
const WebViewPage({Key? key, required this.url}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('WebView'),
),
body: WebView(
initialUrl: url,
javascriptMode: JavascriptMode.unrestricted,
),
);
}
}import 'package:geojson_vi/geojson_vi.dart';
import 'package:get/get.dart';
import 'package:rogapp/pages/index/index_controller.dart';
class SearchBarController extends GetxController {
List<GeoJSONFeature> searchResults = <GeoJSONFeature>[].obs;
@override
void onInit() {
IndexController indexController = Get.find<IndexController>();
if (indexController.locations.isNotEmpty) {
for (int i = 0;
i <= indexController.locations[0].features.length - 1;
i++) {
GeoJSONFeature p = indexController.locations[0].features[i]!;
searchResults.add(p);
}
}
super.onInit();
}
}
import 'package:get/get.dart';
import 'package:rogapp/pages/search/search_controller.dart';
class SearchBinding extends Bindings {
@override
void dependencies() {
Get.put<SearchBarController>(SearchBarController());
}
}
import 'package:flutter/material.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:geojson_vi/geojson_vi.dart';
import 'package:get/get.dart';
import 'package:rogapp/model/destination.dart';
import 'package:rogapp/pages/destination/destination_controller.dart';
import 'package:rogapp/pages/index/index_controller.dart';
import 'package:rogapp/pages/search/search_controller.dart';
//import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_start.dart';
//import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_goal.dart';
//import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_normal_point.dart';
import 'package:rogapp/widgets/bottom_sheet_new.dart';
class SearchPage extends StatelessWidget {
SearchPage({Key? key}) : super(key: key);
SearchBarController searchController = Get.find<SearchBarController>();
IndexController indexController = Get.find<IndexController>();
Image getImage(int index) {
if (searchController.searchResults[index].properties!["photos"] == null ||
searchController.searchResults[index].properties!["photos"] == "") {
return const Image(image: AssetImage('assets/images/empty_image.png'));
} else {
return Image(
image: NetworkImage(
searchController.searchResults[index].properties!["photos"]),
errorBuilder:
(BuildContext context, Object exception, StackTrace? stackTrace) {
return Image.asset("assets/images/empty_image.png");
},
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.white,
leading: IconButton(
onPressed: () {
Get.back();
},
icon: const Icon(
Icons.arrow_back_ios_new,
color: Colors.black,
)),
centerTitle: true,
//title: const CupertinoSearchTextField(),
),
body: SingleChildScrollView(
child: TypeAheadField<GeoJSONFeature>(
// textFieldConfiguration: TextFieldConfiguration(
// autofocus: true,
// style: DefaultTextStyle.of(context).style.copyWith(
// fontStyle: FontStyle.normal,
// fontSize: 15.0,
// ),
// decoration: InputDecoration(
// border: const OutlineInputBorder(),
// hintText: "検索",
// prefixIcon: const Icon(Icons.search),
// suffixIcon: IconButton(
// icon: const Icon(Icons.clear),
// onPressed: () {
// // clear the text field
// },
// ),
// ),
// ),
onSelected: (GeoJSONFeature suggestion) {
indexController.currentFeature.clear();
indexController.currentFeature.add(suggestion);
DestinationController destinationController =
Get.find<DestinationController>();
Destination des =
destinationController.festuretoDestination(suggestion);
Get.back();
Widget bottomSheet = BottomSheetNew(destination: des);
/*
if (des.cp == -1 || des.cp == 0) {
bottomSheet = BottomSheetStart(destination: des);
} else if (des.cp == -2 || des.cp == 0) {
bottomSheet = BottomSheetGoal(destination: des);
} else {
bottomSheet = BottomSheetNormalPoint(destination: des);
}
*/
showModalBottomSheet(
constraints:
BoxConstraints.loose(Size(Get.width, Get.height * 0.75)),
isScrollControlled: true,
context: context,
builder: ((context) => bottomSheet)
);
},
suggestionsCallback: (pattern) async {
return searchController.searchResults
.where((GeoJSONFeature element) => element
.properties!["location_name"]
.toString()
.contains(pattern))
.toList();
//return await
},
itemBuilder: (context, GeoJSONFeature suggestion) {
return ListTile(
title: Text(suggestion.properties!["location_name"]),
subtitle: suggestion.properties!["category"] != null
? Text(suggestion.properties!["category"])
: const Text(""),
//leading: getImage(index),
);
},
),
),
);
}
}
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';
class RegisterPage extends StatelessWidget {
final IndexController indexController = Get.find<IndexController>();
TextEditingController emailController = TextEditingController();
TextEditingController passwordController = TextEditingController();
TextEditingController confirmPasswordController = TextEditingController();
RegisterPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
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,
)),
),
body: SafeArea(
child: SingleChildScrollView(
child: SizedBox(
height: MediaQuery.of(context).size.height,
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
children: [
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
"sign_up".tr, // "サインアップ"
style: const TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
const SizedBox(
height: 20,
),
Text(
"create_account".tr, // アカウントを無料で作成します。
style: TextStyle(
fontSize: 15,
color: Colors.grey[700],
),
),
const SizedBox(
height: 30,
)
],
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 40),
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".tr,
"password_does_not_match".tr,
backgroundColor: Colors.red,
colorText: Colors.white,
icon: const Icon(Icons.assistant_photo_outlined,
size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
// backgroundColor: Colors.yellow,
//icon:Image(image:AssetImage("assets/images/dora.png"))
);
}
if (emailController.text.isEmpty ||
passwordController.text.isEmpty) {
Get.snackbar(
"no_values".tr,
"email_and_password_required".tr,
backgroundColor: Colors.red,
colorText: Colors.white,
icon: const Icon(Icons.assistant_photo_outlined,
size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
//backgroundColor: Colors.yellow,
//icon:Image(image:AssetImage("assets/images/dora.png"))
);
return;
}
indexController.isLoading.value = true;
indexController.register(emailController.text,
passwordController.text, context);
},
color: Colors.redAccent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(40)),
child: Text(
"sign_up".tr,
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),
),
),
],
)
],
),
],
),
),
),
),
);
}
}
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,
)
],
);
}
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());
}
}// 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 GetView<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: FutureBuilder(
future: Get.putAsync(() => ApiService().init()),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Obx(() => 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} - ${team.zekkenNumber}'),
//trailing: Text('メンバー: ${team.memberCount}'),
trailing: Text('メンバー: (メンバー数表示予定)'),
onTap: () => Get.toNamed(AppPages.TEAM_DETAIL, arguments: {'mode': 'edit', 'team': team}),
);
},
));
} else {
return Center(child: CircularProgressIndicator());
}
},
),
);
}
}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());
}
}// lib/controllers/team_controller.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 = <Category>[].obs;
final teamMembers = <User>[].obs;
final selectedCategory = Rx<Category?>(null);
final currentUser = Rx<User?>(null);
final isLoading = true.obs;
final error = RxString('');
@override
void onInit() async{
super.onInit();
try {
//await Get.putAsync(() => ApiService().init());
_apiService = Get.find<ApiService>();
await Future.wait([
fetchTeams(),
fetchCategories(),
getCurrentUser(),
]);
}catch(e){
print("Team Controller error: $e");
}finally{
isLoading.value = false;
}
}
Future<void> fetchTeams() async {
try {
final fetchedTeams = await _apiService.getTeams();
teams.assignAll(fetchedTeams);
} catch (e) {
print('Error fetching teams: $e');
}
}
Future<void> fetchCategories() async {
try {
final fetchedCategories = await _apiService.getCategories();
categories.assignAll(fetchedCategories);
} 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<void> createTeam(String teamName, int categoryId) async {
try {
final newTeam = await _apiService.createTeam(teamName, categoryId);
teams.add(newTeam);
} catch (e) {
print('Error creating team: $e');
}
}
Future<void> updateTeam(int teamId, String teamName, int categoryId) async {
try {
final updatedTeam = await _apiService.updateTeam(teamId, teamName, categoryId);
final index = teams.indexWhere((team) => team.id == teamId);
if (index != -1) {
teams[index] = updatedTeam;
}
} catch (e) {
print('Error updating team: $e');
}
}
Future<void> deleteTeam(int teamId) async {
try {
await _apiService.deleteTeam(teamId);
teams.removeWhere((team) => team.id == teamId);
} catch (e) {
print('Error deleting team: $e');
}
}
Future<void> fetchTeamMembers(int teamId) async {
try {
final members = await _apiService.getTeamMembers(teamId);
teamMembers.assignAll(members);
} catch (e) {
print('Error fetching team members: $e');
}
}
void updateTeamName(String value) {
// Update local state
}
void updateCategory(Category? value) {
selectedCategory.value = value;
}
}// 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/services/api_service.dart';
class TeamDetailPage extends GetView<TeamController> {
@override
Widget build(BuildContext context) {
final mode = Get.arguments['mode'];
final team = Get.arguments['team'];
return Scaffold(
appBar: AppBar(
title: Text(mode == 'new' ? '新規チーム作成' : 'チーム詳細'),
actions: [
if (mode == 'edit')
IconButton(
icon: Icon(Icons.add),
onPressed: () => Get.toNamed(AppPages.MEMBER_DETAIL, arguments: {'mode': 'new', 'teamId': team.id}),
),
],
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
decoration: InputDecoration(labelText: 'チーム名'),
onChanged: (value) => controller.updateTeamName(value),
),
DropdownButtonFormField(
decoration: InputDecoration(labelText: 'カテゴリ'),
value: controller.selectedCategory.value,
items: controller.categories.map((category) => DropdownMenuItem(
value: category,
child: Text(category.categoryName),
)).toList(),
onChanged: (value) => controller.updateCategory(value),
),
if (mode == 'edit')
Text('ゼッケン番号: ${team.zekkenNumber}'),
Obx(() {
final currentUser = controller.currentUser.value;
return Text('所有者: ${currentUser?.email ?? "未設定"}');
}),
SizedBox(height: 20),
Text('メンバーリスト', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
Expanded(
child: Obx(() => ListView.builder(
itemCount: controller.teamMembers.length,
itemBuilder: (context, index) {
final member = controller.teamMembers[index];
return ListTile(
title: Text('${member.lastname}, ${member.firstname}'),
onTap: () => Get.toNamed(AppPages.MEMBER_DETAIL, arguments: {'mode': 'edit', 'member': member}),
);
},
)),
),
],
),
),
);
}
}// 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';
class MemberDetailPage extends GetView<MemberController> {
@override
Widget build(BuildContext context) {
final mode = Get.arguments['mode'];
final member = Get.arguments['member'];
return Scaffold(
appBar: AppBar(
title: Text(mode == 'new' ? 'メンバー追加' : 'メンバー詳細'),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (mode == 'edit' && member.email != null)
Text('Email: ${member.email}'),
if (mode == 'new' || (mode == 'edit' && member.email == null))
TextField(
decoration: InputDecoration(labelText: 'Email'),
onChanged: (value) => controller.updateEmail(value),
),
TextField(
decoration: InputDecoration(labelText: '姓'),
onChanged: (value) => controller.updateLastname(value),
),
TextField(
decoration: InputDecoration(labelText: '名'),
onChanged: (value) => controller.updateFirstname(value),
),
// 誕生日選択ウィジェットを追加
if (mode == 'edit')
Text('ステータス: ${controller.getMemberStatus()}'),
if (mode == 'edit' && controller.getMemberStatus() == '招待中')
ElevatedButton(
child: Text('招待メールを再送信'),
onPressed: () => controller.resendInvitation(),
),
if (mode == 'edit')
ElevatedButton(
child: Text('メンバーから除外'),
onPressed: () => controller.deleteMember(),
),
],
),
),
);
}
}// lib/controllers/member_controller.dart
import 'package:get/get.dart';
import 'package:rogapp/model/user.dart';
import 'package:rogapp/services/api_service.dart';
class MemberController extends GetxController {
late final ApiService _apiService;
final int teamId = 0;
final member = Rx<User?>(null);
final email = ''.obs;
final firstname = ''.obs;
final lastname = ''.obs;
final dateOfBirth = Rx<DateTime?>(null);
@override
void onInit() async{
super.onInit();
await Get.putAsync(() => ApiService().init());
_apiService = Get.find<ApiService>();
if (Get.arguments != null && Get.arguments['member'] != null) {
member.value = Get.arguments['member'];
_initializeMemberData();
}
}
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;
}
}
Future<void> createMember(int teamId) async {
try {
final newMember = await _apiService.createTeamMember(
teamId,
email.value,
firstname.value,
lastname.value,
dateOfBirth.value,
);
member.value = newMember;
} catch (e) {
print('Error creating member: $e');
}
}
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,
);
member.value = updatedMember;
} catch (e) {
print('Error updating member: $e');
}
}
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 (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 '招待中';
}
}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();
TextEditingController newPasswordController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: Colors.white,
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.white,
leading: IconButton(
onPressed: () {
logManager.addOperationLog('User clicked cancel button on the drawer');
Navigator.pop(context);
},
icon: const Icon(
Icons.arrow_back_ios,
size: 20,
color: Colors.black,
)),
),
body: SizedBox(
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Column(
children: [
Column(
children: [
Text(
"change_password".tr,
style: const TextStyle(fontSize: 24.0),
),
const SizedBox(
height: 30,
),
],
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 40),
child: Column(
children: [
makeInput(
label: "old_password".tr,
controller: oldPasswordController),
makeInput(
label: "new_password".tr,
controller: newPasswordController,
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),
),
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: 60,
onPressed: () {
if (oldPasswordController
.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,
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.changePassword(
oldPasswordController.text,
newPasswordController.text,
context);
//indexController.login(oldPasswordController.text, newPasswordController.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: 10.0,
),
],
)),
),
)),
const SizedBox(
height: 20,
),
const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [],
)
],
),
],
),
));
}
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,
)
],
);
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rogapp/pages/index/index_controller.dart';
class SubPerfPage extends StatelessWidget {
SubPerfPage({Key? key}) : super(key: key);
IndexController indexController = Get.find<IndexController>();
@override
Widget build(BuildContext context) {
debugPrint("SubPerfPage ---->");
return Scaffold(
appBar: AppBar(
title: const Text("Select Sub Perfecture"),
),
body: ListView.builder(
itemCount: indexController.subPerfs.length,
itemBuilder: (context, index){
return ListTile(
onTap: (){
indexController.dropdownValue = indexController.perfectures[index][0]["id"].toString();
//indexController.populateForPerf(indexController.dropdownValue, indexController.mapController!);
Get.back();
},
title: Text(indexController.perfectures[index][0]["adm1_ja"].toString()),
);
},
),
);
}
}import 'dart:io';
import 'package:flutter/material.dart';
import 'package:rogapp/model/destination.dart';
import 'package:rogapp/utils/database_helper.dart';
import 'package:get/get.dart';
class HistoryPage extends StatefulWidget {
const HistoryPage({super.key});
@override
State<HistoryPage> createState() => _HistoryPageState();
}
class _HistoryPageState extends State<HistoryPage> {
DatabaseHelper db = DatabaseHelper.instance;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("pass_history".tr),
),
body: SingleChildScrollView(
child: Column(
children: [
FutureBuilder(
// 要検討:スナップショットのエラーハンドリングが行われていますが、具体的なエラーメッセージを表示するようにすることをお勧めします。
//
future: db.getDestinations(),
builder: (BuildContext context,
AsyncSnapshot<List<Destination>> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return Center(
child: Text(
'${snapshot.error} occurred',
style: const TextStyle(fontSize: 18),
),
);
} else if (snapshot.hasData) {
final dests = snapshot.data;
if (dests!.isNotEmpty) {
debugPrint("----- 通過履歴表示 -----");
return SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: ListView.builder(
itemCount: dests.length,
itemBuilder: (ctx, index) {
//print("--- photo ${dests[index].checkin_image!} ----");
return Padding(
padding: const EdgeInsets.all(8.0),
child: CustomWidget(
// 要検討:画像のサイズがハードコードされています。画像のサイズを動的に設定できるようにすることを検討してください。
title: dests[index].name!,
subtitle:
"${dests[index].sub_loc_id} : ${dests[index].name}",
image1: dests[index].checkin_image != null
? Image.file(
File(dests[index].checkin_image!))
: null,
image2:
dests[index].buypoint_image != null
? Image.file(File(
dests[index].buypoint_image!))
: null,
),
);
}));
} else {
return Center(child: Text("no_checkin_yet".tr));
}
}
} else if (snapshot.connectionState ==
ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
return Container();
}),
],
),
),
);
}
}
class CustomWidget extends StatelessWidget {
final Image? image1;
final Image? image2;
final String title;
final String subtitle;
const CustomWidget({
super.key,
this.image1,
this.image2,
required this.title,
required this.subtitle,
});
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width:
104, // 50 (width of each image) + 2 (space between images) + 2*1 (padding on both sides)
child: Row(
children: [
if (image1 != null)
SizedBox(
width: 50,
height: 100,
child: image1,
),
if (image1 != null && image2 != null) const SizedBox(width: 2),
if (image2 != null)
SizedBox(
width: 50,
height: 100,
child: image2,
),
],
),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style:
const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
maxLines:
null, // Allows the text to wrap onto an unlimited number of lines
),
Text(
subtitle,
style: const TextStyle(fontSize: 16),
maxLines:
null, // Allows the text to wrap onto an unlimited number of lines
),
],
),
),
],
);
}
}
// import 'package:flutter/material.dart';
// import 'package:get/get.dart';
// import 'package:rogapp/pages/index/index_controller.dart';
// class MainPerfPage extends StatelessWidget {
// MainPerfPage({Key? key}) : super(key: key);
// IndexController indexController = Get.find<IndexController>();
// @override
// Widget build(BuildContext context) {
// return Scaffold(
// appBar: AppBar(
// title: const Text("Select Main Perfecture"),
// ),
// body: ListView.builder(
// itemCount: indexController.perfectures.length,
// itemBuilder: (context, index){
// return ListTile(
// onTap: (){
// indexController.dropdownValue = indexController.perfectures[index][0]["id"].toString();
// indexController.populateForPerf(indexController.dropdownValue, indexController.mapController);
// Get.back();
// },
// title: Text(indexController.perfectures[index][0]["adm1_ja"].toString()),
// );
// },
// ),
// );
// }
// }import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rogapp/pages/destination/destination_controller.dart';
import 'package:rogapp/pages/drawer/drawer_page.dart';
import 'package:rogapp/pages/index/index_controller.dart';
import 'package:rogapp/routes/app_pages.dart';
import 'package:rogapp/widgets/list_widget.dart';
import 'package:rogapp/widgets/map_widget.dart';
import 'package:rogapp/utils/location_controller.dart';
// index_page.dartファイルの主な内容です。
// このファイルは、アプリのメインページのUIを構築し、各機能へのナビゲーションを提供しています。
// また、IndexControllerとDestinationControllerを使用して、状態管理と各種機能の実装を行っています。
//
// MapWidgetとListWidgetは、それぞれ別のファイルで定義されているウィジェットであり、マップモードとリストモードの表示を担当しています。
//
// 全体的に、index_page.dartはアプリのメインページの構造を定義し、他のコンポーネントやページへの橋渡しを行っているファイルです。
//
// 要検討GPSデータの表示アイコンをタップした際のエラーハンドリングを追加することをお勧めします。
// MapWidgetとListWidgetの切り替えにObxを使用していますが、パフォーマンスを考慮して、必要な場合にのみウィジェットを再構築するようにしてください。
// DestinationControllerのisSimulationModeを使用してGPS信号の強弱をシミュレーションしていますが、本番環境では適切に実際のGPS信号を使用するようにしてください。
// IndexPageクラスは、GetView<IndexController>を継承したStatelessWidgetです。このクラスは、アプリのメインページを表すウィジェットです。
//
class IndexPage extends GetView<IndexController> {
IndexPage({Key? key}) : super(key: key);
// IndexControllerとDestinationControllerのインスタンスを取得しています。
//
final LocationController locationController = Get.find<LocationController>();
final IndexController indexController = Get.find<IndexController>();
final DestinationController destinationController =
Get.find<DestinationController>();
// buildメソッドは、ウィジェットのUIを構築するメソッドです。
// ここでは、WillPopScopeウィジェットを使用して、端末の戻るボタンが押された際の動作を制御しています。
//
@override
Widget build(BuildContext context) {
return PopScope(
canPop: false,
child: Scaffold(
//
// Scaffoldウィジェットを使用して、アプリのメインページのレイアウトを構築しています。
//
drawer: DrawerPage(),
appBar: AppBar(
title: Text("add_location".tr),
actions: [
// IconButton(
// onPressed: () {
// DatabaseService ds = DatabaseService();
// ds.updateDatabase();
// },
// icon: const Icon(Icons.ten_k_sharp)),
//
// AppBarには、タイトルとアクションアイコンが含まれています。
// アクションアイコンには、GPSデータの表示、履歴の表示、マップの更新、検索などの機能が含まれています。
//
IconButton(
onPressed: () async {
// GpsDatabaseHelper db = GpsDatabaseHelper.instance;
// List<GpsData> data = await db.getGPSData(
// indexController.currentUser[0]["user"]['team_name'],
// indexController.currentUser[0]["user"]["event_code"]);
// print("GPS data is ${data.length}");
Get.toNamed(AppPages.GPS);
},
icon: const Icon(Icons.telegram)),
IconButton(
onPressed: () {
Get.toNamed(AppPages.HISTORY);
},
icon: const Icon(Icons.history)),
IconButton(
onPressed: () {
final tk = indexController.currentUser[0]["token"];
if (tk != null) {
destinationController.fixMapBound(tk);
}
},
icon: const Icon(Icons.refresh)),
InkWell(
onTap: () {
Get.toNamed(AppPages.SEARCH);
},
child: Container(
height: 32,
width: 75,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(25),
),
child: const Center(
child: Icon(Icons.search),
),
),
),
//CatWidget(indexController: indexController,),
//
// デバッグ時のみリロードボタンの横にGPS信号レベルの設定ボタンを設置し、
// タップすることでGPS信号の強弱をシミュレーションできるようにする
// Akira 2024-4-5
//
/*
Obx(() {
if (locationController.isSimulationMode) {
return DropdownButton<String>(
value: locationController.getSimulatedSignalStrength(),
onChanged: (value) {
//debugPrint("DropDown changed!");
locationController.setSimulatedSignalStrength(value!);
},
items: ['low', 'medium', 'high', 'real']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
} else {
return Container();
}
}),
*/
],
),
// bottomNavigationBar: BottomAppBar(
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: <Widget>[
// Obx(
// () => destinationController.isInRog.value == true
// ? IconButton(
// onPressed: () {},
// icon: const Icon(
// Icons.run_circle,
// size: 44,
// color: Colors.green,
// ))
// : IconButton(
// onPressed: () {},
// icon: const Icon(
// Icons.run_circle,
// size: 44,
// color: Colors.black12,
// )),
// ),
// Padding(
// padding:
// const EdgeInsets.only(right: 10.0, top: 4.0, bottom: 4.0),
// child: InkWell(
// child: Obx(() => destinationController
// .isGpsSelected.value ==
// true
// ? Padding(
// padding: const EdgeInsets.only(
// right: 10.0, top: 4.0, bottom: 4.0),
// child: InkWell(
// child: const Image(
// image:
// AssetImage('assets/images/route3_off.png'),
// width: 35,
// height: 35,
// ),
// onTap: () {
// //indexController.switchPage(AppPages.TRAVEL);
// },
// ),
// )
// : Padding(
// padding: const EdgeInsets.only(
// right: 10.0, top: 4.0, bottom: 4.0),
// child: InkWell(
// child: const Image(
// image:
// AssetImage('assets/images/route2_on.png'),
// width: 35,
// height: 35,
// ),
// onTap: () {
// //indexController.switchPage(AppPages.TRAVEL);
// },
// ),
// ))),
// ),
// ],
// ),
// ),
//
// マップモードとリストモードを切り替えるためのボタンです。
//
floatingActionButton: FloatingActionButton(
onPressed: () {
indexController.toggleMode();
},
elevation: 1.0,
//
// Obxウィジェットを使用して、indexController.mode.valueの値に基づいて、MapWidgetまたはListWidgetを表示しています。
//
child: Obx(
() => indexController.mode.value == 0
? const Image(image: AssetImage('assets/images/list2.png'))
: const Image(image: AssetImage('assets/images/map.png')),
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
//
// bodyには、SafeAreaウィジェットを使用して、画面の安全な領域内にUIを構築しています。
//
body: SafeArea(
child: Column(
children: [
Expanded(
child: Obx(
() => indexController.mode.value == 0
? MapWidget()
: const ListWidget(),
))
],
),
),
),
);
}
}
import 'package:get/get.dart';
import 'package:rogapp/pages/destination/destination_controller.dart';
import 'package:rogapp/pages/index/index_controller.dart';
import 'package:rogapp/utils/location_controller.dart';
class IndexBinding extends Bindings {
@override
void dependencies() {
Get.put<IndexController>(IndexController());
Get.put<LocationController>(LocationController());
Get.put<DestinationController>(DestinationController());
}
}
import 'dart:async';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_polyline_points/flutter_polyline_points.dart';
import 'package:geojson_vi/geojson_vi.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:latlong2/latlong.dart';
import 'package:rogapp/model/destination.dart';
import 'package:rogapp/pages/destination/destination_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 'package:rogapp/services/api_service.dart';
import '../../main.dart';
class IndexController extends GetxController with WidgetsBindingObserver {
List<GeoJSONFeatureCollection> locations = <GeoJSONFeatureCollection>[].obs;
List<GeoJSONFeature> currentFeature = <GeoJSONFeature>[].obs;
List<Destination> currentDestinationFeature = <Destination>[].obs;
List<dynamic> perfectures = <dynamic>[].obs;
List<LatLngBounds> currentBound = <LatLngBounds>[].obs;
List<dynamic> subPerfs = <dynamic>[].obs;
List<dynamic> areas = <dynamic>[].obs;
List<dynamic> customAreas = <dynamic>[].obs;
List<dynamic> cats = <dynamic>[].obs;
List<String> currentCat = <String>[].obs;
List<Map<String, dynamic>> currentUser = <Map<String, dynamic>>[].obs;
List<dynamic> currentAction = <dynamic>[].obs;
List<PointLatLng> routePoints = <PointLatLng>[].obs;
var routePointLenght = 0.obs;
double currentLat = 0.0, currentLon = 0.0;
var isLoading = false.obs;
var isRogMapcontrollerLoaded = false.obs;
var isCustomAreaSelected = false.obs;
RxBool isMapControllerReady = RxBool(false); // MapControllerの初期化状態を管理するフラグ
//final mapControllerReadyStream = Stream<bool>.value(false); // MapControllerの初期化状態を通知するためのストリーム
MapController mapController = MapController();
MapController rogMapController = MapController();
LogManager logManager = LogManager();
String? userToken;
// mode = 0 is map mode, mode = 1 list mode
var mode = 0.obs;
// master mode, rog or selection
var rogMode = 1.obs;
var desinationMode = 1.obs;
bool showPopup = true;
String dropdownValue = "9";
String subDropdownValue = "-1";
String areaDropdownValue = "-1";
String cateogory = "-all-";
ConnectivityResult connectionStatus = ConnectivityResult.none;
var connectionStatusName = "".obs;
final Connectivity _connectivity = Connectivity();
late StreamSubscription<ConnectivityResult> _connectivitySubscription;
void toggleMode() {
if (mode.value == 0) {
mode += 1;
} else {
mode -= 1;
}
}
void toggleDestinationMode() {
if (desinationMode.value == 0) {
desinationMode.value += 1;
} else {
desinationMode.value -= 1;
}
}
void switchPage(String page) {
////print("######## ${currentUser[0]["user"]["id"]}");
switch (page) {
case AppPages.INDEX:
{
rogMode.value = 0;
//print("-- rog mode is ctrl is ${rog_mode.value}");
Get.toNamed(page);
}
break;
case AppPages.TRAVEL:
{
rogMode.value = 1;
//Get.back();
//Get.off(DestnationPage(), binding: DestinationBinding());
}
break;
case AppPages.LOGIN:
{
rogMode.value = 2;
Get.toNamed(page);
}
break;
default:
{
rogMode.value = 1;
Get.toNamed(AppPages.INDEX);
}
}
}
Future<void> _checkLocationPermission() async {
if (Get.context == null) {
debugPrint('Get.context is null in _checkLocationPermission');
return;
}
LocationPermission permission = await Geolocator.checkPermission();
//permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
debugPrint('GPS : Denied');
await showLocationPermissionDeniedDialog();
} else if (permission == LocationPermission.deniedForever) {
debugPrint('GPS : Denied forever');
await showLocationPermissionDeniedDialog();
}else if (permission == LocationPermission.whileInUse){
debugPrint('GPS : While-In-Use');
await showLocationPermissionDeniedDialog();
}else{
debugPrint("Permission is no problem....");
}
}
// 追加
Future<void> showLocationPermissionDeniedDialog() async {
if (Get.context != null) {
print('Showing location permission denied dialog');
await showDialog(
context: Get.context!,
barrierDismissible: false,
builder: (BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: AlertDialog(
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('confirm'.tr),
),
],
),
);
},
);
} else {
print('Get.context is null in showLocationPermissionDeniedDialog');
// Get.contextがnullの場合の処理
print('Location permission denied, but context is null');
}
}
@override
void onInit() {
_connectivitySubscription =
_connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
super.onInit();
WidgetsBinding.instance?.addObserver(this);
_startLocationService(); // アプリ起動時にLocationServiceを開始する
initializeApiService();
print('IndexController onInit called'); // デバッグ用の出力を追加
}
Future<void> initializeApiService() async {
if (currentUser.isNotEmpty) {
// 既にログインしている場合
await Get.putAsync(() => ApiService().init());
// 必要に応じて追加の初期化処理
}
}
/*
void checkPermission()
{
debugPrint("MapControllerの初期化が完了したら、位置情報の許可をチェックする");
_checkLocationPermission();
}
*/
@override
void onClose() {
_connectivitySubscription.cancel();
WidgetsBinding.instance?.removeObserver(this);
_stopLocationService(); // アプリ終了時にLocationServiceを停止する
super.onClose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
if (!_isLocationServiceRunning()) {
_startLocationService();
}
} else if (state == AppLifecycleState.paused) {
_stopLocationService();
}
}
bool _isLocationServiceRunning() {
// LocationServiceが実行中かどうかを確認する処理を実装する
// 例えば、SharedPreferencesにサービスの状態を保存するなど
// ここでは簡単のために、常にfalseを返すようにしています
return false;
}
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}'.");
}
}
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}'.");
}
}
/*
@override
void onReady() async {
await readUserToken();
final token = userToken;
if (token != null && token.isNotEmpty) {
await loadUserDetailsForToken(token);
fixMapBound(token);
} else {
// ユーザートークンが存在しない場合はログイン画面にリダイレクト
Get.offAllNamed(AppPages.LOGIN);
}
// 地図のイベントリスナーを設定
indexController.mapController.mapEventStream.listen((MapEvent mapEvent) {
if (mapEvent is MapEventMoveEnd) {
indexController.loadLocationsBound();
}
});
super.onReady();
}
*/
Future<void> _updateConnectionStatus(ConnectivityResult result) async {
connectionStatus = result;
connectionStatusName.value = result.name;
}
Future<void> initConnectivity() async {
late ConnectivityResult result;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
result = await _connectivity.checkConnectivity();
} on PlatformException catch (_) {
//print('Couldn\'t check connectivity status --- $e');
return;
}
return _updateConnectionStatus(result);
}
LatLngBounds boundsFromLatLngList(List<LatLng> list) {
double? x0, x1, y0, y1;
for (LatLng latLng in list) {
if (x0 == null || x1 == null || y0 == null || y1 == null) {
x0 = x1 = latLng.latitude;
y0 = y1 = latLng.longitude;
} else {
if (latLng.latitude > x1) x1 = latLng.latitude;
if (latLng.latitude < x0) x0 = latLng.latitude;
if (latLng.longitude > y1) y1 = latLng.longitude;
if (latLng.longitude < y0) y0 = latLng.longitude;
}
}
logManager.addOperationLog("Called boundsFromLatLngList (${x1!},${y1!})-(${x0!},${y0!}).");
return LatLngBounds(LatLng(x1!, y1!), LatLng(x0!, y0!));
}
// 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。
//
void login(String email, String password, BuildContext context) async{
AuthService.login(email, password).then((value) async {
print("------- logged in user details ######## $value ###### --------");
if (value.isNotEmpty) {
logManager.addOperationLog("User logged in : ${value}.");
// Navigator.pop(context);
print("--------- user details login ----- $value");
changeUser(value);
// ログイン成功後、api_serviceを初期化
await Get.putAsync(() => ApiService().init());
} else {
logManager.addOperationLog("User failed login : ${email} , ${password}.");
isLoading.value = false;
Get.snackbar(
"login_failed".tr,
"check_login_id_or_password".tr,
backgroundColor: Colors.red,
colorText: Colors.white,
icon: const Icon(Icons.error, size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
//backgroundColor: Colors.yellow,
//icon:Image(image:AssetImage("assets/images/dora.png"))
);
}
});
}
// 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。
//
void changePassword(
String oldpassword, String newpassword, BuildContext context) {
String token = currentUser[0]['token'];
////print("------- change password ######## ${currentUser[0]['token']} ###### --------");
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) {
switchPage(AppPages.TRAVEL);
} else {
switchPage(AppPages.INDEX);
}
} else {
logManager.addOperationLog("User failed to change password : ${oldpassword} , ${newpassword}.");
Get.snackbar(
'failed'.tr,
'password_change_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),
//backgroundColor: Colors.yellow,
//icon:Image(image:AssetImage("assets/images/dora.png"))
);
}
});
isLoading.value = false;
}
/*
void logout() async {
locations.clear();
DatabaseHelper db = DatabaseHelper.instance;
db.deleteAllDestinations().then((value) {
DestinationController destinationController =
Get.find<DestinationController>();
destinationController.populateDestinations();
});
currentUser.clear();
cats.clear();
// ユーザートークンをデバイスから削除
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.remove("user_token");
}
*/
void logout() async {
logManager.addOperationLog("User logout : ${currentUser} .");
saveGameState();
locations.clear();
DatabaseHelper db = DatabaseHelper.instance;
db.deleteAllDestinations().then((value) {
DestinationController destinationController =
Get.find<DestinationController>();
destinationController.populateDestinations();
});
currentUser.clear();
cats.clear();
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.remove("user_token");
}
// 要検討:エラーハンドリングが行われていますが、エラーメッセージをローカライズすることを検討してください。
//
void register(String email, String password, BuildContext context) {
AuthService.register(email, password).then((value) {
if (value.isNotEmpty) {
debugPrint("ユーザー登録成功:${email}, ${password}");
logManager.addOperationLog("User tried to register new account : ${email} , ${password} .");
currentUser.clear();
currentUser.add(value);
isLoading.value = false;
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, // ユーザー登録に失敗しました。
backgroundColor: Colors.red,
colorText: Colors.white,
icon: const Icon(Icons.error, size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
//backgroundColor: Colors.yellow,
//icon:Image(image:AssetImage("assets/images/dora.png"))
);
}
});
}
void saveToDevice(String val) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString("user_token", val);
print("saveToDevice: ${val}");
}
/*
void changeUser(Map<String, dynamic> value, {bool replace = true}) {
print("---- change user to $value -----");
currentUser.clear();
currentUser.add(value);
if (replace) {
saveToDevice(currentUser[0]["token"]);
}
isLoading.value = false;
loadLocationsBound();
if (currentUser.isNotEmpty) {
rogMode.value = 0;
} else {
rogMode.value = 1;
}
print('--- c rog mode --- ${rogMode.value}');
Get.toNamed(AppPages.INDEX);
}
*/
void changeUser(Map<String, dynamic> value, {bool replace = true}) {
currentUser.clear();
currentUser.add(value);
if (replace) {
saveToDevice(currentUser[0]["token"]);
}
isLoading.value = false;
loadLocationsBound();
if (currentUser.isNotEmpty) {
rogMode.value = 0;
restoreGame();
} else {
rogMode.value = 1;
}
Get.toNamed(AppPages.INDEX);
}
loadUserDetailsForToken(String token) async {
AuthService.userForToken(token).then((value) {
print("----token val-- $value ------");
if (value![0]["user"].isEmpty) {
Get.toNamed(AppPages.LOGIN);
return;
}
changeUser(value[0], replace: false);
});
}
/* Old code
void loadLocationsBound() {
if (isCustomAreaSelected.value == true) {
return;
}
locations.clear();
String cat = currentCat.isNotEmpty ? currentCat[0] : "";
if (currentCat.isNotEmpty && currentCat[0] == "-all-") {
cat = "";
}
LatLngBounds bounds = mapController.bounds!;
currentBound.clear();
currentBound.add(bounds);
////print(currentCat);
LocationService.loadLocationsBound(
bounds.southWest.latitude,
bounds.southWest.longitude,
bounds.northWest.latitude,
bounds.northWest.longitude,
bounds.northEast.latitude,
bounds.northEast.longitude,
bounds.southEast.latitude,
bounds.southEast.longitude,
cat)
.then((value) {
////print("---value length ------ ${value!.collection.length}");
if (value == null) {
return;
}
if (value.features.isEmpty) {
if (showPopup == false) {
return;
}
Get.snackbar(
"Too many Points",
"please zoom in",
icon: const Icon(Icons.assistant_photo_outlined,
size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 2),
backgroundColor: Colors.yellow,
//icon:Image(image:AssetImage("assets/images/dora.png"))
);
showPopup = false;
//Get.showSnackbar(GetSnackBar(message: "Too many points, please zoom in",));
}
if (value.features.isNotEmpty) {
////print("---- added---");
locations.add(value);
}
});
}
*/
// 2024-04-03 Akira .. Update the code . See ticket 2800.
//
// 2024-4-8 Akira : See 2809
// IndexControllerクラスでは、Future.delayedの呼び出しをunawaitedで囲んで、
// 非同期処理の結果を待たずに先に進むようにしました。これにより、メモリリークを防ぐことができます
//
// 要検討Future.delayedを使用して非同期処理を待たずに先に進むようにしていますが、
// これによってメモリリークが発生する可能性があります。非同期処理の結果を適切に処理することを検討してください。
//
void loadLocationsBound() async {
if (isCustomAreaSelected.value == true) {
return;
}
// MapControllerの初期化が完了するまで待機
await waitForMapControllerReady();
locations.clear();
String cat = currentCat.isNotEmpty ? currentCat[0] : "";
if (currentCat.isNotEmpty && currentCat[0] == "-all-") {
cat = "";
}
/*
// Akira Add 2024-4-6
if( mapController.controller == null ) {
print("操作が完了する前にMapControllerまたはウィジェットが破棄されました。");
isLoading.value = true; // ローディング状態をtrueに設定
return;
}
//
*/
LatLngBounds bounds = mapController.bounds!;
if (bounds == null) {
// MapControllerの初期化が完了していない場合は処理を行わない
return;
}
currentBound.clear();
currentBound.add(bounds);
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 value = await LocationService.loadLocationsBound(
bounds.southWest.latitude,
bounds.southWest.longitude,
bounds.northWest.latitude,
bounds.northWest.longitude,
bounds.northEast.latitude,
bounds.northEast.longitude,
bounds.southEast.latitude,
bounds.southEast.longitude,
cat
);
/*
if (value == null) {
// APIからのレスポンスがnullの場合
print("LocationService.loadLocationsBound からの回答がnullのため、マップをリロード");
DestinationController destinationController = Get.find<DestinationController>(); // 追加
final tk = currentUser[0]["token"]; // 追加
if (tk != null) { // 追加
destinationController.fixMapBound(tk); // 追加
} // 追加
return;
}
*/
isLoading.value = false; // ローディング状態をfalseに設定
if (value == null) {
// APIからのレスポンスがnullの場合
print("LocationService.loadLocationsBound からの回答がnullです");
} else {
if (value.features.isEmpty) {
if (showPopup == false) {
return;
}
Get.snackbar(
"Too many Points",
"please zoom in",
backgroundColor: Colors.yellow,
colorText: Colors.white,
icon: const Icon(Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
);
showPopup = false;
}
if (value.features.isNotEmpty) {
locations.add(value);
}
}
/*
if (value != null && value.features.isEmpty) {
if (showPopup == false) {
return;
}
Get.snackbar(
"Too many Points",
"please zoom in",
backgroundColor: Colors.yellow,
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,
);
showPopup = false;
}
if (value != null && value.features.isNotEmpty) {
locations.add(value);
}
*/
} catch ( e) {
print("Error in loadLocationsBound: $e");
// エラーが発生した場合のリトライ処理や適切なエラーメッセージの表示を行う
// 例えば、一定時間後に再度loadLocationsBound()を呼び出すなど
}
}
//===Akira 追加:2024-4-6 #2800
// 要検討MapControllerの初期化が完了するまで待機していますが、タイムアウトを設定することを検討してください。
// 初期化に時間がかかりすぎる場合、ユーザーにわかりやすいメッセージを表示するようにしてください。
//
Future<void> waitForMapControllerReady() async {
if (!isMapControllerReady.value) {
await Future.doWhile(() async {
await Future.delayed(const Duration(milliseconds: 100));
return !isMapControllerReady.value;
});
}
}
//===Akira 追加:2024-4-6 #2800
void setBound(LatLngBounds bounds) {
currentBound.clear();
currentBound.add(bounds);
}
GeoJSONFeature? getFeatureForLatLong(double lat, double long) {
if (locations.isNotEmpty) {
GeoJSONFeature? foundFeature;
locations[0].features.forEach((i) {
GeoJSONMultiPoint p = i!.geometry as GeoJSONMultiPoint;
if (p.coordinates[0][1] == lat && p.coordinates[0][0] == long) {
foundFeature = i;
}
});
return foundFeature;
}
return null;
}
}
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
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 bool _isRequestingPermission = false;
static Completer<bool>? _permissionCompleter;
static Future<bool> checkLocationPermissions() async {
final locationPermission = await Permission.location.status;
final whenInUsePermission = await Permission.locationWhenInUse.status;
final alwaysPermission = await Permission.locationAlways.status;
return locationPermission == PermissionStatus.granted &&
(whenInUsePermission == PermissionStatus.granted || alwaysPermission == PermissionStatus.granted);
}
static Future<bool> checkAndRequestPermissions() async {
if (_isRequestingPermission) {
return _permissionCompleter!.future;
}
_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 ==");
final storagePermission = await Permission.storage.status;
return storagePermission == PermissionStatus.granted;
}
static Future<void> requestStoragePermission() async {
//debugPrint("(gifunavi)== requestStoragePermission ==");
final storagePermission = await Permission.storage.request();
if (storagePermission == PermissionStatus.granted) {
return;
}
if (storagePermission == PermissionStatus.permanentlyDenied) {
// リクエストが完了するまで待機
await Future.delayed(Duration(milliseconds: 500));
showPermissionDeniedDialog('storage_permission_needed_title','storage_permission_needed_main');
}
}
/*
static Future<bool> checkLocationBasicPermission() async {
//debugPrint("(gifunavi)== checkLocationBasicPermission ==");
final locationPermission = await Permission.location.status;
return locationPermission == PermissionStatus.granted;
}
static Future<bool> checkLocationWhenInUsePermission() async {
//debugPrint("(gifunavi)== checkLocationWhenInUsePermission ==");
final whenInUsePermission = await Permission.locationWhenInUse.status;
return whenInUsePermission == PermissionStatus.granted;
}
static Future<bool> checkLocationAlwaysPermission() async {
//debugPrint("(gifunavi)== checkLocationAlwaysPermission ==");
final alwaysPermission = await Permission.locationAlways.status;
return alwaysPermission == PermissionStatus.granted;
}
static bool isBasicPermission=false;
static Future<void> requestLocationBasicPermissions() async {
//debugPrint("(gifunavi)== requestLocationBasicPermissions ==");
try{
if(!isBasicPermission){
isBasicPermission=true;
final locationStatus = await Permission.location.request();
if (locationStatus != PermissionStatus.granted) {
showPermissionDeniedDialog('location_permission_needed_title','location_permission_needed_main');
}
}
}catch (e, stackTrace){
print('Exception: $e');
print('Stack trace: $stackTrace');
debugPrintStack(label: 'Exception occurred', stackTrace: stackTrace);
}
}
static bool isLocationServiceRunning = false;
static bool isRequestedWhenInUsePermission = false;
static Future<void> requestLocationWhenInUsePermissions() async {
//debugPrint("(gifunavi)== requestLocationWhenInUsePermissions ==");
try{
if(!isRequestedWhenInUsePermission){
isRequestedWhenInUsePermission=true;
final whenInUseStatus = await Permission.locationWhenInUse.request();
if (whenInUseStatus != PermissionStatus.granted) {
showPermissionDeniedDialog('location_permission_needed_title','location_permission_needed_main');
}else{
if( !isLocationServiceRunning ){
isLocationServiceRunning=true;
const platform = MethodChannel('location');
try {
await platform.invokeMethod('startLocationService'); // Location Service を開始する。
} on PlatformException catch (e) {
debugPrint("Failed to start location service: '${e.message}'.");
}
}
}
}
}catch (e, stackTrace){
debugPrint('Exception: $e');
debugPrint('Stack trace: $stackTrace');
debugPrintStack(label: 'Exception occurred', stackTrace: stackTrace);
}
}
static bool isRequestedAlwaysPermission = false;
static Future<void> requestLocationAlwaysPermissions() async {
//debugPrint("(gifunavi)== requestLocationAlwaysPermissions ==");
try {
if( !isRequestedAlwaysPermission ){
isRequestedAlwaysPermission=true;
final alwaysStatus = await Permission.locationAlways.request();
if (alwaysStatus != PermissionStatus.granted) {
showPermissionDeniedDialog('location_permission_needed_title','location_permission_needed_main');
}
}
}catch (e, stackTrace){
print('Exception: $e');
print('Stack trace: $stackTrace');
debugPrintStack(label: 'Exception occurred', stackTrace: stackTrace);
}
}
static Future<void> checkAndRequestPermissions() async {
final hasPermissions = await checkLocationBasicPermission();
if (!hasPermissions) {
await requestLocationBasicPermissions();
}
final hasWIUPermissions = await checkLocationWhenInUsePermission();
if (!hasWIUPermissions) {
await requestLocationWhenInUsePermissions();
}
final hasAlwaysPermissions = await checkLocationAlwaysPermission();
if (!hasAlwaysPermissions) {
await requestLocationAlwaysPermissions();
}
}
*/
}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';
// 要検討:ログインボタンとサインアップボタンの配色を見直すことを検討してください。現在の配色では、ボタンの役割がわかりにくい可能性があります。
// エラーメッセージをローカライズすることを検討してください。
// ログイン処理中にエラーが発生した場合のエラーハンドリングを追加することをお勧めします。
//
class LoginPage extends StatelessWidget {
final IndexController indexController = Get.find<IndexController>();
TextEditingController emailController = TextEditingController();
TextEditingController passwordController = TextEditingController();
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),
makeInput(
label: "password".tr,
controller: passwordController,
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),
),
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: 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(
"rogaining_user_need_tosign_up".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(
"app_developed_by_gifu_dx".tr,
style: const TextStyle(
overflow: TextOverflow.ellipsis,
fontSize: 10.0),
),
),
),
],
),
const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text(
"※第8回と第9回は、岐阜県の令和年度「清流の国ぎふ」SDGs推進ネットワーク連携促進補助金を受けています",
style: TextStyle(
//overflow: TextOverflow.ellipsis,
fontSize:
10.0, // Consider adjusting the font size if the text is too small.
// Removed overflow: TextOverflow.ellipsis to allow text wrapping.
),
),
),
),
],
),
],
),
],
),
)
: TextButton(
onPressed: () {
indexController.currentUser.clear();
},
child: const Text("Already Logged in, Click to logout"),
),
);
}
}
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,
)
],
);
}
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:rogapp/utils/location_controller.dart';
class DebugController extends GetxController {
final LocationController locationController = Get.find<LocationController>();
final gpsSignalStrength = 'high'.obs;
final currentPosition = Rx<Position?>(null);
final isSimulationMode = false.obs;
@override
void onInit() {
super.onInit();
// 位置情報の更新を監視
locationController.locationMarkerPositionStream.listen((position) {
if (position != null) {
currentPosition.value = Position(
latitude: position.latitude,
longitude: position.longitude,
accuracy: position.accuracy,
altitudeAccuracy: 30,
headingAccuracy: 30,
heading: 0,
altitude: 0,
speed: 0,
speedAccuracy: 0,
timestamp: DateTime.now(),
);
}
});
}
void setGpsSignalStrength(String value) {
gpsSignalStrength.value = value;
locationController.setSimulatedSignalStrength(value);
}
void toggleSimulationMode() {
isSimulationMode.value = !isSimulationMode.value;
locationController.setSimulationMode(isSimulationMode.value);
if (!isSimulationMode.value) {
// 標準モードに切り替えた場合は、シミュレートされた信号強度をリセット
locationController.setSimulatedSignalStrength('low');
gpsSignalStrength.value = 'low';
}
}
}import 'package:get/get.dart';
import 'package:rogapp/pages/debug/debug_controller.dart';
class DebugBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<DebugController>(() => DebugController());
}
}import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rogapp/pages/debug/debug_controller.dart';
class DebugPage extends GetView<DebugController> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('デバッグモード'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('GPS信号強度'),
const SizedBox(height: 20),
Obx(
() => DropdownButton<String>(
value: controller.gpsSignalStrength.value,
onChanged: (value) {
controller.setGpsSignalStrength(value!);
},
items: ['high', 'medium', 'low']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
),
const SizedBox(height: 20),
const Text('現在のGPS座標'),
const SizedBox(height: 10),
Obx(
() => Text(
'緯度: ${controller.currentPosition.value?.latitude ?? '-'}, 経度: ${controller.currentPosition.value?.longitude ?? '-'}',
),
),
const SizedBox(height: 20),
const Text('現在のGPS精度'),
const SizedBox(height: 10),
Obx(
() => Text(
'精度: ${controller.currentPosition.value?.accuracy.toStringAsFixed(2) ?? '-'} m',
),
),
const SizedBox(height: 20),
Obx(
() => ElevatedButton(
onPressed: controller.toggleSimulationMode,
child: Text(controller.isSimulationMode.value
? 'シミュレーションモード'
: '標準モード'),
),
),
],
),
),
);
}
}import 'package:get/get.dart';
import 'package:get/get_navigation/src/routes/get_route.dart';
import 'package:rogapp/pages/category/category_page.dart';
import 'package:rogapp/pages/changepassword/change_password_page.dart';
import 'package:rogapp/pages/city/city_page.dart';
import 'package:rogapp/pages/destination/destination_binding.dart';
import 'package:rogapp/pages/gps/gps_page.dart';
import 'package:rogapp/pages/history/history_page.dart';
import 'package:rogapp/pages/home/home_binding.dart';
import 'package:rogapp/pages/home/home_page.dart';
import 'package:rogapp/pages/index/index_page.dart';
import 'package:rogapp/pages/landing/landing_page.dart';
import 'package:rogapp/pages/loading/loading_page.dart';
import 'package:rogapp/pages/login/login_page.dart';
import 'package:rogapp/pages/mainperf/mainperf_page.dart';
import 'package:rogapp/pages/permission/permission.dart';
import 'package:rogapp/pages/progress/progress.dart';
import 'package:rogapp/pages/register/register_page.dart';
import 'package:rogapp/pages/search/search_binding.dart';
import 'package:rogapp/pages/search/search_page.dart';
import 'package:rogapp/pages/settings/settings_page.dart';
import 'package:rogapp/pages/settings/settings_binding.dart';
import 'package:rogapp/pages/debug/debug_page.dart';
import 'package:rogapp/pages/debug/debug_binding.dart';
import 'package:rogapp/pages/subperf/subperf_page.dart';
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';
part 'app_routes.dart';
class AppPages {
// ignore: constant_identifier_names
static const INDEX = Routes.INDEX;
// ignore: constant_identifier_names
static const SPA = Routes.SPA;
static const LANDING = Routes.LANDING;
static const LOGIN = Routes.LOGIN;
static const REGISTER = Routes.REGISTER;
static const TRAVEL = Routes.TRAVEL;
static const LOADING = Routes.LOADING;
static const DESTINATION_MAP = Routes.DESTINATION_MAP;
static const HOME = Routes.HOME;
static const PERMISSION = Routes.PERMISSION;
//static const PERMISSION = Routes.HOME;
static const SEARCH = Routes.SEARCH;
static const MAINPERF = Routes.MAINPERF;
static const SUBPERF = Routes.SUBPERF;
static const CITY = Routes.CITY;
static const CATEGORY = Routes.CATEOGORY;
static const CHANGE_PASSWORD = Routes.CHANGE_PASSWORD;
static const CAMERA_PAGE = Routes.CAMERA_PAGE;
static const PROGRESS = Routes.PROGRESS;
static const HISTORY = Routes.HISTORY;
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 final routes = [
GetPage(
name: Routes.INDEX,
page: () => IndexPage(),
//binding: IndexBinding(),
),
GetPage(
name: Routes.LANDING,
page: () => const LandingPage(),
//binding: SpaBinding(),
),
GetPage(
name: Routes.LOGIN,
page: () => LoginPage(),
//binding: SpaBinding(),
),
GetPage(
name: Routes.REGISTER,
page: () => RegisterPage(),
//binding: SpaBinding(),
),
GetPage(
name: Routes.LOADING,
page: () => const LoadingPage(),
//binding: DestinationBinding(),
),
GetPage(
name: Routes.HOME,
page: () => HomePage(),
binding: HomeBinding(),
),
GetPage(
name: Routes.PERMISSION,
page: () => PermissionHandlerScreen(),
),
GetPage(
name: Routes.SEARCH,
page: () => SearchPage(),
binding: SearchBinding(),
),
GetPage(
name: Routes.CITY,
page: () => const CityPage(),
),
GetPage(
name: Routes.CHANGE_PASSWORD,
page: () => ChangePasswordPage(),
),
GetPage(
name: Routes.PROGRESS,
page: () => const ProgressPage(),
),
GetPage(
name: Routes.HISTORY,
page: () => const HistoryPage(),
),
GetPage(
name: Routes.GPS,
page: () => const GpsPage(),
),
GetPage(
name: Routes.SETTINGS,
page: () => const SettingsPage(),
binding: SettingsBinding(),
),
GetPage(
name: Routes.DEBUG,
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(),
),
];
}
part of 'app_pages.dart';
abstract class Routes {
// Main Menu Route
// static const HOME = '/';
// static const MAP = '/map';
// ignore: constant_identifier_names
static const INDEX = '/';
// ignore: constant_identifier_names
static const SPA = '/spa';
static const LANDING = '/landing';
static const LOGIN = '/login';
static const REGISTER = '/register';
static const TRAVEL = '/travel';
static const LOADING = '/loading';
static const DESTINATION_MAP = '/destination_map';
static const HOME = '/home';
static const PERMISSION = '/permission';
static const SEARCH = '/search';
static const MAINPERF = '/mainperf';
static const SUBPERF = '/subperf';
static const CITY = '/city';
static const CATEOGORY = '/category';
static const CHANGE_PASSWORD = '/change_password';
static const CAMERA_PAGE = '/camera_page';
static const PROGRESS = '/progress';
static const HISTORY = '/history';
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';
}
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:rogapp/utils/const.dart';
class ActionService {
static Future<Map<String, dynamic>> makeAction(int userId, int locationId,
bool wanttogo, bool like, bool checkin) async {
//print("----- action is ---- $like-- $wanttogo-- $checkin");
Map<String, dynamic> cats = {};
String serverUrl = ConstValues.currentServer();
String url =
"$serverUrl/api/makeaction/?user_id=$userId&location_id=$locationId&wanttogo=$wanttogo&like=$like&checkin=$checkin";
//String url = "http://localhost:8100/api/makeaction/?user_id=${user_id}&location_id=${location_id}&wanttogo=${wanttogo}&like=${like}&checkin=${checkin}";
//print('++++++++$url');
//print("url is ------ $url");
final http.Response response =
await http.get(Uri.parse(url), headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
});
if (response.statusCode == 200) {
cats = json.decode(utf8.decode(response.bodyBytes));
}
return cats;
}
static Future<List<dynamic>?> userAction(int userId, int locationId) async {
List<dynamic> cats = [];
String serverUrl = ConstValues.currentServer();
String url =
'$serverUrl/api/useraction/?user_id=$userId&location_id=$locationId';
//print('++++++++$url');
//String url = 'http://localhost:8100/api/useraction/?user_id=${user_id}&location_id=${location_id}';
final response = await http.get(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
);
if (response.statusCode == 200) {
cats = json.decode(utf8.decode(response.bodyBytes));
}
return cats;
}
}
// import 'package:geojson/geojson.dart';
// import 'package:http/http.dart' as http;
// import '../utils/const.dart';
// class LocationLineService {
// static Future<GeoJsonFeature?> loadLocationLines() async {
// final geo = GeoJson();
// GeoJsonFeature? fs;
// String serverUrl = ConstValues.currentServer();
// String url = '$serverUrl/api/location_line/';
// //print('++++++++$url');
// final response = await http.get(
// Uri.parse(url),
// headers: <String, String>{
// 'Content-Type': 'application/json; charset=UTF-8',
// },
// );
// if (response.statusCode == 200) {
// geo.processedFeatures.listen((fst) {
// fs = fst;
// });
// await geo.parse(response.body, verbose: true);
// return fs;
// } else {
// throw Exception('Failed to create album.');
// }
// }
// }
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:geojson_vi/geojson_vi.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:rogapp/pages/destination/destination_controller.dart';
import 'package:rogapp/pages/index/index_controller.dart';
import 'package:rogapp/utils/const.dart';
class LocationService {
static Future<GeoJSONFeatureCollection?> loadLocationsFor(
String perfecture, String cat) async {
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/inperf/?rog=$r&perf=$perfecture&cat=$cat';
} else {
url = '$serverUrl/api/inperf/?perf=$perfecture&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'];
url = '$serverUrl/api/inperf/?rog=$r&perf=$perfecture';
} else {
url = '$serverUrl/api/inperf/?perf=$perfecture';
}
}
//print('++++++++$url');
final response = await http.get(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
);
if (response.statusCode == 200) {
GeoJSONFeatureCollection cc =
GeoJSONFeatureCollection.fromJSON(utf8.decode(response.bodyBytes));
//print(cc);
return cc; //featuresFromGeoJson(utf8.decode(response.bodyBytes));
}
return null;
}
static Future<List<dynamic>?> getLocationsExt(String token) async {
List<dynamic> ext = [];
String serverUrl = ConstValues.currentServer();
String url = '$serverUrl/api/locsext/';
//print('++++++++$url');
final response = await http.post(Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Token $token'
},
body: jsonEncode(<String, String>{
'token': token,
}));
if (response.statusCode == 200) {
ext = json.decode(utf8.decode(response.bodyBytes));
}
return ext;
}
static Future<GeoJSONFeatureCollection?> loadLocationsBound(
double lat1,
double lon1,
double lat2,
double lon2,
double lat3,
double lon3,
double lat4,
double lon4,
String cat) 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',
},
);
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;
}
}
return null;
}
static Future<GeoJSONFeatureCollection?> loadCustomLocations(
String name, String cat) async {
final IndexController indexController = Get.find<IndexController>();
String url = "";
if (cat == "-all-") {
cat = "";
}
String serverUrl = ConstValues.currentServer();
//print("loadCustomLocations url is ----- $cat");
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/custom_area/?rog=$r&&cat=$cat';
} else {
url = '$serverUrl/api/custom_area/?&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'];
url = '$serverUrl/api/customarea?rog=$r&name=$name';
} else {
url = '$serverUrl/api/customarea?name=$name';
}
}
//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) {
GeoJSONFeatureCollection cc =
GeoJSONFeatureCollection.fromJSON(utf8.decode(response.bodyBytes));
if (cc.features.isEmpty) {
return null;
} else {
return cc;
}
}
return null;
}
static const platform = MethodChannel('location');
static Future<bool> isLocationServiceRunning() async {
try {
final bool isRunning = await platform.invokeMethod('isLocationServiceRunning');
return isRunning;
} catch (e) {
print("Failed to check if location service is running: $e");
return false;
}
}
}
import 'dart:convert';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:rogapp/model/destination.dart';
import 'package:rogapp/pages/destination/destination_controller.dart';
class MatrixService {
static Future<Map<String, dynamic>> getDestinations(
List<Destination> destinations) async {
final DestinationController destinationController =
Get.find<DestinationController>();
String locs = "";
String origin = "";
String destination = "";
int i = 0;
for (Destination d in destinations) {
//print("---- getting matrix for $d ------------");
if (i == 0) {
origin = "${d.lat}, ${d.lon}";
i++;
continue;
}
if (i == (destinations.length - 1)) {
destination = "${d.lat}, ${d.lon}";
i++;
continue;
}
//print("lat is ${d.lat}, long is ${d.lon}");
locs += "${d.lat}, ${d.lon}|";
i++;
}
locs = "optimize:false|$locs";
String mode = "walking";
switch (destinationController.travelMode.value) {
case 1:
mode = "driving";
break;
case 2:
mode = "transit";
break;
default:
mode = "walking";
break;
}
Map<String, dynamic> cats = {};
String url =
"https://maps.googleapis.com/maps/api/directions/json?destination=$destination&mode=$mode&waypoints=$locs&origin=$origin&key=AIzaSyCN2xFsqFyadWwpjiFxymrxzS6G1tNzraI";
//print('++++++++$url');
final http.Response response =
await http.get(Uri.parse(url), headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
});
if (response.statusCode == 200) {
cats = json.decode(utf8.decode(response.bodyBytes));
}
return cats;
}
}
import 'dart:async';
import 'dart:io';
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, List<String> operationLogs) async {
try {
final String errorMessage = error.toString();
final String stackTraceString = stackTrace.toString();
final String estimatedCause = _estimateErrorCause(errorMessage);
//final String deviceInfo = await _getDeviceInfo();
final Uri url = Uri.parse('https://rogaining.sumasen.net/report-error');
final response = await http.post(
url,
body: {
'error_message': errorMessage,
'stack_trace': stackTraceString,
'estimated_cause': estimatedCause,
'device_info': deviceInfo,
'operation_logs': operationLogs.join('\n'), // オペレーションログを改行で結合して送信
},
);
if (response.statusCode == 200) {
// エラー報告が成功した場合の処理(必要に応じて)
debugPrint("===== エラーログ送信成功しました。 ====");
} else {
// エラー報告が失敗した場合の処理(必要に応じて)
debugPrint("===== エラーログ送信失敗しました。 ====");
}
} catch (e) {
// エラー報告中にエラーが発生した場合の処理(必要に応じて)
debugPrint("===== エラーログ送信中にエラーになりました。 ====");
}
}
static String _estimateErrorCause(String errorMessage) {
// エラーメッセージに基づいてエラーの原因を推定するロジックを追加する
if (errorMessage.contains('NetworkException')) {
return 'ネットワーク接続エラー';
} else if (errorMessage.contains('DatabaseException')) {
return 'データベースエラー';
} else if (errorMessage.contains('AuthenticationException')) {
return '認証エラー';
} else {
return '不明なエラー';
}
}
/*
// 2024-4-8 Akira: メモリ使用量のチェックのため追加 See #2810
//
static void reportMemoryError(String message, StackTrace stackTrace) async {
final errorDetails = FlutterErrorDetails(
exception: Exception(message),
stack: stackTrace,
);
await reportError(errorDetails.exception, errorDetails.stack ?? StackTrace.current, deviceInfo);
}
*/
}
import 'dart:convert';
import 'package:flutter_polyline_points/flutter_polyline_points.dart';
import 'package:get/get.dart';
//import 'package:google_maps_webservice/directions.dart';
import 'package:http/http.dart' as http;
import 'package:rogapp/model/destination.dart';
import 'package:rogapp/pages/destination/destination_controller.dart';
import '../utils/const.dart';
class DestinationService {
static Future<List<dynamic>> getDestinations(int userId) async {
List<dynamic> cats = [];
String serverUrl = ConstValues.currentServer();
String url = "$serverUrl/api/destinations/?user_id=$userId";
//print('++++++++$url');
final http.Response response =
await http.get(Uri.parse(url), headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
});
if (response.statusCode == 200) {
cats = json.decode(utf8.decode(response.bodyBytes));
}
return cats;
}
static Future<Map<String, dynamic>> deleteDestination(int destId) async {
Map<String, dynamic> cats = {};
String serverUrl = ConstValues.currentServer();
String url = "$serverUrl/api/delete_destination/?dest_id=$destId";
//print('++++++++$url');
final http.Response response =
await http.get(Uri.parse(url), headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
});
if (response.statusCode == 200) {
cats = json.decode(utf8.decode(response.bodyBytes));
}
//print("####### ---- $cats");
return cats;
}
static Future<int> updateOrder(int actionId, int order, String dir) async {
int cats = 0;
String serverUrl = ConstValues.currentServer();
String url =
"$serverUrl/api/updateorder/?user_action_id=$actionId&order=$order&dir=$dir";
//print('++++++++$url');
final http.Response response =
await http.get(Uri.parse(url), headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
});
if (response.statusCode == 200) {
cats = json.decode(utf8.decode(response.bodyBytes));
}
return cats;
}
static Future<List<PointLatLng>>? getDestinationLine(
List<Destination> destinations, Map<String, dynamic> mtx) async {
PolylinePoints polylinePoints = PolylinePoints();
if (destinations.isEmpty) {
return [];
}
//print("##### @@@@@ ${destinations[0].lat}");
PointLatLng origin =
PointLatLng(destinations[0].lat!, destinations[0].lon!);
PointLatLng dest = PointLatLng(destinations[destinations.length - 1].lat!,
destinations[destinations.length - 1].lon!);
List<PolylineWayPoint> wayPoints = [];
int i = 0;
for (dynamic d in destinations) {
if (i == 0 || i == (destinations.length - 1)) {
i += 1;
continue;
}
double la = d.lat;
double ln = d.lon;
int j = 0;
PolylineWayPoint pwp =
PolylineWayPoint(location: "$la,$ln", stopOver: false);
//print("----- UUUUUU ${pwp}");
//PointLatLng wp = PointLatLng(d["Location"]["geometry"][0][1], d["Location"]["geometry"][0][0]);
wayPoints.add(pwp);
i += 1;
j += 4;
}
final DestinationController destinationController =
Get.find<DestinationController>();
// int travMode = destinationController.travelMode.value;
// String mode = "WALKING";
// if (travMode == 1) {
// //_mode = TravelMode.driving;
// mode = "DRIVING";
// } else if (travMode == 2) {
// //_mode = TravelMode.transit;
// mode = "TRANSIT";
// }
//PolylineResult result = await polylinePoints.getRouteBetweenCoordinates("AIzaSyAUBI1ablMKuJwGj2-kSuEhvYxvB1A-mOE", PointLatLng(35.389282, 136.498027), PointLatLng(36.285848, 137.575186));
Map<String, dynamic> pl =
destinationController.matrix["routes"][0]["overview_polyline"];
List<PointLatLng> result = polylinePoints.decodePolyline(pl["points"]);
//List<PointLatLng> result = polylinePoints.decodePolyline("qkyvEq`z`Yp@DBMr@XL@Td@Eb@PREd@IFe@rKIzCY|GEvCBzCHvS@xC?HnBHtBHlBFnBFhGRtDVW~BE`@ICHLk@dE_ClPgAtHu@bFsAhPg@~Ge@bFaEtg@kEpi@oCd\\w@nIw@hGe@fBy@nBqAjC{@zBgBtFOd@M@Wv@i@`BQf@ITKCuE`@yDZqBRCHa@DKG_AHwBRiBR_Fp@y@LYBY]M@KJo@v@M@cAGoGN_Cx@}Cf@}@@mM~@qF`@gCLwBj@sBrAeAhAsAtCoF|MmAbD{@fBwAdBw@p@_Ax@BFOHAl@?`@MAQCEAOIQSaBx@{Ah@eATsAHSB?d@A`D?f@IdWy@AS??V?|BCJ}@?cA?k@Au@wBqDuKQaACg@z@gELg@GK~@uEp@{A@q@y@CHwFHcG?KDqCDK^ABABEH{AE{B{@_Ho@uFIaBFQhBaC@SQSg@k@g@q@Yw@qA{De@}B]uDCsAMEWDqAFu@@^A@TDjA@vDA`CETK|AEtAIFY@o@ALpBZ~HBlCBn@EDGPu@pASJO`@Qf@?ROr@K?qDLHnEUTsDNkENYB{Ab@I^?zA}CrCkBfBw@t@@LwA`Bo@r@eGvD}BrAGGuAj@[?i@rBVi@P}@T?F?^MxDuBhDsBzAcAn@s@zCgDAI~A{A|CsC?{A?UHItA_@DCXC~J_@TUIoEvDKTm@?Y^iALIb@k@f@aAE}AA_BC{@\\Cv@CxAEj@ExCwDDc@CYFANCh@WHEIIRQhB}B|C_E\\w@Hq@JE?a@O}CGkAIwEGmDEmDAKLA^?A}@C{@?e@E_DFQNi@LcB\\eBPsADGKOEWBOH[GCPs@Pq@\\cANs@^q@jAu@fCqAf@]HCXoCV_BVmAZmBVcDBeCCgDAaB?s@RE?aCCaEAyHAoDd@EJiD@_@AaAj@A\\A?Gp@@r@oBXm@LQ?IEy@Fy@tA[n@Gj@Tz@[~ACdAAx@Lp@Kr@]hAa@HAQoCMwCSwGSiGK_CCCKaBCgCOoCOgECwGB_OB{JHkBEmC?yCDyFF{QFue@BsYByE?oAEgAByLBiL?gLBuGXsEd@cCNA?OHa@jAuCn@eAtCyDh@k@v@EvBKr@EEkACUKaC?G~@gAlCeDFBT[jFeGZAfBEh@UpBM`AEMaFjFYIhE?hEPpCJzAPt@Fj@GNUFu@N[FyBbAuB`@_@LEIOB}@HUBQFk@FcAACGQA}@Bi@F@F[Dc@D[FQHELGhBMtDGR?D");
//PolylineResult result = await polylinePoints.getRouteBetweenCoordinates("AIzaSyAUBI1ablMKuJwGj2-kSuEhvYxvB1A-mOE", origin,dest, travelMode: _mode, wayPoints: wayPoints);
//print("#####@@@@@ ${result.points}");
return result;
}
}
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../utils/const.dart';
class CatService {
static Future<List<dynamic>?> loadCats(double lat1, double lon1, double lat2,
double lon2, double lat3, double lon3, double lat4, double lon4) async {
List<dynamic> cats = [];
String serverUrl = ConstValues.currentServer();
String url =
'$serverUrl/api/cats/?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 == 200) {
cats = json.decode(utf8.decode(response.bodyBytes));
}
return cats;
}
static Future<List<dynamic>?> loadCatByCity(String cityname) async {
List<dynamic> cats = [];
String serverUrl = ConstValues.currentServer();
String url = '$serverUrl/api/catbycity/?$cityname';
//print('++++++++$url');
final response = await http.get(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
);
if (response.statusCode == 200) {
cats = json.decode(utf8.decode(response.bodyBytes));
}
return cats;
}
}
import 'dart:io';
import 'package:flutter/material.dart';
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/pages/destination/destination_controller.dart';
import 'package:rogapp/pages/index/index_controller.dart';
import 'package:rogapp/utils/database_gps.dart';
import 'package:rogapp/utils/database_helper.dart';
import 'dart:convert';
import '../model/gps_data.dart';
import '../utils/const.dart';
//
// Rog type 0- start 1- checkin 2- goal
//
class ExternalService {
static final ExternalService _instance = ExternalService._internal();
factory ExternalService() {
return _instance;
}
ExternalService._internal();
String getFormatedTime(DateTime datetime) {
return DateFormat('yyyy-MM-dd HH:mm:ss').format(datetime);
}
Future<Map<String, dynamic>> startRogaining() async {
final IndexController indexController = Get.find<IndexController>();
debugPrint("== startRogaining ==");
Map<String, dynamic> res = {};
int userId = indexController.currentUser[0]["user"]["id"];
//print("--- Pressed -----");
String team = indexController.currentUser[0]["user"]['team_name'];
//print("--- _team : ${_team}-----");
String eventCode = indexController.currentUser[0]["user"]["event_code"];
if (indexController.connectionStatusName.value != "wifi" &&
indexController.connectionStatusName.value != "mobile") {
debugPrint("== No network ==");
DatabaseHelper db = DatabaseHelper.instance;
Rog rog = Rog(
id: 1,
team_name: team,
event_code: eventCode,
user_id: userId,
cp_number: -1,
checkintime: DateTime.now().toUtc().microsecondsSinceEpoch,
image: null,
rog_action_type: 0);
db.insertRogaining(rog);
} else {
debugPrint("== startRogaining processing==");
String url = 'https://rogaining.sumasen.net/gifuroge/start_from_rogapp';
//print('++++++++$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>{'team_name': team, 'event_code': eventCode}),
);
//print("---- start rogianing api status ---- ${response.statusCode}");
if (response.statusCode == 200) {
res = json.decode(utf8.decode(response.bodyBytes));
//print('----_res : $res ----');
}
}
return res;
}
Future<Map<String, dynamic>> makeCheckpoint(
int userId,
String token,
String checkinTime,
String teamname,
int cp,
String eventcode,
String imageurl) async {
// print("~~~~ in API call function ~~~~");
// print("~~~~ cp is $cp ~~~~");
//print("--cpcp-- ${cp}");
Map<String, dynamic> res = {};
String url = 'https://rogaining.sumasen.net/gifuroge/checkin_from_rogapp';
//print('++++++++$url');
final IndexController indexController = Get.find<IndexController>();
if (imageurl != null) {
if (indexController.connectionStatusName.value != "wifi" &&
indexController.connectionStatusName.value != "mobile") {
debugPrint("== checkin without network ==");
DatabaseHelper db = DatabaseHelper.instance;
Rog rog = Rog(
id: 1,
team_name: teamname,
event_code: eventcode,
user_id: userId,
cp_number: cp,
checkintime: DateTime.now().toUtc().microsecondsSinceEpoch,
image: imageurl,
rog_action_type: 1,
);
db.insertRogaining(rog);
} else {
debugPrint("== Normal Check in ===");
String serverUrl = ConstValues.currentServer();
String url1 = "$serverUrl/api/checkinimage/";
final im1Bytes = File(imageurl).readAsBytesSync();
String im1_64 = base64Encode(im1Bytes);
//print("~~~~ before calling api 1 ~~~~");
try {
final http.Response response = await http.post(
Uri.parse(url1),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Token $token'
},
// 'id', 'user', 'goalimage', 'goaltime', 'team_name', 'event_code','cp_number'
body: jsonEncode(<String, String>{
'user': userId.toString(),
'team_name': teamname,
'event_code': eventcode,
'checkinimage': im1_64,
'checkintime': checkinTime,
'cp_number': cp.toString()
}),
);
res = json.decode(utf8.decode(response.bodyBytes));
//print("~~~~ api1 result $res ~~~~");
//print("-----@@@@@ checkin $_res -----");
if (response.statusCode == 201) {
//print("~~~~ image from api1 ${res["checkinimage"].toString()} ~~~~");
//print('---- toekn is ${token} -----');
//print("~~~~ token is $token ~~~~");
//print("~~~~ before callling api2 ~~~~");
final http.Response response2 = await http.post(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
'team_name': teamname,
'cp_number': cp.toString(),
'event_code': eventcode,
'image': res["checkinimage"].toString().replaceAll(
'http://localhost:8100', 'http://rogaining.sumasen.net')
}),
);
var vv = jsonEncode(<String, String>{
'team_name': teamname,
'cp_number': cp.toString(),
'event_code': eventcode,
'image': res["checkinimage"].toString().replaceAll(
'http://localhost:8100', 'http://rogaining.sumasen.net')
});
//print("~~~~ api 2 values $vv ~~~~");
//print("--json-- $vv");
//print("--- checnin response ${response2.statusCode}----");
if (response2.statusCode == 200) {
res = json.decode(utf8.decode(response2.bodyBytes));
//print('----checkin res _res : $res ----');
if (res["status"] == "ERROR" && cp>0 ) {
// スタート・ゴールはエラー除外。
Get.snackbar("エラーがおきました", res["detail"],
backgroundColor: Colors.red,
colorText: Colors.white
);
}
}
} else {
Get.snackbar("サーバーエラーがおきました", "サーバーと通信できませんでした",
backgroundColor: Colors.red,
colorText: Colors.white
);
}
} catch( e ) {
print('Error in makeCheckpoint: $e');
Get.snackbar("通信エラーがおきました", "サーバーと通信できませんでした",
backgroundColor: Colors.red,
colorText: Colors.white
);
}
}
} else {
if (indexController.connectionStatusName.value != "wifi" ||
indexController.connectionStatusName.value != "mobile") {
DatabaseHelper db = DatabaseHelper.instance;
Rog rog = Rog(
id: 1,
team_name: teamname,
event_code: eventcode,
user_id: userId,
cp_number: cp,
checkintime: DateTime.now().toUtc().microsecondsSinceEpoch,
image: null,
rog_action_type: 1,
);
db.insertRogaining(rog);
} else {
final http.Response response3 = await http.post(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
'team_name': teamname,
'cp_number': cp.toString(),
'event_code': eventcode,
'image': ""
}),
);
// var vvv = jsonEncode(<String, String>{
// 'team_name': teamname,
// 'cp_number': cp.toString(),
// 'event_code': eventcode,
// 'image': res["checkinimage"].toString().replaceAll(
// 'http://localhost:8100', 'http://rogaining.sumasen.net')
// });
// print("--json-- $vvv");
// print("--- checnin response ${response3.statusCode}----");
if (response3.statusCode == 200) {
res = json.decode(utf8.decode(response3.bodyBytes));
//print('----checkin res _res : $res ----');
}
}
}
//print("~~~~ done checkin ~~~~");
return res;
}
Future<Map<String, dynamic>> makeGoal(int userId, String token,
String teamname, String image, String goalTime, String eventcode) async {
Map<String, dynamic> res2 = {};
final IndexController indexController = Get.find<IndexController>();
final DestinationController destinationController =
Get.find<DestinationController>();
debugPrint("== goal Rogaining ==");
//if(indexController.connectionStatusName != "wifi" && indexController.connectionStatusName != "mobile"){
DatabaseHelper db = DatabaseHelper.instance;
Rog rog = Rog(
id: 1,
team_name: teamname,
event_code: eventcode,
user_id: userId,
cp_number: -1,
checkintime: DateTime.now().toUtc().microsecondsSinceEpoch,
image: image,
rog_action_type: 1,
);
db.insertRogaining(rog);
// }
// else{
String serverUrl = ConstValues.currentServer();
String url1 = "$serverUrl/api/goalimage/";
final im1Bytes = File(image).readAsBytesSync();
String im1_64 = base64Encode(im1Bytes);
final http.Response response = await http.post(
Uri.parse(url1),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Token $token'
},
// 'id', 'user', 'goalimage', 'goaltime', 'team_name', 'event_code','cp_number'
body: jsonEncode(<String, String>{
'user': userId.toString(),
'team_name': teamname,
'event_code': eventcode,
'goaltime': goalTime,
'goalimage': im1_64,
'cp_number': "-1"
}),
);
String url = 'https://rogaining.sumasen.net/gifuroge/goal_from_rogapp';
//print('++++++++$url');
if (response.statusCode == 201) {
Map<String, dynamic> res = json.decode(utf8.decode(response.bodyBytes));
// print('----_res : $res ----');
// print('---- image url ${res["goalimage"]} ----');
final http.Response response2 = await http.post(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
'team_name': teamname,
'event_code': eventcode,
'goal_time': goalTime,
'image': res["goalimage"].toString().replaceAll(
'http://localhost:8100', 'http://rogaining.sumasen.net')
}),
);
String rec = jsonEncode(<String, String>{
'team_name': teamname,
'event_code': eventcode,
'goal_time': goalTime,
'image': res["goalimage"]
.toString()
.replaceAll('http://localhost:8100', 'http://rogaining.sumasen.net')
});
//print("-- json -- $rec");
//print('----- response2 is $response2 --------');
if (response2.statusCode == 200) {
res2 = json.decode(utf8.decode(response2.bodyBytes));
}
}
//}
destinationController.resetRogaining(isgoal: true);
return res2;
}
Future<bool> removeCheckin(int cp) async {
final IndexController indexController = Get.find<IndexController>();
//int userId = indexController.currentUser[0]["user"]["id"];
//print("--- Pressed -----");
String team = indexController.currentUser[0]["user"]['team_name'];
//print("--- _team : ${_team}-----");
String eventCode = indexController.currentUser[0]["user"]["event_code"];
if (indexController.connectionStatusName.value != "wifi" &&
indexController.connectionStatusName.value != "mobile") {
return Future.value(false);
} else {
String url =
'https://rogaining.sumasen.net/gifuroge/remove_checkin_from_rogapp';
//print('++++++++$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>{
'event_code': eventCode,
'team_name': team,
'cp_number': cp.toString()
}),
);
//print("---- remove checkin ---- ${response.statusCode}");
if (response.statusCode == 200) {
return Future.value(true);
//print('----_res : $res ----');
}
}
return Future.value(false);
}
String timestampToTimeString(int timestamp) {
// Convert timestamp to DateTime and format it as needed
var dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp);
//print("^^^^ time ${dateTime}");
// Format dateTime to a time string (e.g., '12:00:00')
// Adjust the format as needed
return "${dateTime.hour}:${dateTime.minute}:${dateTime.second}";
}
Future<bool> pushGPS() async {
//print("^^^^ pushed ^^^");
final IndexController indexController = Get.find<IndexController>();
//int userId = indexController.currentUser[0]["user"]["id"];
//print("--- Pressed -----");
String team = indexController.currentUser[0]["user"]['team_name'];
//print("--- _team : ${_team}-----");
String eventCode = indexController.currentUser[0]["user"]["event_code"];
List<GpsData> gpsDataList = [];
if (indexController.connectionStatusName.value != "wifi" &&
indexController.connectionStatusName.value != "mobile") {
return Future.value(false);
} else {
// Step 1: Fetch data from the local database
gpsDataList =
await GpsDatabaseHelper.instance.getUnsyncedGPSData(team, eventCode);
// Step 2: Transform data into the required format
var payload = {
'team_name': team,
'event_code': eventCode,
'waypoints': gpsDataList.map((gpsData) {
return {
'latitude': gpsData.lat.toString(),
'longitude': gpsData.lon.toString(),
// Convert the timestamp to a formatted time string
'time': timestampToTimeString(gpsData.created_at),
};
}).toList(),
};
//print("calling push gps step 2 ${payload}");
String urlS =
'https://rogaining.sumasen.net/gifuroge/get_waypoint_datas_from_rogapp';
//print('++++++++$url');
var url = Uri.parse(urlS); // Replace with your server URL
var response = await http.post(
url,
headers: {"Content-Type": "application/json"},
body: json.encode(payload),
);
//print("GPS Data res ${response.statusCode}");
if (response.statusCode == 200) {
// Handle success
// make local data as synced
await GpsDatabaseHelper.instance.setSyncData(gpsDataList);
//print("GPS Data sent successfully");
} else {
// Handle error
//print("Failed to send data");
}
}
return Future.value(false);
}
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";
//print('++++++++$url');
final http.Response response =
await http.get(Uri.parse(url), headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
});
if (response.statusCode == 200) {
res = json.decode(utf8.decode(response.bodyBytes));
}
return res;
}
}
import 'dart:async';
import 'package:rogapp/model/destination.dart';
import 'package:rogapp/utils/database_helper.dart';
class DatabaseService {
// Private constructor
DatabaseService._privateConstructor();
// Static instance
static final DatabaseService _instance =
DatabaseService._privateConstructor();
// Factory constructor to return the instance
factory DatabaseService() {
return _instance;
}
// StreamController for updates
final StreamController<List<Destination>> _dbUpdateController =
StreamController<List<Destination>>.broadcast();
// Getter for the stream
Stream<List<Destination>> get destinationUpdatesStream =>
_dbUpdateController.stream;
// Method to fetch destinations
Future<List<Destination>> fetchDestinations() async {
// Your database fetch logic here
List<Destination> destinations = await _fetchFromDb();
_dbUpdateController.add(destinations);
return destinations;
}
// Method to update the database and emit an update through the stream
Future<void> updateDatabase() async {
// Your update logic here
// After updating, fetch the updated list and add it to the stream
fetchDestinations();
}
// Method to fetch data from the database (placeholder)
Future<List<Destination>> _fetchFromDb() async {
// Simulate fetching data with a delay
DatabaseHelper db = DatabaseHelper.instance;
List<Destination> destinations = await db.getDestinations();
return destinations; // Replace with your actual fetch operation
}
// Dispose method to close the stream controller
void dispose() {
_dbUpdateController.close();
}
}
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:rogapp/model/auth_user.dart';
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import '../utils/const.dart';
class AuthService {
Future<AuthUser?> userLogin(String email, String password) async {
final serverUrl = ConstValues.currentServer();
final url = '$serverUrl/api/login/';
try {
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}),
);
switch (response.statusCode) {
case 200:
final data = json.decode(utf8.decode(response.bodyBytes));
AuthUser user = AuthUser.fromMap(data["user"]);
final String token = data["token"];
user.auth_token = token;
return user;
default:
return null;
//throw Exception(response.reasonPhrase);
}
} on Exception catch (_) {
rethrow;
}
}
Future<AuthUser?> userFromToken(String token) async {
final serverUrl = ConstValues.currentServer();
final url = '$serverUrl/api/user/';
try {
final http.Response response =
await http.get(Uri.parse(url), headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Token $token'
});
switch (response.statusCode) {
case 200:
final data = json.decode(utf8.decode(response.bodyBytes));
AuthUser user = AuthUser.fromMap(data);
user.auth_token = token;
return user;
default:
return null;
//throw Exception(response.reasonPhrase);
}
} on Exception catch (_) {
rethrow;
}
}
static Future<Map<String, dynamic>> changePassword(
String oldpassword, String newpassword, String token) async {
Map<String, dynamic> changePassword = {};
String serverUrl = ConstValues.currentServer();
String url = '$serverUrl/api/change-password/';
//print('++++++++$url');
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(<String, String>{
'old_password': oldpassword,
'new_password': newpassword
}),
);
if (response.statusCode == 200) {
changePassword = json.decode(utf8.decode(response.bodyBytes));
}
return changePassword;
}
static Future<Map<String, dynamic>> login(
String email, String password) async {
//print("------- in logged email $email pwd $password ###### --------");
Map<String, dynamic> cats = {};
String serverUrl = ConstValues.currentServer();
String url = '$serverUrl/api/login/';
//print('++++++++$url');
//String url = 'http://localhost:8100/api/login/';
try {
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}),
);
if (response.statusCode == 200) {
cats = json.decode(utf8.decode(response.bodyBytes));
} else {
print('Login failed with status code: ${response.statusCode}');
cats = {};
}
} catch( e ){
print('Error in login: $e');
Get.snackbar("通信エラーがおきました", "サーバーと通信できませんでした",
backgroundColor: Colors.red,
colorText: Colors.white);
Get.snackbar(
"通信エラーがおきました",
"サーバーと通信できませんでした",
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,
);
cats = {};
}
return cats;
}
// ユーザー登録
//
static Future<Map<String, dynamic>> register(
String email, String password) async {
Map<String, dynamic> cats = {};
String serverUrl = ConstValues.currentServer();
String url = '$serverUrl/api/register/';
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}),
);
//print(response.body);
if (response.statusCode == 200) {
cats = json.decode(utf8.decode(response.bodyBytes));
}
return cats;
}
static Future<Map<String, dynamic>> deleteUser(String token) async {
Map<String, dynamic> cats = {};
String serverUrl = ConstValues.currentServer();
String url = '$serverUrl/api/delete-account/';
//print('++++++++$url');
final http.Response response =
await http.get(Uri.parse(url), headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Token $token'
});
if (response.statusCode == 200) {
cats = json.decode(utf8.decode(response.bodyBytes));
}
return cats;
}
static Future<List<dynamic>?> userDetails(int userid) async {
List<dynamic> cats = [];
String serverUrl = ConstValues.currentServer();
String url = '$serverUrl/api/userdetials?user_id=$userid';
//print('++++++++$url');
//print("---- UserDetails url is $url");
final response = await http.get(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
);
if (response.statusCode == 200) {
cats = json.decode(utf8.decode(response.bodyBytes));
}
return cats;
}
static Future<List<dynamic>?> userForToken(String token) async {
Map<String, dynamic> cats = {};
String serverUrl = ConstValues.currentServer();
String url = '$serverUrl/api/user/';
//print('++++++++$url');
//print("---- UserDetails url is $url");
final response = await http.get(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Token $token'
},
);
if (response.statusCode == 200) {
cats = json.decode(utf8.decode(response.bodyBytes));
//print("--- eeeeee $cats ----");
}
return [
{"user": cats, "token": token}
];
}
}
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:rogapp/utils/const.dart';
class TrackingService {
static Future<Map<String, dynamic>> addTrack(
String userId, double lat, double lon) async {
Map<String, dynamic> cats = {};
String serverUrl = ConstValues.currentServer();
String url = '$serverUrl/api/track/';
//print('++++++++$url');
final geom = '{"type": "MULTIPOINT", "coordinates": [[$lon, $lat]]}';
final http.Response response = await http.post(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{'user_id': userId, 'geom': geom}),
);
if (response.statusCode == 200) {
cats = json.decode(utf8.decode(response.bodyBytes));
}
return cats;
}
}
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:rogapp/utils/const.dart';
class PerfectureService {
static Future<List<dynamic>?> loadPerfectures() async {
List<dynamic> perfs = [];
String serverUrl = ConstValues.currentServer();
String url = '$serverUrl/api/perf_main/';
//print('++++++++$url');
final response = await http.get(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
);
if (response.statusCode == 200) {
perfs = json.decode(utf8.decode(response.bodyBytes));
}
return perfs;
}
static Future<List<dynamic>?> loadSubPerfectures(String area) async {
List<dynamic> perfs = [];
String serverUrl = ConstValues.currentServer();
String url = '$serverUrl/api/subperfinmain/?area=$area';
//print('++++++++$url');
final response = await http.get(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
);
if (response.statusCode == 200) {
perfs = json.decode(utf8.decode(response.bodyBytes));
}
return perfs;
}
static Future<List<dynamic>?> getMainPerfExt(String id) async {
List<dynamic> perfs = [];
String serverUrl = ConstValues.currentServer();
String url = '$serverUrl/api/mainperfext/?perf=$id';
//print('++++++++$url');
final response = await http.get(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
);
if (response.statusCode == 200) {
perfs = json.decode(utf8.decode(response.bodyBytes));
}
return perfs;
}
static Future<List<dynamic>?> loadGifuAreas(String perf) async {
List<dynamic> perfs = [];
String serverUrl = ConstValues.currentServer();
String url = '$serverUrl/api/allgifuareas/?perf=$perf';
//print('++++++++$url');
final response = await http.get(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
);
if (response.statusCode == 200) {
perfs = json.decode(utf8.decode(response.bodyBytes));
}
return perfs;
}
static Future<List<dynamic>?> loadCustomAreas() async {
List<dynamic> perfs = [];
String serverUrl = ConstValues.currentServer();
String url = '$serverUrl/api/customareanames';
//print('++++++++$url');
final response = await http.get(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
);
if (response.statusCode == 200) {
perfs = json.decode(utf8.decode(response.bodyBytes));
}
return perfs;
}
static Future<List<dynamic>?> getSubExt(String id) async {
List<dynamic> perfs = [];
String serverUrl = ConstValues.currentServer();
String url = '$serverUrl/api/perfext/?sub_perf=$id';
//print('++++++++$url');
final response = await http.get(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
);
if (response.statusCode == 200) {
perfs = json.decode(utf8.decode(response.bodyBytes));
}
return perfs;
}
}
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:device_info_plus/device_info_plus.dart';
class DeviceInfoService {
static Future<Map<String, dynamic>> getDeviceInfo() async {
final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
final PackageInfo packageInfo = await PackageInfo.fromPlatform();
if (Platform.isAndroid) {
final AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
return {
'os': 'Android',
'os_version': androidInfo.version.release,
'device_model': androidInfo.model,
'app_version': packageInfo.version,
'app_build_number': packageInfo.buildNumber,
};
} else if (Platform.isIOS) {
final IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
return {
'os': 'iOS',
'os_version': iosInfo.systemVersion,
'device_model': iosInfo.model,
'app_version': packageInfo.version,
'app_build_number': packageInfo.buildNumber,
};
} else {
return {
'os': Platform.operatingSystem,
'os_version': Platform.operatingSystemVersion,
'app_version': packageInfo.version,
'app_build_number': packageInfo.buildNumber,
};
}
}
}
// lib/services/api_service.dart
import 'package:get/get.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
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';
class ApiService extends GetxService{
static ApiService get to => Get.find<ApiService>();
String serverUrl = '';
String baseUrl = '';
String token = 'your-auth-token'; // これが使用されている。
Future<void> init() async {
try {
// ここで必要な初期化処理を行う
serverUrl = ConstValues.currentServer();
baseUrl = '$serverUrl/api';
//await Future.delayed(Duration(seconds: 2)); // 仮の遅延(実際の初期化処理に置き換えてください)
print('ApiService initialized successfully');
} catch(e) {
print('Error in ApiService initialization: $e');
rethrow; // エラーを再スローして、呼び出し元で処理できるようにする
}
}
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();
final response = await http.get(
Uri.parse('$baseUrl/teams/'),
headers: {'Authorization': 'Token $token'},
);
if (response.statusCode == 200) {
List<dynamic> teamsJson = json.decode(response.body);
return teamsJson.map((json) => Team.fromJson(json)).toList();
} else {
throw Exception('Failed to load teams');
}
}
Future<List<Category>> getCategories() async {
init();
getToken();
final response = await http.get(
Uri.parse('$baseUrl/categories/'),
headers: {'Authorization': 'Token $token'},
);
if (response.statusCode == 200) {
List<dynamic> categoriesJson = json.decode(response.body);
return categoriesJson.map((json) => Category.fromJson(json)).toList();
} else {
throw Exception('Failed to load categories');
}
}
Future<User> getCurrentUser() async {
init();
getToken();
final response = await http.get(
Uri.parse('$baseUrl/user/'),
headers: {'Authorization': 'Token $token'},
);
if (response.statusCode == 200) {
return User.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to get current user');
}
}
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',
},
body: json.encode({
'team_name': teamName,
'category': categoryId,
}),
);
if (response.statusCode == 201) {
return Team.fromJson(json.decode(response.body));
} 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',
},
body: json.encode({
'team_name': teamName,
'category': categoryId,
}),
);
if (response.statusCode == 200) {
return Team.fromJson(json.decode(response.body));
} 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'},
);
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'},
);
if (response.statusCode == 200) {
List<dynamic> membersJson = json.decode(response.body);
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) async {
init();
getToken();
final response = await http.post(
Uri.parse('$baseUrl/teams/$teamId/members/'),
headers: {
'Authorization': 'Token $token',
'Content-Type': 'application/json',
},
body: json.encode({
'email': email,
'firstname': firstname,
'lastname': lastname,
'date_of_birth': dateOfBirth?.toIso8601String(),
}),
);
if (response.statusCode == 201) {
return User.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to create team member');
}
}
Future<User> updateTeamMember(int teamId,int memberId, String firstname, String lastname, DateTime? dateOfBirth) async {
init();
getToken();
final response = await http.put(
Uri.parse('$baseUrl/teams/$teamId/members/$memberId/'),
headers: {
'Authorization': 'Token $token',
'Content-Type': 'application/json',
},
body: json.encode({
'firstname': firstname,
'lastname': lastname,
'date_of_birth': dateOfBirth?.toIso8601String(),
}),
);
if (response.statusCode == 200) {
return User.fromJson(json.decode(response.body));
} 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> resendMemberInvitation(int memberId) async {
init();
getToken();
final response = await http.post(
Uri.parse('$baseUrl/members/$memberId/resend-invitation/'),
headers: {'Authorization': 'Token $token'},
);
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/entries/'),
headers: {'Authorization': 'Token $token'},
);
if (response.statusCode == 200) {
List<dynamic> entriesJson = json.decode(response.body);
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'},
);
if (response.statusCode == 200) {
List<dynamic> eventsJson = json.decode(response.body);
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) async {
init();
getToken();
final response = await http.post(
Uri.parse('$baseUrl/entry/'),
headers: {
'Authorization': 'Token $token',
'Content-Type': 'application/json',
},
body: json.encode({
'team': teamId,
'event': eventId,
'category': categoryId,
'date': date.toIso8601String(),
}),
);
if (response.statusCode == 201) {
return Entry.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to create entry');
}
}
Future<Entry> updateEntry(int entryId, int eventId, int categoryId, DateTime date) async {
init();
getToken();
final response = await http.put(
Uri.parse('$baseUrl/entry/$entryId/'),
headers: {
'Authorization': 'Token $token',
'Content-Type': 'application/json',
},
body: json.encode({
'event': eventId,
'category': categoryId,
'date': date.toIso8601String(),
}),
);
if (response.statusCode == 200) {
return Entry.fromJson(json.decode(response.body));
} 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');
}
}
}// import 'package:geojson/geojson.dart';
// import 'package:http/http.dart' as http;
// import '../utils/const.dart';
// class LocationPolygonervice {
// static Future<GeoJsonFeature?> loadLocationLines() async {
// final geo = GeoJson();
// GeoJsonFeature? fs;
// String serverUrl = ConstValues.currentServer();
// String url = '$serverUrl/api/location_polygon/';
// //print('++++++++$url');
// final response = await http.get(
// Uri.parse(url),
// headers: <String, String>{
// 'Content-Type': 'application/json; charset=UTF-8',
// },
// );
// if (response.statusCode == 200) {
// geo.processedFeatures.listen((fst) {
// fs = fst;
// });
// await geo.parse(response.body, verbose: true);
// return fs;
// } else {
// throw Exception('Failed to create album.');
// }
// }
// }
import 'package:flutter/material.dart';
class CustomIcons {
static const _fontFamily = 'CustomIcons';
static const IconData gps_signal_low = IconData(0xe900, fontFamily: _fontFamily);
static const IconData gps_signal_middle = IconData(0xe913, fontFamily: _fontFamily);
static const IconData gps_signal_high = IconData(0xe91d, fontFamily: _fontFamily);
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rogapp/utils/location_controller.dart';
enum GPSStatus { high, middle, low }
class GpsSignalStrengthIndicator extends StatelessWidget {
LocationController locationController;
final bool minimized;
// コンストラクタにminimizedパラメータを追加し、デフォルト値をfalseに設定
GpsSignalStrengthIndicator({
super.key,
required this.locationController,
this.minimized = false, // ここでデフォルト値を指定
});
@override
Widget build(BuildContext context) {
// final LocationController locationController = Get.find<LocationController>();
return Obx(() {
String signalStrength = locationController.latestSignalStrength.value;
//debugPrint("GpsSignalStrengthIndicator : signalStrength=${signalStrength}");
IconData iconData;
Color backgroundColor;
String text;
// signalStrengthに応じて、アイコン、背景色、テキストを設定
switch (signalStrength) {
case 'high':
backgroundColor = Colors.green;
iconData = Icons.signal_cellular_alt;
// iconData = CustomIcons.gps_signal_high;
text = 'GPS 強';
break;
case 'medium':
backgroundColor = Colors.orange;
iconData = Icons.signal_cellular_alt_2_bar;
// iconData = CustomIcons.gps_signal_middle;
text = 'GPS 中';
break;
default:
backgroundColor = Colors.grey; // Fallback color
iconData = Icons.signal_cellular_connected_no_internet_4_bar;
// iconData = CustomIcons.gps_signal_low;
text = 'GPS 弱';
}
// コンテナの設定をminimizedの値に応じて調整
return Container(
height: minimized ? 40 : null,
width: minimized ? 40 : null,
padding: minimized ? null : const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
decoration: BoxDecoration(
color: backgroundColor,
shape: minimized ? BoxShape.circle : BoxShape.rectangle,
borderRadius: minimized ? null : BorderRadius.circular(10),
),
child: minimized
? Center(
child: Icon(iconData, color: Colors.white, size: 24),
)
: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(iconData, color: Colors.white),
const SizedBox(width: 8),
Text(
text,
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
],
),
);
});
}
}
import 'dart:ui' as ui;
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'package:flutter/material.dart';
import 'package:geojson_vi/geojson_vi.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart';
import 'package:latlong2/latlong.dart';
import 'package:rogapp/main.dart';
import 'package:rogapp/model/destination.dart';
import 'package:rogapp/pages/camera/camera_page.dart';
import 'package:rogapp/pages/destination/destination_controller.dart';
import 'package:rogapp/pages/index/index_controller.dart';
import 'package:rogapp/services/external_service.dart';
import 'package:rogapp/utils/const.dart';
import 'package:rogapp/utils/database_helper.dart';
import 'package:rogapp/utils/text_util.dart';
import 'package:rogapp/widgets/bottom_sheet_controller.dart';
import 'package:rogapp/widgets/debug_widget.dart';
import 'package:url_launcher/url_launcher.dart';
// BottomSheetNewは、StatelessWidgetを継承したクラスで、目的地の詳細情報を表示するボトムシートのUIを構築します。
// コンストラクタでは、destination目的地オブジェクトとisAlreadyCheckedInすでにチェックイン済みかどうかのフラグを受け取ります。
// buildメソッドでは、detailsSheetメソッドを呼び出して、目的地の詳細情報を表示します。
//
class BottomSheetNew extends GetView<BottomSheetController> {
BottomSheetNew(
{this.isAlreadyCheckedIn = false, Key? key, required this.destination})
: super(key: key);
final IndexController indexController = Get.find<IndexController>();
final DestinationController destinationController =
Get.find<DestinationController>();
final Destination destination; // 目的地オブジェクト
final bool isAlreadyCheckedIn; // すでにチェックイン済みかどうかのフラグ
final RxBool isButtonDisabled = false.obs;
// 目的地の画像を取得するためのメソッドです。
// indexController.rogModeの値に基づいて、適切な画像を返します。画像が見つからない場合は、デフォルトの画像を返します。
//
Image getImage() {
String serverUrl = ConstValues.currentServer();
if (indexController.rogMode == 1) {
if (indexController.currentDestinationFeature.isEmpty ||
indexController.currentDestinationFeature[0].photos! == "") {
return const Image(image: AssetImage('assets/images/empty_image.png'));
} else {
//print("@@@@@@@@@@@@@ rog mode -------------------- ${indexController.currentDestinationFeature[0].photos} @@@@@@@@@@@");
String photo = indexController.currentDestinationFeature[0].photos!;
if (photo.contains('http')) {
return Image(
image: NetworkImage(
indexController.currentDestinationFeature[0].photos!,
),
errorBuilder: (BuildContext context, Object exception,
StackTrace? stackTrace) {
return Image.asset("assets/images/empty_image.png");
},
);
} else {
return Image(
image: NetworkImage(
'$serverUrl/media/compressed/${indexController.currentDestinationFeature[0].photos!}',
),
errorBuilder: (BuildContext context, Object exception,
StackTrace? stackTrace) {
return Image.asset("assets/images/empty_image.png");
},
);
}
}
} else {
GeoJSONFeature gf = indexController.currentFeature[0];
//print("=== photo sss ${gf.properties!["photos"]}");
if (gf.properties!["photos"] == null || gf.properties!["photos"] == "") {
return const Image(image: AssetImage('assets/images/empty_image.png'));
} else {
String photo = gf.properties!["photos"];
if (photo.contains('http')) {
return Image(
image: NetworkImage(
gf.properties!["photos"],
),
errorBuilder: (BuildContext context, Object exception,
StackTrace? stackTrace) {
return Image.asset("assets/images/empty_image.png");
},
);
} else {
String imageUrl = Uri.encodeFull(
'$serverUrl/media/compressed/${gf.properties!["photos"]}');
return Image(
image: NetworkImage(
imageUrl,
),
errorBuilder: (BuildContext context, Object exception,
StackTrace? stackTrace) {
return Image.asset("assets/images/empty_image.png");
},
);
}
}
}
}
// URLを開くためのメソッドです。
// url_launcherパッケージを使用して、指定されたURLを開きます。
//
void _launchURL(url) async {
if (!await launchUrl(url)) throw 'Could not launch $url';
}
// 指定されたlocationidが目的地リストに含まれているかどうかを確認するメソッドです。
// destinationController.destinationsリストを走査し、locationidが一致する目的地があるかどうかを返します。
//
bool isInDestination(String locationid) {
int lid = int.parse(locationid);
if (destinationController.destinations
.where((element) => element.location_id == lid)
.isNotEmpty) {
return true;
} else {
return false;
}
}
Future<void> saveTemporaryImage(Destination destination) async {
final serverUrl = ConstValues.currentServer();
final imagePath = '${serverUrl}/media/compressed/${destination.photos}';
final tempDir = await getTemporaryDirectory();
final tempFile = await File('${tempDir.path}/temp_image.jpg').create(recursive: true);
final response = await http.get(Uri.parse(imagePath));
await tempFile.writeAsBytes(response.bodyBytes);
destinationController.photos.clear();
destinationController.photos.add(tempFile);
}
// アクションボタン(チェックイン、ゴールなど)を表示するためのメソッドです。
// 現在の状態に基づいて、適切なボタンを返します。
// ボタンがタップされたときの処理も含まれています。
//
Widget getActionButton(BuildContext context, Destination destination) {
/*
debugPrint("getActionButton ${destinationController.rogainingCounted.value}");
debugPrint("getActionButton ${destinationController.distanceToStart()}");
debugPrint("getActionButton ${destination.cp}");
debugPrint("getActionButton ${DestinationController.ready_for_goal}");
// ...2024-04-03 Akira デバッグモードのみ出力するようにした。
*/
// bool isInRog=false;
Destination cdest = destinationController
.festuretoDestination(indexController.currentFeature[0]);
var distance = const Distance();
double distanceToDest = distance.as(
LengthUnit.Meter,
LatLng(
destinationController.currentLat, destinationController.currentLon),
LatLng(cdest.lat!, cdest.lon!));
// Check conditions to show confirmation dialog
if (destinationController.isInRog.value == false &&
(destinationController.distanceToStart() <= 100 || destinationController.isGpsSignalWeak() ) && //追加 Akira 2024-4-5
(destination.cp == -1 || destination.cp == 0 ) &&
destinationController.rogainingCounted.value == false) {
// ゲームが始まってなければ
// ロゲ開始している && (開始地点から100m以内 又は 電波が弱い) && CP番号が 1 or 0 && rogainingCounted==false(どこにもチェックインしていない) なら
return Obx(() {
final isInRog = destinationController.isInRog.value;
return ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.secondary,
),
onPressed: destinationController.isInRog.value
? null
: () async {
destinationController.isInRog.value = true;
// Show confirmation dialog
Get.dialog(
AlertDialog(
title: Text("confirm".tr), //confirm
content: Text(
"clear_rog_data_message".tr), //are you sure
actions: <Widget>[
TextButton(
child: Text("no".tr), //no
onPressed: () {
// ダイアログをキャンセルした場合はボタンを再度有効化
destinationController.isInRog.value = false;
Get.back(); // Close the dialog
Get.back(); // Close the bottom sheet
},
),
TextButton(
child: Text("yes".tr), //yes
onPressed: () async {
destinationController.isInRog.value = true;
await saveTemporaryImage(destination);
// Clear data and start game logic here
destinationController.resetRogaining();
destinationController.addToRogaining(
destinationController.currentLat,
destinationController.currentLon,
destination.location_id!,
);
saveGameState();
await ExternalService().startRogaining();
Get.back();
Get.back();// Close the dialog and potentially navigate away
},
),
],
),
barrierDismissible: false, // User must tap a button to close the dialog
);
},
child: Text(
isInRog ? 'in_game'.tr : 'start_rogaining'.tr,
style: TextStyle(color: Colors.white),
),
);
});
//print("counted ${destinationController.rogainingCounted.value}");
}else if (destinationController.rogainingCounted.value == true &&
// destinationController.distanceToStart() <= 500 && ... GPS信号が弱い時でもOKとする。
(destinationController.distanceToStart() <= 500 || destinationController.isGpsSignalWeak() ) &&
(destination.cp == 0 || destination.cp == -2 || destination.cp == -1) &&
// (destination.cp == 0 || destination.cp == -2 ) &&
DestinationController.ready_for_goal == true) {
// ready_for_goal && (開始地点から500m以内 又は 電波が弱い) && CP番号が -1 or -2 or 0 && rogainingCounted==true なら
// Goal ボタン
//goal
return ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
onPressed: destinationController.rogainingCounted.value == true &&
destinationController.distanceToStart() <= 500 &&
(destination.cp == 0 || destination.cp == -2|| destination.cp == -1) &&
DestinationController.ready_for_goal == true
? () async {
destinationController.isAtGoal.value = true;
destinationController.photos.clear();
await showModalBottomSheet(
constraints: BoxConstraints.loose(
ui.Size(Get.width, Get.height * 0.75)),
context: Get.context!,
isScrollControlled: true,
builder: ((context) => CameraPage(
destination: destination,
))).whenComplete(() {
destinationController.skipGps = false;
destinationController.chekcs = 0;
destinationController.isAtGoal.value = false;
});
}
: null,
child: Text(
"finish_rogaining".tr,
style: TextStyle(color: Colors.white),
));
} else if (distanceToDest <=
destinationController.getForcedChckinDistance(destination)) {
// cpごとの強制チェックイン以内にいれば
//start
return ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.secondary,
),
onPressed: isAlreadyCheckedIn == true
? null
: () async {
try{
destinationController.isCheckingIn.value = true; // ここを追加
Get.back();
Get.back();
await Future.delayed(Duration(milliseconds: 500));
await destinationController.callforCheckin(destination);
destinationController.isCheckingIn.value = false;
} catch (e) {
// エラーハンドリング
Get.snackbar(
'Error',
'An error occurred while processing check-in.',
backgroundColor: Colors.red,
colorText: Colors.white,
duration: Duration(seconds: 3),
);
// 必要に応じてエラーログを記録
print('Error processing check-in: $e');
}
},
child: Text(
destination.cp == -1 &&
destinationController.isInRog.value == false &&
destinationController.rogainingCounted.value == false
? "ロゲ開始"
: destinationController.isInRog.value == true &&
destination.cp == -1
? "in_game".tr
: isAlreadyCheckedIn == true
? "in_game".tr
: destinationController.isInRog.value == true
? "checkin".tr
: "rogaining_not_started".tr,
style: TextStyle(color: Theme.of(context).colorScheme.onSecondary),
),
);
}
return Container();
}
// 継承元のbuild をオーバーライドし、detailsSheetメソッドを呼び出して、目的地の詳細情報を表示します。
@override
Widget build(BuildContext context) {
//print("to start ${destinationController.distanceToStart()}");
destinationController.skipGps = true;
// print('--- c use --- ${indexController.currentUser[0].values}');
// print('---- rog_mode ----- ${indexController.rogMode.value} -----');
// return indexController.rogMode.value == 0
// ? detailsSheet(context)
// : destinationSheet(context);
return Obx(() {
if (!destinationController.isCheckingIn.value) {
return detailsSheet(context);
} else {
return Container(); // チェックイン操作中は空のコンテナを返す
}
});
}
// 指定された目的地がすでにチェックイン済みかどうかを確認するメソッドです。
// DatabaseHelperを使用して、目的地の位置情報に基づいてデータベースを検索し、結果を返します。
//
Future<bool> isDestinationCheckedIn(Destination d) async {
DatabaseHelper db = DatabaseHelper.instance;
List<Destination> ds = await db.getDestinationByLatLon(d.lat!, d.lon!);
return ds.isNotEmpty;
}
// show add location details
// 目的地の詳細情報を表示するためのUIを構築するメソッドです。
// 目的地の画像、名前、住所、電話番号、Webサイト、備考などの情報を表示します。
// また、アクションボタンや「ここへ行く」ボタンも表示されます。
//
SingleChildScrollView detailsSheet(BuildContext context) {
Destination cdest = destinationController
.festuretoDestination(indexController.currentFeature[0]);
var distance = const Distance();
double distanceToDest = distance.as(
LengthUnit.Meter,
LatLng(
destinationController.currentLat, destinationController.currentLon),
LatLng(cdest.lat!, cdest.lon!));
debugPrint("Distance from current point : $distanceToDest");
debugPrint(
"forced distance for point : ${destinationController.getForcedChckinDistance(destination)}");
debugPrint(
"current point : ${destinationController.currentLat}, ${destinationController.currentLon} - ${DateTime.now().hour}:${DateTime.now().minute}:${DateTime.now().second}:${DateTime.now().microsecond}");
debugPrint("Checkin radius : ${destination.checkin_radious}");
debugPrint("--${destination.cp}--");
return SingleChildScrollView(
child: Column(
children: [
Padding( // 1行目
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
MaterialButton( // キャンセルボタン
onPressed: () {
Get.back();
},
color: Colors.blue,
textColor: Colors.white,
padding: const EdgeInsets.all(16),
shape: const CircleBorder(),
child: const Icon(
Icons.arrow_back_ios,
size: 14,
),
),
Expanded( // チェックポイント番号+ポイント名
child: Container(
alignment: Alignment.centerLeft,
child: Obx(() => Text(
"${TextUtils.getDisplayTextFeture(indexController.currentFeature[0])} : ${indexController.currentFeature[0].properties!["location_name"]}",
style: const TextStyle(
fontSize: 15.0,
fontWeight: FontWeight.bold,
),
)),
),
),
],
),
),
Row( // 2行目 チェックポイント写真
children: [
Expanded(
child: SizedBox(
height: 260.0,
child: Obx(() => getImage()),
)),
],
),
Obx(() => Padding( // 3行め ボタン類
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Row( // 開始・ゴールボタン
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
// Finish or Goal
(destination.cp == -1 || destination.cp == 0 || destination.cp == -2)
? getActionButton(context, destination)
: Container(), // 一般CPは空
],
),
Row( // 2列目 チェックインボタン
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
//checkin or remove checkin
destinationController.isInRog.value == true
&& (distanceToDest <=
destinationController.getForcedChckinDistance(destination) || destination.checkin_radious==-1 )
&& destination.cp != 0 && destination.cp != -1 && destination.cp != -2
? (isAlreadyCheckedIn == false
? ElevatedButton( // まだチェックインしていなければ、チェックインボタンを表示
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red),
onPressed: () async {
try {
Get.back();
await destinationController.callforCheckin(destination);
} catch (e) {
// エラーハンドリング
Get.snackbar(
'Error',
'An error occurred while processing check-in.',
backgroundColor: Colors.red,
colorText: Colors.white,
duration: Duration(seconds: 3),
);
// 必要に応じてエラーログを記録
print('Error processing check-in: $e');
}
},
child: Text(
"checkin".tr,
style: TextStyle(color: Colors.white),
)
)
: ElevatedButton( // チェックインしていれば、チェックイン取消ボタン
style: ElevatedButton.styleFrom(backgroundColor: Colors.grey[300]),
onPressed: () async {
try {
await destinationController.removeCheckin(destination.cp!.toInt());
destinationController.deleteDestination(destination);
Get.back();
Get.snackbar(
'チェックイン取り消し',
'${destination.name}のチェックインは取り消されました',
backgroundColor: Colors.green,
colorText: Colors.white,
duration: Duration(seconds: 3),
);
} catch (e) {
// エラーハンドリング
Get.snackbar(
'Error',
'An error occurred while canceling check-in.',
backgroundColor: Colors.red,
colorText: Colors.white,
duration: Duration(seconds: 3),
);
// 必要に応じてエラーログを記録
print('Error canceling check-in: $e');
}
},
child: Text(
"cancel_checkin".tr,
style: TextStyle(color: Colors.black),
)
)
) : Container(), // 近くにいなければ空
// go here or cancel route
Obx(() => ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context)
.colorScheme
.onPrimaryContainer),
onPressed: () async {
if (destinationController.isRouteShowing.value) {
destinationController.clearRoute();
Get.back();
} else {
Get.back();
Position position =
await Geolocator.getCurrentPosition(
desiredAccuracy:
LocationAccuracy.bestForNavigation,
forceAndroidLocationManager: true);
Destination ds = Destination(
lat: position.latitude,
lon: position.longitude);
Destination tp = Destination(
lat: destination.lat, lon: destination.lon);
destinationController
.destinationMatrixFromCurrentPoint([ds, tp]);
}
},
child: Text( //ルート表示 or ルート消去
destinationController.isRouteShowing.value
? "cancel_route".tr
: "go_here".tr,
style: TextStyle(
color:
Theme.of(context).colorScheme.onPrimary),
)
)
),
],
),
Row(
children: [
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
indexController.currentDestinationFeature
.isNotEmpty &&
destinationController.isInCheckin.value ==
true
? Container()
: FutureBuilder<Widget>(
future: wantToGo(context),
builder: (context, snapshot) {
return Container(
child: snapshot.data,
);
},
),
indexController.currentFeature[0]
.properties!["location_name"] !=
null &&
(indexController.currentFeature[0]
.properties!["location_name"]
as String)
.isNotEmpty
? Flexible(
child: Text(indexController
.currentFeature[0]
.properties!["location_name"]))
: const SizedBox(
width: 0.0,
height: 0,
),
],
),
),
],
),
const SizedBox(
height: 8.0,
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
const Icon(Icons.roundabout_left),
const SizedBox(
width: 8.0,
),
indexController.currentFeature[0]
.properties!["address"] !=
null &&
(indexController.currentFeature[0]
.properties!["address"] as String)
.isNotEmpty
? getDetails(
context,
"address".tr,
indexController.currentFeature[0]
.properties!["address"] ??
'')
: const SizedBox(
width: 0.0,
height: 0,
),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
const Icon(Icons.phone),
const SizedBox(
width: 8.0,
),
indexController.currentFeature[0]
.properties!["phone"] !=
null &&
(indexController.currentFeature[0]
.properties!["phone"] as String)
.isNotEmpty
? getDetails(
context,
"telephone".tr,
indexController.currentFeature[0]
.properties!["phone"] ??
'')
: const SizedBox(
width: 0.0,
height: 0,
),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
const Icon(Icons.email),
const SizedBox(
width: 8.0,
),
indexController.currentFeature[0]
.properties!["email"] !=
null &&
(indexController.currentFeature[0]
.properties!["email"] as String)
.isNotEmpty
? getDetails(
context,
"email".tr,
indexController.currentFeature[0]
.properties!["email"] ??
'')
: const SizedBox(
width: 0.0,
height: 0,
),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
const Icon(Icons.language),
const SizedBox(
width: 8.0,
),
indexController.currentFeature[0]
.properties!["webcontents"] !=
null &&
(indexController.currentFeature[0]
.properties!["webcontents"] as String)
.isNotEmpty
? getDetails(
context,
"web".tr,
indexController.currentFeature[0]
.properties!["webcontents"] ??
'',
isurl: true)
: const SizedBox(
width: 0.0,
height: 0,
),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
const SizedBox(
width: 8.0,
),
indexController.currentFeature[0]
.properties!["remark"] !=
null &&
(indexController.currentFeature[0]
.properties!["remark"] as String)
.isNotEmpty
? getDetails(
context,
"remarks".tr,
indexController.currentFeature[0]
.properties!["remark"] ??
'',
isurl: false)
: const SizedBox(
width: 0.0,
height: 0,
),
],
),
),
],
),
)),
const SizedBox(
height: 60.0,
)
],
),
);
}
// 「行きたい」ボタンを表示するためのUIを構築するメソッドです。
// 目的地が選択されているかどうかに基づいて、適切なアイコンとテキストを表示します。
// ボタンがタップされたときの処理も含まれています。
//
Future<Widget> wantToGo(BuildContext context) async {
bool selected = false;
// print(
// '---target-- ${indexController.currentFeature[0].properties!["location_id"]}----');
for (Destination d in destinationController.destinations) {
//print('---- ${d.location_id.toString()} ----');
if (d.location_id ==
indexController.currentFeature[0].properties!["location_id"]) {
selected = true;
break;
}
}
DatabaseHelper db = DatabaseHelper.instance;
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// indexController.rog_mode == 0 ?
// // IconButton(
// // icon: Icon(Icons.pin_drop_sharp, size: 32, color: _selected == true ? Colors.amber : Colors.blue,),
// // onPressed: (){
// // if(_selected){
// // // show remove from destination
// // Get.defaultDialog(
// // title: "本当にこのポイントを通過順から外しますか?",
// // middleText: "場所は目的地リストから削除されます",
// // backgroundColor: Colors.blue.shade300,
// // titleStyle: TextStyle(color: Colors.white),
// // middleTextStyle: TextStyle(color: Colors.white),
// // textConfirm: "はい",
// // textCancel: "いいえ",
// // cancelTextColor: Colors.white,
// // confirmTextColor: Colors.blue,
// // buttonColor: Colors.white,
// // barrierDismissible: false,
// // radius: 10,
// // content: Column(
// // children: [
// // ],
// // ),
// // onConfirm: (){
// // int _id = indexController.currentFeature[0].properties!["location_id"];
// // Destination? d = destinationController.destinationById(_id);
// // print('--- des id is : ${d} -----');
// // if(d != null) {
// // //print('--- des id is : ${d.location_id} -----');
// // destinationController.deleteDestination(d);
// // Get.back();
// // Get.back();
// // Get.snackbar("追加した", "場所が削除されました");
// // }
// // }
// // );
// // return;
// // }
// // // show add to destination
// // Get.defaultDialog(
// // title: "この場所を登録してもよろしいですか",
// // middleText: "ロケーションがロガニング リストに追加されます",
// // backgroundColor: Colors.blue.shade300,
// // titleStyle: TextStyle(color: Colors.white),
// // middleTextStyle: TextStyle(color: Colors.white),
// // textConfirm: "はい",
// // textCancel: "いいえ",
// // cancelTextColor: Colors.white,
// // confirmTextColor: Colors.blue,
// // buttonColor: Colors.white,
// // barrierDismissible: false,
// // radius: 10,
// // content: Column(
// // children: [
// // ],
// // ),
// // onConfirm: (){
// // GeoJsonMultiPoint mp = indexController.currentFeature[0].geometry as GeoJsonMultiPoint;
// // LatLng pt = LatLng(mp.geoSerie!.geoPoints[0].latitude, mp.geoSerie!.geoPoints[0].longitude);
// // print("----- want to go sub location is ---- ${indexController.currentFeature[0].properties!["sub_loc_id"]} -----");
// // Destination dest = Destination(
// // name: indexController.currentFeature[0].properties!["location_name"],
// // address: indexController.currentFeature[0].properties!["address"],
// // phone: indexController.currentFeature[0].properties!["phone"],
// // email: indexController.currentFeature[0].properties!["email"],
// // webcontents: indexController.currentFeature[0].properties!["webcontents"],
// // videos: indexController.currentFeature[0].properties!["videos"],
// // category: indexController.currentFeature[0].properties!["category"],
// // series: 1,
// // lat: pt.latitude,
// // lon: pt.longitude,
// // sub_loc_id: indexController.currentFeature[0].properties!["sub_loc_id"],
// // location_id: indexController.currentFeature[0].properties!["location_id"],
// // list_order: 1,
// // photos: indexController.currentFeature[0].properties!["photos"],
// // checkin_radious: indexController.currentFeature[0].properties!["checkin_radius"],
// // auto_checkin: indexController.currentFeature[0].properties!["auto_checkin"] == true ? 1 : 0,
// // cp: indexController.currentFeature[0].properties!["cp"],
// // checkin_point: indexController.currentFeature[0].properties!["checkin_point"],
// // buy_point: indexController.currentFeature[0].properties!["buy_point"],
// // selected: false,
// // checkedin: false,
// // hidden_location: indexController.currentFeature[0].properties!["hidden_location"] == true ?1 : 0
// // );
// // destinationController.addDestinations(dest);
// // Get.back();
// // Get.back();
// // Get.snackbar("追加した", "場所が追加されました");
// // }
// // );
// // },
// // ):
// // Container(),
const SizedBox(
width: 8.0,
),
Obx((() => indexController.rogMode.value == 1
? ElevatedButton(
onPressed: () async {
Destination dest =
indexController.currentDestinationFeature[0];
//print("~~~~ before checking button ~~~~");
//print("------ curent destination is ${dest!.checkedIn}-------");
destinationController.makeCheckin(
dest, !dest.checkedin!, "");
},
child: indexController
.currentDestinationFeature[0].checkedin ==
false
? const Text("チェックイン")
: const Text("チェックアウト"))
: Container())),
],
),
],
);
}
Widget getCheckin(BuildContext context) {
//print("------ currentAction ----- ${indexController.currentAction}-----");
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
indexController.currentAction[0][0]["checkin"] == false
? Column(
children: [
Row(
mainAxisSize: MainAxisSize.max,
children: [
ElevatedButton(
child: const Text("Image"),
onPressed: () {
final ImagePicker picker = ImagePicker();
picker
.pickImage(source: ImageSource.camera)
.then((value) {
//print("----- image---- ${value!.path}");
});
},
)
],
),
ElevatedButton(
onPressed: () {
if (indexController.currentAction.isNotEmpty) {
//print(indexController.currentAction[0]);
indexController.currentAction[0][0]["checkin"] =
true;
Map<String, dynamic> temp =
Map<String, dynamic>.from(
indexController.currentAction[0][0]);
indexController.currentAction.clear();
//print("---temp---${temp}");
indexController.currentAction.add([temp]);
}
},
child: Text("checkin".tr))
],
)
: ElevatedButton(
onPressed: () {
if (indexController.currentAction.isNotEmpty) {
//print(indexController.currentAction[0]);
indexController.currentAction[0][0]["checkin"] = false;
Map<String, dynamic> temp = Map<String, dynamic>.from(
indexController.currentAction[0][0]);
indexController.currentAction.clear();
//print("---temp---${temp}");
indexController.currentAction.add([temp]);
}
},
child: const Icon(Icons.favorite, color: Colors.red),
)
],
)
],
);
}
// 目的地の詳細情報住所、電話番号、Webサイトなどを表示するためのUIを構築するメソッドです。
// ラベルとテキストを受け取り、適切なアイコンとともに表示します。
//
Widget getDetails(BuildContext context, String label, String text,
{bool isurl = false}) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(label),
const SizedBox(
width: 10.0,
),
InkWell(
onTap: () {
if (isurl) {
if (indexController.rogMode.value == 0) {
_launchURL(indexController
.currentFeature[0].properties!["webcontents"]);
} else {
indexController.currentDestinationFeature[0].webcontents;
}
}
},
child: SizedBox(
width: MediaQuery.of(context).size.width -
(MediaQuery.of(context).size.width * 0.35),
child: Text(
text,
textAlign: TextAlign.justify,
style: TextStyle(
color: isurl ? Colors.blue : Colors.black,
),
),
),
),
],
);
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rogapp/model/destination.dart';
import 'package:rogapp/pages/destination/destination_controller.dart';
import 'package:rogapp/pages/index/index_controller.dart';
import 'package:rogapp/utils/const.dart';
import 'package:rogapp/utils/database_helper.dart';
//import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_start.dart';
//import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_goal.dart';
//import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_normal_point.dart';
import 'package:rogapp/widgets/bottom_sheet_new.dart';
import 'package:timeline_tile/timeline_tile.dart';
class DestinationWidget extends StatelessWidget {
DestinationWidget({Key? key}) : super(key: key);
final DestinationController destinationController =
Get.find<DestinationController>();
final IndexController indexController = Get.find<IndexController>();
final List<int> _items = List<int>.generate(50, (int index) => index);
Image getImage(int index) {
if (destinationController.destinations[index].photos == null ||
destinationController.destinations[index].photos == "") {
return const Image(image: AssetImage('assets/images/empty_image.png'));
} else {
// print(
// "------- image is ${destinationController.destinations[index].photos!}------");
String photo = destinationController.destinations[index].photos!;
if (photo.contains('http')) {
return Image(
image:
NetworkImage(destinationController.destinations[index].photos!),
errorBuilder:
(BuildContext context, Object exception, StackTrace? stackTrace) {
return Image.asset("assets/images/empty_image.png");
},
);
} else {
String serverUrl = ConstValues.currentServer();
//print("==== photo is ${server_url + '/media/compressed/' + destinationController.destinations[index].photos!} ===");
return Image(
image: NetworkImage(
'$serverUrl/media/compressed/${destinationController.destinations[index].photos!}'),
errorBuilder:
(BuildContext context, Object exception, StackTrace? stackTrace) {
return Image.asset("assets/images/empty_image.png");
},
);
}
}
}
// bool getSelection(int index){
// bool ret = false;
// destinationController.destination_index_data.forEach((element) {
// if(index == element["index"]){
// if(element["selected"] == true){
// ret = true;
// return;
// }
// }
// });
// return ret;
// }
void doDelete() {
for (var element in destinationController.currentSelectedDestinations) {
destinationController.deleteDestination(element);
destinationController.resetRogaining();
}
// destinationController.destination_index_data.forEach((element) {
// //print(element["index"]);
// destinationController.deleteDestination(element["index"]);
// });
// destinationController.destination_index_data.clear();
}
void moveUp() {
Destination? d;
for (Destination ad in destinationController.destinations) {
if (ad.selected == true) {
d = ad;
break;
}
}
if (d != null) {
//print("--- selected destination is ${d.list_order}");
destinationController.makeOrder(d, -1);
}
}
void moveDown() {
Destination? d;
for (Destination ad in destinationController.destinations) {
if (ad.selected == true) {
d = ad;
break;
}
}
if (d != null) {
//print("--- selected destination is ${d.list_order}");
destinationController.makeOrder(d, 1);
}
}
void clearall() {
Get.defaultDialog(
title: "are_you_sure_want_to_delete_all".tr,
middleText: "all_added_destination_will_be_deleted".tr,
backgroundColor: Colors.blue.shade300,
titleStyle: const TextStyle(color: Colors.white),
middleTextStyle: const TextStyle(color: Colors.white),
textConfirm: "confirm".tr,
textCancel: "cancel".tr,
cancelTextColor: Colors.white,
confirmTextColor: Colors.blue,
buttonColor: Colors.white,
barrierDismissible: false,
radius: 10,
content: const Column(
children: [],
),
onConfirm: () {
destinationController.deleteAllDestinations();
Get.back();
Get.snackbar(
"deleted".tr, "all_destinations_are_deleted_successfully".tr,
backgroundColor: Colors.green,
colorText: Colors.white);
});
}
void interChange() {
// int first_index = -1;
// destinationController.destination_index_data.forEach((element) {
// //print(element["index"]);
// int action_id = destinationController.destinations[element["index"]]["id"] as int;
// destinationController.makeOrder(action_id, (element["index"] as int) + 1, "up");
// });
}
Future getIsLocationAvilable(int locationId) async {
DatabaseHelper db = DatabaseHelper.instance;
return await db.isAlreadyAvailable(locationId);
}
@override
Widget build(BuildContext context) {
print(
"------ destination widget------ ${destinationController.destinationCount.value} ----------");
return Obx(() => Stack(
children: [
Padding(
padding: const EdgeInsets.only(top: 45.0),
child: ListView.builder(
itemCount: destinationController.destinationCount.value,
itemBuilder: (BuildContext context, int index) {
return TimelineTile(
alignment: TimelineAlign.manual,
lineXY: 0.2,
isFirst: index == 0 ? true : false,
indicatorStyle: IndicatorStyle(
indicator: CircleAvatar(
backgroundColor: Colors.red,
child: Text(
destinationController.destinations[index].list_order
.toString(),
style: const TextStyle(color: Colors.white),
),
),
),
key: Key(index.toString()),
endChild: Card(
child: Container(
constraints: const BoxConstraints(
minHeight: 80,
),
child: ListTile(
onTap: () async {
{
Destination? fs =
destinationController.destinations[index];
//print("----fsf-----$index");
if (indexController
.currentDestinationFeature.isNotEmpty) {
indexController.currentDestinationFeature
.clear();
}
indexController.currentDestinationFeature
.add(fs);
// print(
// "--- ndexController.currentDestinationFeature ----- ${indexController.currentDestinationFeature[0].name} ----");
//indexController.getAction();
Widget bottomSheet = BottomSheetNew(destination: fs);
/*
if (fs.cp == -1 || fs.cp == 0) {
bottomSheet = BottomSheetStart(destination: fs);
} else if (fs.cp == -2 || fs.cp == 0) {
bottomSheet = BottomSheetGoal(destination: fs);
} else {
bottomSheet = BottomSheetNormalPoint(destination: fs);
}
*/
showModalBottomSheet(
constraints: BoxConstraints.loose(
Size(Get.width, Get.height * 0.85)),
context: context,
isScrollControlled: true,
//builder:((context) => BottomSheetWidget())
builder: ((context) => bottomSheet)
);
}
},
onLongPress: () {
destinationController.toggleSelection(
destinationController.destinations[index]);
},
selectedTileColor: Colors.amberAccent,
selected: destinationController
.destinations[index].selected!,
leading: getImage(index),
title: Text(destinationController
.destinations[index].name!),
subtitle: Text(destinationController
.destinations[index].category!),
),
),
),
startChild: index > 0 &&
destinationController.matrix["routes"][0]
["legs"] !=
null
? Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(destinationController.matrix["routes"][0]
["legs"][index - 1]["distance"] !=
null
? destinationController.matrix["routes"][0]
["legs"][index - 1]["distance"]
["text"]
.toString()
: ''),
Text(destinationController.matrix["routes"][0]
["legs"][index - 1]["duration"] !=
null
? destinationController.matrix["routes"][0]
["legs"][index - 1]["duration"]
["text"]
.toString()
: '')
],
)
: Container(),
);
}),
),
Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.3),
spreadRadius: 5,
blurRadius: 3,
offset: const Offset(0, 7), // changes position of shadow
),
],
),
height: 44.0,
width: MediaQuery.of(context).size.width,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: const Icon(Icons.delete_forever),
//onPressed: (){doDelete();},
onPressed: clearall,
),
IconButton(
icon: const Icon(Icons.cancel),
//onPressed: (){doDelete();},
onPressed: destinationController
.currentSelectedDestinations.isNotEmpty
? doDelete
: null,
),
IconButton(
icon: const Icon(Icons.move_up),
onPressed: destinationController
.currentSelectedDestinations.isNotEmpty
? moveUp
: null,
),
IconButton(
icon: const Icon(Icons.move_down),
onPressed: destinationController
.currentSelectedDestinations.isNotEmpty
? moveDown
: null,
),
// IconButton(
// icon:Icon(Icons.sync),
// onPressed: destinationController.destination_index_data.length == 2 ? interChange : null,
// ),
],
),
)
],
));
}
}
import 'package:flutter/material.dart';
class CFormTextField extends StatelessWidget {
const CFormTextField({
super.key,
required this.cFocus,
required TextEditingController cController,
}) : cTextEditingController = cController;
final FocusNode cFocus;
final TextEditingController cTextEditingController;
@override
Widget build(BuildContext context) {
return TextFormField(
autocorrect: false,
autofocus: true,
focusNode: cFocus,
controller: cTextEditingController,
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.done,
validator: (value) {
if (value == null || value.isEmpty) {
return "Need a valied email address";
}
return null;
},
decoration: InputDecoration(
//filled: true,
//fillColor: Theme.of(context).colorScheme.primaryContainer,
hintText: "Enter email address",
labelText: "Email",
labelStyle: TextStyle(
color: Theme.of(context).colorScheme.onPrimaryContainer,
fontSize: 16),
prefixIcon: const Icon(Icons.email_outlined),
suffixIcon: cTextEditingController.text.isNotEmpty
? IconButton(
onPressed: () {
cTextEditingController.clear();
},
icon: const Icon(Icons.clear))
: Container(
width: 0,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide(
width: 1, color: Theme.of(context).colorScheme.secondary)),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide(
width: 2, color: Theme.of(context).colorScheme.primary))),
);
}
}
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_location_marker/flutter_map_location_marker.dart';
import 'package:flutter_polyline_points/flutter_polyline_points.dart';
import 'package:geojson_vi/geojson_vi.dart';
import 'package:get/get.dart';
import 'package:latlong2/latlong.dart';
import 'package:rogapp/pages/permission/permission.dart';
import 'package:rogapp/pages/settings/settings_binding.dart';
import 'package:rogapp/model/destination.dart';
import 'package:rogapp/pages/destination/destination_controller.dart';
import 'package:rogapp/pages/index/index_controller.dart';
import 'package:rogapp/utils/database_helper.dart';
import 'package:rogapp/utils/location_controller.dart';
import 'package:rogapp/utils/text_util.dart';
import 'package:rogapp/widgets/base_layer_widget.dart';
import 'package:rogapp/widgets/bottom_sheet_new.dart';
import 'package:rogapp/widgets/current_position_widget.dart';
import 'package:rogapp/widgets/game_state_view.dart';
import 'package:rogapp/pages/settings/settings_controller.dart';
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
class MapResetController {
void Function()? resetIdleTimer;
}
class MapWidget extends StatefulWidget {
const MapWidget({Key? key}) : super(key: key);
@override
State<MapWidget> createState() => _MapWidgetState();
}
class _MapWidgetState extends State<MapWidget> with WidgetsBindingObserver {
//class _MapWidgetState extends State<MapWidget> {
final IndexController indexController = Get.find<IndexController>();
final DestinationController destinationController =
Get.find<DestinationController>();
final LocationController locationController = Get.find<LocationController>();
final SettingsController settingsController = Get.find<SettingsController>();
late MapController mapController;
final Completer<MapController> mapControllerCompleter = Completer<MapController>();
StreamSubscription? subscription;
Timer? _timer;
bool curr_marker_display = false;
Map<LatLng, Marker> _markerCache = {};
List<Marker> _markers = [];
@override
void initState() {
super.initState();
SettingsBinding().dependencies(); // これを追加
_startIdleTimer();
mapController = MapController();
indexController.mapController = mapController;
// added by Akira
WidgetsBinding.instance.addObserver(this);
_startIdleTimer();
// マップの操作イベントをリッスンして、_resetTimerを呼び出す
mapController.mapEventStream.listen((MapEvent mapEvent) {
if (mapEvent is MapEventMove || mapEvent is MapEventFlingAnimation) {
_resetTimer();
}
});
// MapControllerの初期化が完了するまで待機
WidgetsBinding.instance.addPostFrameCallback((_) {
debugPrint("MapControllerの初期化が完了");
setState(() {
indexController.isMapControllerReady.value = true;
});
// MapControllerの初期化が完了したら、IndexControllerのonInitを呼び出す
//indexController.checkPermission();
PermissionController.checkAndRequestPermissions();
});
late MapResetController mapResetController = MapResetController();
mapResetController.resetIdleTimer = _resetIdleTimer;
Get.put(mapResetController);
// indexController.mapController = MapController(initCompleter: mapControllerCompleter);
}
void _resetIdleTimer() {
debugPrint("_resetIdleTimer...");
_timer?.cancel();
_startIdleTimer();
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this); // added
mapController?.dispose();
_timer?.cancel();
super.dispose();
}
// added by Akira
/*
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
debugPrint("MapWidget:didChangeAppLifecycleState...state=${state}");
if (state == AppLifecycleState.resumed) {
_resetTimer();
}
}
*/
// _centerMapOnUser を10秒間でコール
void _startIdleTimer() {
//debugPrint("_startIdleTimer ....");
final settingsController = Get.find<SettingsController>();
if (!settingsController.autoReturnDisabled.value) {
_timer = Timer(settingsController.timerDuration.value, _centerMapOnUser);
}
}
// タイマーをリセットして_startIdleTimer をコール
void _resetTimer() {
//debugPrint("_resetTimer ....");
_timer?.cancel();
_startIdleTimer();
}
// マッぷを現在位置を中心にする。
void _centerMapOnUser() {
//debugPrint("_centerMapOnUser ....");
if (mounted) {
//debugPrint("_centerMapOnUser => centering ....");
destinationController.centerMapToCurrentLocation();
}
}
Future<void> _initMarkers() async {
List<Marker> markers = await _getMarkers();
setState(() {
_markers = markers;
});
}
Future<List<Marker>> _getMarkers() async {
List<Marker> markers = [];
if (indexController.locations.isNotEmpty && indexController.locations[0].features.isNotEmpty) {
for (var feature in indexController.locations[0].features) {
GeoJSONMultiPoint point = feature!.geometry as GeoJSONMultiPoint;
LatLng latLng = LatLng(point.coordinates[0][1], point.coordinates[0][0]);
markers.add(Marker(
point: latLng,
width: 30.0,
height: 30.0,
child: getMarkerShape(feature),
));
}
}
return markers;
}
// Widget getMarkerShape(GeoJSONFeature i, BuildContext context) {
Widget getMarkerShape(GeoJSONFeature i) {
GeoJSONMultiPoint p = i.geometry as GeoJSONMultiPoint;
return InkWell(
onTap: () {
GeoJSONFeature? fs = indexController.getFeatureForLatLong(
p.coordinates[0][1], p.coordinates[0][0]);
if (fs != null) {
indexController.currentFeature.clear();
indexController.currentFeature.add(fs);
Destination des = destinationController.festuretoDestination(fs);
DatabaseHelper db = DatabaseHelper.instance;
db.getDestinationByLatLon(des.lat!, des.lon!).then((value) {
destinationController.shouldShowBottomSheet = false;
showModalBottomSheet(
constraints:
BoxConstraints.loose(Size(Get.width, Get.height * 0.85)),
context: context,
isScrollControlled: true,
isDismissible: true,
builder: ((context) => BottomSheetNew(
destination: des, isAlreadyCheckedIn: value.isNotEmpty)),
).whenComplete(() {
destinationController.shouldShowBottomSheet = true;
destinationController.skipGps = false;
});
});
}
},
child: Stack(
fit: StackFit.expand,
children: [
Container( // マーカー
height: 32,
width: 32,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.transparent,
border: Border.all(
color: i.properties!['buy_point'] > 0
? Colors.blue
: Colors.red,
width: 3,
style: BorderStyle.solid,
),
),
child: Stack(
alignment: Alignment.center,
children: [
const Icon(
Icons.circle,
size: 6.0,
),
i.properties!['cp'] <= 0 ? Transform.translate
(
offset: const Offset(-3, 0), //-3
child: Transform.rotate(
alignment: Alignment.centerLeft,
origin: Offset.fromDirection(1, 26),
angle: 270 * pi / 180,
child: const Icon(
Icons.play_arrow_outlined,
color: Colors.red,
size: 70,
)),
)
: Container(
color: Colors.transparent,
),
],
),
),
Transform.translate(
offset: const Offset(30, 0), // 30,0
child: Align(
alignment: Alignment.center,
child: Container (
//width: 80, // 幅を指定
//height: 60, // 40
//color: Colors.purple.withOpacity(0.2),
color: Colors.transparent,
//child: Text(' ').
//constraints: const BoxConstraints(maxWidth: 60.0), // 最大幅を設定
//constraints: BoxConstraints(maxWidth: maxWidth), // 最大幅を設定
//color: Colors.purple.withOpacity(0.2),
child: Stack(
children: <Widget>[
Text( // アウトライン
TextUtils.getDisplayTextFeture(i),
style: TextStyle(
fontSize: 16, // 16
fontWeight: FontWeight.w700,
overflow: TextOverflow.visible,
//height: 1.2,
foreground: Paint()
..style = PaintingStyle.stroke
..strokeWidth = 1 // 2
..color = Colors.white,
),
maxLines: 1, // テキストを1行に制限
softWrap: false, // テキストの折り返しを無効化
),
Text( // テキスト
TextUtils.getDisplayTextFeture(i),
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
overflow: TextOverflow.visible,
//fontWeight: FontWeight.bold,
//height: 1.2,
color: Colors.black,
),
maxLines: 1, // テキストを1行に制限
softWrap: false, // テキストの折り返しを無効化
),
],
),
),
),
)
],
),
);
}
List<LatLng>? getPoints() {
List<LatLng> pts = [];
for (PointLatLng p in indexController.routePoints) {
LatLng l = LatLng(p.latitude, p.longitude);
pts.add(l);
}
return pts;
}
@override
Widget build(BuildContext context) {
final settingsController = Get.find<SettingsController>(); // これを追加
//final PopupController popupController = PopupController();
return Stack(
children: [
Obx(() => indexController.isLoading.value == true
? const Padding(
padding: EdgeInsets.only(top: 60.0),
child: CircularProgressIndicator(),
)
: FlutterMap(
mapController: mapController,
//mapController: indexController.mapController,
options: MapOptions(
maxZoom: 18.4,
onMapReady: () {
_initMarkers();
//indexController.isMapControllerReady.value = true;
},
initialCenter:
const LatLng(37.15319600454702, 139.58765950528198),
bounds: indexController.currentBound.isNotEmpty
? indexController.currentBound[0]
: LatLngBounds.fromPoints([
const LatLng(35.03999881162295, 136.40587119778962),
const LatLng(36.642756778706904, 137.95226720406063)
]),
initialZoom: 1,
interactiveFlags:
InteractiveFlag.pinchZoom | InteractiveFlag.drag,
onPositionChanged: (MapPosition pos, hasGesture) {
if (hasGesture) {
_resetTimer();
}
indexController.currentBound = [pos.bounds!];
},
onMapEvent: (MapEvent mapEvent) {
if (mapEvent is MapEventMove) {
destinationController.shouldShowBottomSheet = true;
}
},
//onTap: (_, __) => popupController.hideAllPopups(),
),
children: [
const BaseLayer(),
Obx(
() => indexController.routePointLenght > 0
? PolylineLayer(
polylines: [
Polyline(
points: getPoints()!,
strokeWidth: 6.0,
color: Colors.indigo,
),
],
)
: Container(),
),
CurrentLocationLayer(
positionStream: locationController
.locationMarkerPositionStreamController.stream,
//alignDirectionOnUpdate: AlignOnUpdate.never,
style: const LocationMarkerStyle(
marker: Stack(
children: [
CircleAvatar(
radius: 13.5,
backgroundColor: Colors.blue,
child: Icon(Icons.navigation, color: Colors.white),
),
],
),
markerSize: Size(27, 27),
markerDirection: MarkerDirection.heading,
),
//child: const Icon(Icons.navigation),
),
FutureBuilder<List<Marker>>(
future: indexController.locations.isNotEmpty ? _getMarkers() : null,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return const Center(child: Text('マーカーの読み込みに失敗しました'));
} else {
return MarkerLayer(markers: snapshot.data ?? []);
}
},
),
//MarkerLayer(markers: indexController.locations.isNotEmpty ? _getMarkers() : []),
],
)),
const Positioned(top: 0, left: 0, child: GameStateWidget()),
const Positioned(bottom: 10, right: 10, child: CurrentPosition()),
StreamBuilder<LocationMarkerPosition?>(
stream: locationController.locationMarkerPositionStream,
builder: (context, snapshot) {
if (!snapshot.hasData) {
//debugPrint("====== Not display current marker");
curr_marker_display = true;
}else if(curr_marker_display){
debugPrint("====== Displayed current marker");
curr_marker_display = false;
}
return Container();
},
)
],
);
}
}
import 'package:flutter/material.dart';
import 'package:rogapp/widgets/GameState/Colors.dart';
enum ConnectionStatus { none, mobile, wifi }
class ConnectionStatusIndicator extends StatelessWidget {
final ConnectionStatus connectionStatus;
final bool minimized;
const ConnectionStatusIndicator({
Key? key,
required this.connectionStatus,
this.minimized = false,
}) : super(key: key);
@override
Widget build(BuildContext context) {
Color backgroundColor;
IconData iconData;
String text;
switch (connectionStatus) {
case ConnectionStatus.none:
backgroundColor = JapaneseColors.ume;
iconData = Icons.signal_cellular_off;
text = 'No Connection';
break;
case ConnectionStatus.mobile:
backgroundColor = JapaneseColors.take;
iconData = Icons.signal_cellular_alt;
text = 'Mobile Data';
break;
case ConnectionStatus.wifi:
backgroundColor = JapaneseColors.sora;
iconData = Icons.wifi;
text = 'Wi-Fi';
break;
default:
backgroundColor = Colors.grey; // Fallback color
iconData = Icons.device_unknown;
text = 'Unknown';
}
return Container(
height: minimized ? 40 : null,
width: minimized ? 40 : null,
padding:
minimized ? null : EdgeInsets.symmetric(vertical: 8, horizontal: 16),
decoration: BoxDecoration(
color: backgroundColor,
shape: minimized ? BoxShape.circle : BoxShape.rectangle,
borderRadius: minimized ? null : BorderRadius.circular(10),
),
child: minimized
? Center(
child: Icon(iconData, color: Colors.white, size: 24),
)
: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(iconData, color: Colors.white),
SizedBox(width: 8),
Text(
text,
style: TextStyle(
color: Colors.white, fontWeight: FontWeight.bold),
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:rogapp/widgets/GameState/Colors.dart';
class LocationVisitedWidget extends StatelessWidget {
final int count;
final bool minimized;
const LocationVisitedWidget(
{Key? key, required this.count, this.minimized = false})
: super(key: key);
@override
Widget build(BuildContext context) {
if (minimized) {
return Container(
height: 40,
width: 40,
decoration: BoxDecoration(
color: JapaneseColors.mizu,
shape: BoxShape.circle,
),
child: Center(
child: Text(
'$count',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
),
);
} else {
return Container(
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 8),
decoration: BoxDecoration(
color: JapaneseColors.matcha,
borderRadius: BorderRadius.circular(10),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.check_circle_outline, color: Colors.white, size: 24),
SizedBox(width: 8),
Text(
'$count チェックイン', // "X Check-ins" in Japanese
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16),
),
],
),
);
}
}
}
import 'package:flutter/material.dart';
class JapaneseColors {
static const Color mizu = Color(0xFFA4DDED); // Mizu (light blue)
static const Color matcha = Color(0xFFC5E1A5);
static const Color ume = Color(0xFFE1A8A8); // Ume (plum)
static const Color take = Color(0xFF7B8D42); // Take (bamboo)
static const Color sora = Color(0xFFA1CAF1);
static const Color indigo = Color(0xFF264653); // Aizome
static const Color sakuraPink = Color(0xFFFAD2E1); // Sakura-iro
/// Matcha (green tea)
}
import 'package:flutter/material.dart';
import 'package:rogapp/widgets/GameState/CheckinState.dart';
import 'package:rogapp/widgets/GameState/game_on_off.dart';
class DashboardWidget extends StatelessWidget {
final bool gameStarted;
final int locationsVisited;
final bool isMinimized;
const DashboardWidget({
Key? key,
required this.gameStarted,
required this.locationsVisited,
this.isMinimized = false,
}) : super(key: key);
@override
Widget build(BuildContext context) {
List<Widget> widgets = [
GameStatusIndicator(gameStarted: gameStarted, minimized: isMinimized),
SizedBox(
height: isMinimized ? 0 : 8, width: isMinimized ? 8 : 0), // Spacing
LocationVisitedWidget(count: locationsVisited, minimized: isMinimized),
];
return Container(
padding: EdgeInsets.all(isMinimized ? 8 : 16),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 5,
blurRadius: 7,
offset: Offset(0, 3),
),
],
borderRadius: BorderRadius.circular(10),
),
child: isMinimized
? Row(
mainAxisSize: MainAxisSize.min,
children: widgets,
)
: Column(
mainAxisSize: MainAxisSize.min,
children: widgets,
),
);
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rogapp/widgets/GameState/Colors.dart';
class GameStatusIndicator extends StatelessWidget {
final bool gameStarted;
final bool minimized;
const GameStatusIndicator(
{Key? key, required this.gameStarted, this.minimized = true})
: super(key: key);
@override
Widget build(BuildContext context) {
// Icons to show based on the game status
IconData iconData =
gameStarted ? Icons.stop_circle : Icons.play_circle_filled;
// Text to show based on the game status
String text = gameStarted ? 'in_game'.tr : 'start_game'.tr;
// Layout for minimized view
if (minimized) {
return Container(
height: 40, // Square size
width: 40, // Square size
decoration: BoxDecoration(
color:
gameStarted ? JapaneseColors.indigo : JapaneseColors.sakuraPink,
shape: BoxShape
.circle, // Making it circle when minimized for a more distinct look
),
child: Icon(iconData, color: Colors.white),
);
}
// Layout for expanded view
return Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: gameStarted ? JapaneseColors.indigo : JapaneseColors.sakuraPink,
borderRadius: BorderRadius.circular(10),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(iconData, color: Colors.white),
SizedBox(width: 8),
Text(
text,
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:rogapp/pages/search/search_page.dart';
class FakeSearch extends StatelessWidget {
const FakeSearch({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return InkWell(
onTap: (){
Navigator.push(context, MaterialPageRoute(builder: (context) => SearchPage()));
},
child: Container(
height: 35,
decoration: BoxDecoration(
border: Border.all(color:Colors.blue, width: 1.4),
borderRadius: BorderRadius.circular(25)
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: Icon(Icons.search, color: Colors.grey,),
),
const Text("What are you looking for", style: TextStyle(fontSize: 16, color: Colors.grey),),
Container(
height: 32,
width: 75,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(25),
),
child: const Center(child: Text("Search", style: TextStyle(fontSize: 16, color: Colors.white),)),
)
],
),
),
);
}
}import 'package:flutter/material.dart';
import 'package:geojson_vi/geojson_vi.dart';
import 'package:get/get.dart';
import 'package:rogapp/model/destination.dart';
import 'package:rogapp/pages/destination/destination_controller.dart';
import 'package:rogapp/pages/index/index_controller.dart';
import 'package:rogapp/services/maxtrix_service.dart';
import 'package:rogapp/utils/const.dart';
//import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_start.dart';
//import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_goal.dart';
//import 'package:rogapp/widgets/bottom_sheets/bottom_sheet_normal_point.dart';
import 'package:rogapp/widgets/bottom_sheet_new.dart';
class ListWidget extends StatefulWidget {
const ListWidget({Key? key}) : super(key: key);
@override
State<ListWidget> createState() => _ListWidgetState();
}
// IndexControllerから目的地のリストを取得し、ListView.builderを使用してリストを表示します。
// 各リストアイテムは、目的地の画像、名前、カテゴリ、サブロケーションID、現在地からの距離を表示します。
// リストアイテムがタップされると、changeCurrentFeatureメソッドを呼び出して現在の目的地を更新し、 BottomSheetウィジェットを表示します。
// 主なロジック:
// IndexControllerから目的地のリストを取得し、ListView.builderを使用してリストを構築します。
// getImageメソッドを使用して、目的地の画像を取得し表示します。画像が存在しない場合は、デフォルトの画像を表示します。
// matrixDistanceメソッドを使用して、現在地から目的地までの距離を計算し表示します。
// リストアイテムがタップされると、changeCurrentFeatureメソッドを呼び出して現在の目的地を更新し、showModalBottomSheetを使用してBottomSheetウィジェットを表示します。
//
class _ListWidgetState extends State<ListWidget> {
final IndexController indexController = Get.find<IndexController>();
final DestinationController destinationController =
Get.find<DestinationController>();
Image getImage(int index) {
if (indexController.locations[0].features[index]!.properties!["photos"] ==
null ||
indexController.locations[0].features[index]!.properties!["photos"] ==
"") {
return const Image(image: AssetImage('assets/images/empty_image.png'));
} else {
//print("==== photo index is $index ===");
String serverUrl = ConstValues.currentServer();
GeoJSONFeature gf = indexController.locations[0].features[index]!;
String photo = gf.properties!["photos"];
return Image(
image: NetworkImage('$serverUrl/media/compressed/$photo'),
errorBuilder:
(BuildContext context, Object exception, StackTrace? stackTrace) {
return Image.asset("assets/images/empty_image.png");
},
);
}
}
// 未使用?
void changeCurrentFeature(GeoJSONFeature fs) {
if (indexController.currentFeature.isNotEmpty) {
indexController.currentFeature.clear();
}
indexController.currentFeature.add(fs);
}
@override
void initState() {
super.initState();
}
Destination createDestination(GeoJSONFeature feature) {
final props = feature.properties;
GeoJSONMultiPoint pt = feature.geometry as GeoJSONMultiPoint;
return Destination(
cp: props!['cp'],
lat: pt.coordinates[0][1],
lon: pt.coordinates[0][0],
);
}
Future<String> matrixDistance(int i) async {
// Create two destinations directly from indexController.locations[0].collection
Destination desCurr = Destination(
lat: indexController.currentLat, lon: indexController.currentLon);
//Destination dest1 = createDestination(indexController.locations[0].collection[0]);
Destination dest2 =
createDestination(indexController.locations[0].features[i]!);
// Get the distance between these two destinations
final res = await MatrixService.getDestinations([desCurr, dest2]);
return res["routes"][0]["legs"][0]["distance"]["text"];
//print("matrix result is ${i} : ${res["routes"][0]["legs"][0]["distance"]["text"]} ");
}
Future<void> _pullRefresh() async {
//print("pull to refesh");
indexController.locations[0].features.sort((a, b) =>
(a!.properties!['cp'] as Comparable)
.compareTo(b!.properties!['cp'] as Comparable));
setState(() {});
}
@override
Widget build(BuildContext context) {
debugPrint("_ListWidgetState");
return Obx(
() => indexController.locations.isNotEmpty
? RefreshIndicator(
onRefresh: _pullRefresh,
child: ListView.builder(
itemCount: indexController.locations[0].features.length,
shrinkWrap: true,
itemBuilder: (_, index) {
bool isFound = false;
for (Destination d in destinationController.destinations) {
if (indexController.locations[0].features[index]!
.properties!['location_id'] ==
d.location_id) {
isFound = true;
break;
}
}
return Card(
child: ListTile(
selected: isFound,
selectedTileColor: Colors.yellow.shade200,
onTap: () {
GeoJSONFeature gf =
indexController.locations[0].features[index]!;
Destination des =
destinationController.festuretoDestination(gf);
changeCurrentFeature(gf);
Widget bottomSheet = BottomSheetNew(destination: des);
/*
if (des.cp == -1 || des.cp == 0) {
bottomSheet = BottomSheetStart(destination: des);
} else if (des.cp == -2 || des.cp == 0) {
bottomSheet = BottomSheetGoal(destination: des);
} else {
bottomSheet = BottomSheetNormalPoint(destination: des);
}
*/
showModalBottomSheet(
constraints: BoxConstraints.loose(
Size(Get.width, Get.height * 0.85)),
isScrollControlled: true,
context: context,
builder: ((context) => bottomSheet ),
);
},
leading: getImage(index),
title: indexController.locations[0].features[index]!
.properties!['location_name'] !=
null
? Text(indexController.locations[0].features[index]!
.properties!['location_name']
.toString())
: const Text(""),
subtitle: indexController.locations[0].features[index]!
.properties!['category'] !=
null
? Text(indexController.locations[0].features[index]!
.properties!['category'])
: const Text(""),
trailing: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
indexController.locations[0].features[index]!
.properties!['sub_loc_id'] !=
null
? Text(indexController.locations[0]
.features[index]!.properties!['sub_loc_id'])
: const Text(""),
SizedBox(
width: 100,
child: FutureBuilder<String>(
future: matrixDistance(index),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasError) {
return const Text("-");
} else {
return Text(
snapshot.data ?? '',
style: const TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold),
);
}
},
),
)
],
)),
);
},
),
)
: const SizedBox(
width: 0,
height: 0,
),
);
}
}
import 'package:geojson_vi/geojson_vi.dart';
import 'package:get/get_state_manager/get_state_manager.dart';
class BottomSheetController extends GetxController {
List<GeoJSONFeature>? currentFeature = <GeoJSONFeature>[];
BottomSheetController({this.currentFeature});
}
//import 'dart:ffi';
//import 'dart:math';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rogapp/model/destination.dart';
import 'package:rogapp/pages/destination/destination_controller.dart';
import 'package:rogapp/pages/index/index_controller.dart';
import 'package:rogapp/services/DatabaseService.dart';
//import 'package:rogapp/utils/database_helper.dart';
import 'package:rogapp/widgets/GameState/CheckinState.dart';
import 'package:rogapp/widgets/GameState/ConnectionStatus.dart';
//import 'package:rogapp/widgets/GameState/DashboardWidget.dart';
import 'package:rogapp/widgets/GameState/game_on_off.dart';
//import 'package:rogapp/widgets/GameState/Colors.dart';
import 'package:rogapp/widgets/gps_status.dart';
import 'package:rogapp/utils/location_controller.dart';
class GameStateManager {
static final GameStateManager _instance = GameStateManager._internal();
factory GameStateManager() {
return _instance;
}
GameStateManager._internal();
final List<String> _logs = [];
final List<VoidCallback> _listeners = [];
List<String> get logs => _logs;
void addLog(String log) {
_logs.add(log);
_notifyListeners(); // Notify all listeners
}
void clearLogs() {
_logs.clear();
_notifyListeners(); // Notify all listeners
}
void addListener(VoidCallback listener) {
_listeners.add(listener);
}
void removeListener(VoidCallback listener) {
_listeners.remove(listener);
}
void _notifyListeners() {
for (var listener in _listeners) {
listener();
}
}
}
class GameStateWidget extends StatefulWidget {
const GameStateWidget({super.key});
@override
State<GameStateWidget> createState() => _GameStateWidgetState();
}
class _GameStateWidgetState extends State<GameStateWidget> {
final GameStateManager gameStateManager = GameStateManager();
final IndexController indexController = Get.find<IndexController>();
final DestinationController destinationController =
Get.find<DestinationController>();
@override
void initState() {
super.initState();
gameStateManager.addListener(_updateLogs);
}
@override
void dispose() {
gameStateManager.removeListener(_updateLogs);
super.dispose();
}
void _updateLogs() {
Future.delayed(Duration.zero, () {
if (mounted) {
setState(() {});
}
});
}
void toggleExpanded() {
setState(() {
isExpanded = !isExpanded;
});
}
void clearLogs() {
gameStateManager.clearLogs();
}
bool isExpanded = false;
@override
Widget build(BuildContext context) {
final DatabaseService dbService = DatabaseService();
//final LocationController locationController = Get.find<LocationController>();
return Container(
width: MediaQuery.of(context).size.width,
decoration: const BoxDecoration(color: Colors.black12),
child: GestureDetector(
onTap: toggleExpanded,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
color: isExpanded ? Colors.black54 : Colors.black12,
height: isExpanded ? 160.0 : 48.0, // Adjust sizes as needed
child: Column(
children: [
// Top bar with clear button
if (isExpanded)
Container(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
color: Colors.blueGrey, // Adjust color as needed
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('game_status'.tr, style: TextStyle(color: Colors.white)),
IconButton(
icon: const Icon(Icons.clear, color: Colors.white),
onPressed: toggleExpanded,
),
],
),
),
// Log messages
Expanded(
child: SingleChildScrollView(
child: Wrap(
alignment: WrapAlignment.spaceEvenly,
children: [
Obx(() => Padding(
padding: const EdgeInsets.all(4.0),
child: GameStatusIndicator(
gameStarted: destinationController.isInRog.value,
minimized: !isExpanded,
),
)),
Padding(
padding: const EdgeInsets.all(4.0),
child: StreamBuilder<List<Destination>>(
stream: dbService.destinationUpdatesStream,
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return const CircularProgressIndicator();
} else if (snapshot.hasError) {
return LocationVisitedWidget(
count: 0,
minimized: !isExpanded,
);
} else if (snapshot.hasData) {
return LocationVisitedWidget(
count: snapshot.data!.length,
minimized: !isExpanded,
);
} else {
return LocationVisitedWidget(
count: 0,
minimized: !isExpanded,
);
}
},
),
// child: LocationVisitedWidget(
// count:
// minimized: !isExpanded,
// ),
),
Padding(
padding: const EdgeInsets.all(4.0),
child: Obx(() => ConnectionStatusIndicator(
connectionStatus: (indexController
.connectionStatusName.value ==
"wifi" ||
indexController
.connectionStatusName.value ==
"mobile")
? indexController.connectionStatusName.value ==
"wifi"
? ConnectionStatus.wifi
: ConnectionStatus.mobile
: ConnectionStatus.none,
minimized: !isExpanded)),
), // Expanded view
Padding(
padding: const EdgeInsets.all(4.0),
child:GpsSignalStrengthIndicator(
locationController: Get.find<LocationController>(),
minimized: !isExpanded, // isExpanded はあなたのロジックに依存した変数),
)
),
],
),
// child: Obx(
// () => DashboardWidget(
// gameStarted: destinationController.isInRog.value,
// locationsVisited: 3,
// isMinimized: false,
// ),
),
),
],
),
),
),
);
}
}
import 'package:flutter/material.dart';
class CPasswordTextField extends StatefulWidget {
const CPasswordTextField(
{super.key, required this.cFocusNode, required this.cController});
final FocusNode cFocusNode;
final TextEditingController cController;
@override
State<CPasswordTextField> createState() => _CPasswordTextFieldState();
}
class _CPasswordTextFieldState extends State<CPasswordTextField> {
var _isVisible = false;
@override
Widget build(BuildContext context) {
return TextFormField(
controller: widget.cController,
textInputAction: TextInputAction.go,
obscureText: !_isVisible,
validator: (value) {
if (value == null || value.isEmpty || value.length < 5) {
return "Need a valied password with more than 4 charectors";
}
return null;
},
decoration: InputDecoration(
//filled: true,
//fillColor: Theme.of(context).colorScheme.tertiaryContainer,
hintText: "Enter password",
labelText: "Password",
labelStyle: TextStyle(
color: Theme.of(context).colorScheme.primary, fontSize: 16),
suffixIcon: IconButton(
onPressed: () {
setState(() {
_isVisible = !_isVisible;
});
},
icon: _isVisible
? const Icon(Icons.visibility)
: const Icon(Icons.visibility_off)),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide(
width: 1, color: Theme.of(context).colorScheme.secondary)),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide(
width: 2, color: Theme.of(context).colorScheme.primary))));
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:rogapp/pages/destination/destination_controller.dart';
import 'package:rogapp/routes/app_pages.dart'; // これを追加
class CurrentPosition extends StatefulWidget {
const CurrentPosition({super.key});
@override
State<CurrentPosition> createState() => _CurrentPositionState();
}
class _CurrentPositionState extends State<CurrentPosition> {
final DestinationController destinationController =
Get.find<DestinationController>();
void _onLongPress() async {
PermissionStatus status = await Permission.location.status;
if (!status.isGranted) {
await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('位置情報の許可が必要です'),
content: Text('現在位置を表示するには、位置情報の許可が必要です。「設定」からアプリの権限を許可してください。'),
actions: [
TextButton(
child: Text('キャンセル'),
onPressed: () => Navigator.of(context).pop(),
),
TextButton(
child: Text('設定'),
onPressed: () {
Navigator.of(context).pop();
openAppSettings();
},
),
],
);
},
);
} else {
Get.toNamed(AppPages.SETTINGS);
}
}
@override
Widget build(BuildContext context) {
return GestureDetector( // GestureDetectorを追加
onLongPress: _onLongPress, // 長押しイベントを追加
child: Container (
// return Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.grey, borderRadius: BorderRadius.circular(20.0)),
child: IconButton(
onPressed: () {
destinationController.centerMapToCurrentLocation();
},
icon: const Icon(
Icons.location_searching,
color: Colors.white,
),
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rogapp/pages/permission/permission.dart';
class PermissionHandlerScreen extends StatefulWidget {
@override
_PermissionHandlerScreenState createState() => _PermissionHandlerScreenState();
}
class _PermissionHandlerScreenState extends State<PermissionHandlerScreen> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await PermissionController.checkAndRequestPermissions();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('権限の確認'),
),
body: Center(
child: Text('権限の確認中...'),
),
);
}
}import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
class BaseLayer extends StatelessWidget {
const BaseLayer({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return TileLayer(
urlTemplate: "https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png",
tileProvider: FMTC.instance('OpenStreetMap (A)').getTileProvider(
settings: FMTCTileProviderSettings(
behavior: CacheBehavior.values.byName('cacheFirst'),
cachedValidDuration: const Duration(days: 14),
),
),
);
}
}
// var Esri_WorldImagery = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
// attribution: 'Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
// });import 'package:flutter/material.dart';
class LogManager {
static final LogManager _instance = LogManager._internal();
factory LogManager() {
return _instance;
}
LogManager._internal();
List<String> _logs = [];
List<VoidCallback> _listeners = [];
List<String> _operationLogs = [];
List<String> get operationLogs => _operationLogs;
List<String> get logs => _logs;
void addLog(String log) {
_logs.add(log);
_notifyListeners(); // Notify all listeners
}
void clearLogs() {
_logs.clear();
_notifyListeners(); // Notify all listeners
}
void addOperationLog(String log) {
_operationLogs.add(log);
_notifyListeners();
}
void clearOperationLogs() {
_operationLogs.clear();
_notifyListeners();
}
void addListener(VoidCallback listener) {
_listeners.add(listener);
}
void removeListener(VoidCallback listener) {
_listeners.remove(listener);
}
void _notifyListeners() {
for (var listener in _listeners) {
listener();
}
}
}
class DebugWidget extends StatefulWidget {
const DebugWidget({super.key});
@override
State<DebugWidget> createState() => _DebugWidgetState();
}
class _DebugWidgetState extends State<DebugWidget> {
final LogManager logManager = LogManager();
@override
void initState() {
super.initState();
logManager.addListener(_updateLogs);
}
@override
void dispose() {
logManager.removeListener(_updateLogs);
super.dispose();
}
void _updateLogs() {
Future.delayed(Duration.zero, () {
if (mounted) {
setState(() {});
}
});
}
void toggleExpanded() {
setState(() {
isExpanded = !isExpanded;
});
}
void clearLogs() {
logManager.clearLogs();
}
bool isExpanded = false;
@override
Widget build(BuildContext context) {
return Positioned(
left: 0,
right: 0,
bottom: 0,
child: GestureDetector(
onTap: toggleExpanded,
child: AnimatedContainer(
duration: Duration(milliseconds: 200),
color: Colors.black54,
height: isExpanded ? 450.0 : 50.0, // Adjust sizes as needed
child: Column(
children: [
// Top bar with clear button
if (isExpanded)
Container(
padding: EdgeInsets.symmetric(horizontal: 10.0),
color: Colors.blueGrey, // Adjust color as needed
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Debug Logs', style: TextStyle(color: Colors.white)),
IconButton(
icon: Icon(Icons.clear, color: Colors.white),
onPressed: clearLogs,
),
],
),
),
// Log messages
Expanded(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: logManager.logs.reversed
.map((log) => Text(
"${DateTime.now().hour}:${DateTime.now().minute}:${DateTime.now().second}:${DateTime.now().microsecond} - $log",
style: const TextStyle(color: Colors.white)))
.toList(),
),
),
),
],
),
),
),
);
}
}