Commit db64bd24 authored by Clouard Regis's avatar Clouard Regis
Browse files

Added open course widget

parent 0587db06
......@@ -27,7 +27,7 @@ sa localisation et un mode promenade où l’orienteur est positionné sur la ca
## Téléchargement
[Télécharger sur Google Play](https://play.google.com/store/apps/details?id=fr.ensicaen.ecole.vikazimut)
[Télécharger sur Google Play](https://play.google.com/store/apps/details?id=fr.ensicaen.vikazimut)
## Traductions
......@@ -45,7 +45,7 @@ Licence
[Licence GPL v3](http://www.gnu.org/licenses)
## L'équipe Vikazimut ([ENSICAEN](https://www.ensicaen.fr/) - spécialité informatique)
## L'équipe Vikazimut ([ENSICAEN](https://www.ensicaen.fr/) - ([spécialité informatique](https://www.ensicaen.fr/formations/ingenieur-informatique/))
### 2015/2016
- Florentin BLANC - e-Paiement & Cybersécurité
......
import 'package:Vikazimut_flutter/map/geodesic_point.dart';
import 'package:Vikazimut_flutter/map/orienteering_map.dart';
import 'CourseControl.dart';
abstract class AbstractCourseActivity extends AppCompatActivity implements GpsLocationListener {
Menu _menu;
static final int CHOOSE_QR_CODE = 0;
static final String TIME_FORMAT_AS_STRING = "%d:%02d:%02d";
MapView _mapView;
NfcAdapter _nfcDevice;
BroadcastReceiver _compassBroadcastReceiver;
Chronometer _chronometer;
int _screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
CourseControl _courseControl;
ValidationMode _validationMode;
StartMenuItem _startMenuItem;
bool _startPressed;
TextView _lastPunchedTextView;
enum CourseFormat {PRESET_ORDER, FREE_ORDER, WALK_ORDER}
enum ValidationMode {AUTO_MODE, MANUAL_MODE}
@override
void onCreate( Bundle savedInstanceState ) {
super.onCreate(savedInstanceState);
Bundle extras = getIntent().getExtras();
assert extras != null;
OrienteeringMap map = extras.getParcelable(CourseFactory.EXTRA_SELECTED_MAP);
assert map != null;
createBroadcastReceiverForCompassOrientation();
freezeOrientation();
NfcManager systemService = (NfcManager) getSystemService(Context.NFC_SERVICE);
if (systemService != null) {
_nfcDevice = systemService.getDefaultAdapter();
}
Toolbar toolbar = createToolbarAndGetChronometer();
_chronometer = toolbar.findViewById(R.id.view_chronometer);
_lastPunchedTextView = toolbar.findViewById(R.id.view_last_punched);
_courseControl = createCourseControl(map, _chronometer);
showCourseSettingsDialog();
createMapView(map);
}
@override
void onResume() {
super.onResume();
enableNfc();
registerReceiver(_compassBroadcastReceiver, new IntentFilter(Compass.BROADCAST_ACTION));
// Temporary bug fix for https://issuetracker.google.com/issues/110237673
ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> runningAppProcesses = activityManager.getRunningAppProcesses();
if (runningAppProcesses != null) {
int importance = runningAppProcesses.get(0).importance;
if (importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
startService(new Intent(fr.ensicaen.vikazimut.course.AbstractCourseActivity.this, Compass.class));
}
}
_courseControl.onResume();
_mapView.onResume();
}
@override
void onPause() {
super.onPause();
disableNfc();
unregisterReceiver(_compassBroadcastReceiver);
stopService(new Intent(this, Compass.class));
_courseControl.onPause();
_mapView.onPause();
}
@override
void onDestroy() {
super.onDestroy();
_courseControl.quitCourse();
}
@override
void onBackPressed() {
// Disable back button: force to use the related menu item.
String message = getString(R.string.back_pressed_error) + " \"" + getString(R.string.quit_course) + "\"";
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
}
@override
bool onCreateOptionsMenu( Menu menu ) {
getMenuInflater().inflate(R.menu.menu_course, menu);
MenuCompat.setGroupDividerEnabled(menu, true);
_menu = menu;
return super.onCreateOptionsMenu(menu);
}
@override
bool onOptionsItemSelected( MenuItem item ) {
int itemId = item.getItemId();
if (itemId == R.id.action_start_stop_gps_course) {
startCourseWithGpsValidation(item);
} else if (itemId == R.id.action_use_qrcode_scanner) {
scanQrCode();
} else if (itemId == R.id.action_display_course_state) {
showCourseState();
} else if (itemId == R.id.action_change_orientation) {
changeOrientation();
} else if (itemId == R.id.action_validate_missing_control_point) {
notifyControlPointIsMissing();
} else if (itemId == R.id.action_force_missing_control_point) {
forceControlPointValidation();
} else if (itemId == R.id.action_skip_control_point) {
skipCurrentControlPoint();
} else if (itemId == R.id.action_abort_course) {
if (isStarted()) {
askStopConfirmation();
} else {
askQuitConfirmation();
}
} else if (itemId == R.id.action_help) {
displayHelpDialog();
}
return super.onOptionsItemSelected(item);
}
void startCourseWithGpsValidation( MenuItem item ) {
if (_startPressed) {
_startPressed = false;
if (isStarted()) {
askStopConfirmation();
} else {
askQuitConfirmation();
}
} else {
_startPressed = true;
_startMenuItem.changeIcon(this, item);
_courseControl.launchStartProcedure();
}
}
@override
bool onTouchEvent( MotionEvent event ) {
if (_mapView.handTouchScreenEvent(event)) {
return true;
}
return super.onTouchEvent(event);
}
Toolbar createToolbarAndGetChronometer() {
Toolbar toolbar = findViewById(R.id.toolbar_course);
setSupportActionBar(toolbar);
ActionBar supportActionBar = getSupportActionBar();
if (supportActionBar != null) {
getSupportActionBar().setDisplayShowTitleEnabled(false);
}
return toolbar;
}
CourseControl createCourseControl( OrienteeringMap map, Chronometer chronometer ) {
Course course = new Course(map);
course.setChronometer(chronometer);
return new CourseControl(this, course);
}
void createMapView( final OrienteeringMap map ) {
_mapView = findViewById(R.id.view_map);
new Thread() {
@Override
void run() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Bitmap bitmap = loadMapImage(map.getDrawableName());
final MapScaleView mapScaleView = findViewById(R.id.map_scale_view);
_mapView.setImage(bitmap, mapScaleView, map.getBoundingBox());
}
});
}
}.start();
}
void showCourseSettingsDialog() {
runOnUiThread(new Runnable() {
@override
void run() {
final fr.ensicaen.vikazimut.course.CourseSettingsView view = new fr.ensicaen.vikazimut.course.CourseSettingsView(fr.ensicaen.vikazimut.course.AbstractCourseActivity.this);
view.showAndExecActionAtTheEnd(new Runnable() {
@override
void run() {
_validationMode = view.getValidationMode();
_courseControl.setCourseType(view.getCourseFormat(), view.getValidationMode(), view.isAugmentedMode());
initializeCourseMenu(_menu, view.getValidationMode());
_startMenuItem = new StartMenuItem(fr.ensicaen.vikazimut.course.AbstractCourseActivity.this, _menu, view.getCourseFormat(), view.getValidationMode());
}
});
}
});
}
abstract void initializeCourseMenu( Menu menu, ValidationMode validationFormat );
void displayHelpDialog() {
Intent manual = new Intent(this, UserManualActivity.class);
startActivity(manual);
}
void createBroadcastReceiverForCompassOrientation() {
_compassBroadcastReceiver = new BroadcastReceiver() {
@override
void onReceive( Context context, Intent intent ) {
int azimut = intent.getIntExtra(Compass.EXTRA_COMPASS_AZIMUTH, -1);
if (azimut >= 0) {
CompassView compass = findViewById(R.id.view_compass);
compass.changeOrientation(azimut);
}
}
};
}
void enableNfc() {
if (_nfcDevice != null && _nfcDevice.isEnabled()) {
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
_nfcDevice.enableForegroundDispatch(this, pendingIntent, null, null);
}
}
void disableNfc() {
if (_nfcDevice != null && _nfcDevice.isEnabled()) {
_nfcDevice.disableForegroundDispatch(this);
}
}
Bitmap loadMapImage( String filename ) {
return OrienteeringMap.loadMapImage(getWindowManager(), filename);
}
void freezeOrientation() {
int displayOrientation = getResources().getConfiguration().orientation;
if (displayOrientation == android.content.res.Configuration.ORIENTATION_PORTRAIT) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
_screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
} else {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
_screenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
}
}
void changeOrientation() {
if (_screenOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
_screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
} else {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
_screenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
}
}
void setChronometerVisibility( boolean value ) {
if (value) {
_chronometer.setVisibility(View.VISIBLE);
} else {
_chronometer.setVisibility(View.GONE);
}
}
void displayLastPunched( int controlPointId, String controlPointName ) {
if (controlPointId == 0) {
_lastPunchedTextView.setText(getString(R.string.cp_start));
} else {
if (_validationMode == ValidationMode.MANUAL_MODE) {
_lastPunchedTextView.setText(getString(R.string.last_punched_display_with_name, controlPointId, controlPointName));
} else {
_lastPunchedTextView.setText(getString(R.string.last_punched_display, controlPointId));
}
}
}
void scanQrCode() {
Intent intent = new Intent(this, QrCodeScanner.class);
intent.putExtra(QrCodeScanner.EXTRA_DEVICE_ORIENTATION, getRequestedOrientation());
startActivityForResult(intent, CHOOSE_QR_CODE);
}
void showSuccess() {
playSound(R.raw.success);
vibrate();
}
void showFailure() {
playSound(R.raw.failure);
vibrate();
}
void playSound( int rawSound ) {
final MediaPlayer mp = MediaPlayer.create(this, rawSound);
mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion( MediaPlayer player ) {
player.reset();
player.release();
}
});
mp.start();
}
void vibrate() {
Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
if (v != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
v.vibrate(VibrationEffect.createOneShot(500, VibrationEffect.DEFAULT_AMPLITUDE));
} else {
v.vibrate(500);
}
}
}
static String getNfcContentAfterScan( Intent intent ) {
return NfcScanner.getNfcContent(intent);
}
@override
void onNewIntent( Intent intent ) {
super.onNewIntent(intent);
String controlPointId = getNfcContentAfterScan(intent);
_courseControl.punchControlPointFromDevice(controlPointId);
}
@override
void onActivityResult( int requestCode, int resultCode, Intent intent ) {
super.onActivityResult(requestCode, resultCode, intent);
if (requestCode == CHOOSE_QR_CODE && resultCode == RESULT_OK) {
String controlPointId = intent.getStringExtra(QrCodeScanner.SCAN_RESULT);
_courseControl.punchControlPointFromDevice(controlPointId);
}
}
abstract void displayResult( int itemIndex );
void handleStartCourse() {
closeAnimationsOnStartMenuItem();
}
boolean isStarted() {
return _courseControl.isStarted();
}
void handleStopCourse( Course course ) {
int recordIndex = storeResultIntoDatabase();
displayResult(recordIndex);
finishLater();
}
int storeResultIntoDatabase() {
CourseResultEntity courseResultEntity = _courseControl.buildCourseResultEntity();
DatabaseGateway database = new DatabaseGateway(getApplicationContext());
return database.saveCourseResult(courseResultEntity);
}
void finishLater() {
Thread thread = new Thread() {
@override
void run() {
try {
// Time required or closing all related windows.
Thread.sleep(500);
finish();
} catch (Exception ignored) {
finish();
}
}
};
thread.start();
}
void quitCourse() {
_courseControl.quitCourse();
closeAnimationsOnStartMenuItem();
finishLater();
}
void closeAnimationsOnStartMenuItem() {
_startMenuItem.stopAnimation();
}
List<GeodesicPoint> getWayPoints() {
return _courseControl.getWayPoints();
}
GeodesicPoint getLastLocation() {
return _courseControl.getCurrentLocation();
}
MapView getMapView() {
return _mapView;
}
void askQuitConfirmation() {
new ConfirmDialog(this,
R.string.quit_course_confirmation_title,
R.string.quit_course_confirmation_message,
new ConfirmDialog.OnQuitListener() {
@Override
public void run() {
quitCourse();
}
})
.show();
}
void askStopConfirmation() {
new ConfirmDialog(this,
R.string.stop_course_confirmation_title,
R.string.quit_course_confirmation_message,
new ConfirmDialog.OnQuitListener() {
@Override
public void run() {
_courseControl.stopCourseImmediately();
}
})
.show();
}
void showCourseState() {
int[] punchedTimes = _courseControl.getPunchedTimes();
StringBuilder text = new StringBuilder();
for (int i = 0; i < punchedTimes.length; i++) {
if (i == 0) {
text.append(getString(R.string.cp_start));
} else if (i == punchedTimes.length - 1) {
text.append(getString(R.string.cp_finish));
} else {
text.append(getString(R.string.control_point_number)).append(String.format(Locale.FRANCE, "%2d", i));
}
text.append(" : ");
if (punchedTimes[i] >= 0) {
text.append(_getTimeAsString(punchedTimes[i])).append("\n");
} else {
text.append("_:__:__\n");
}
}
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this)
.setTitle(R.string.course_state_message)
.setMessage(text.toString())
.setPositiveButton(getString(R.string.ok), null);
AlertDialog dialog = dialogBuilder.show();
TextView messageView = dialog.findViewById(android.R.id.message);
messageView.setTypeface(Typeface.MONOSPACE);
}
void notifyControlPointIsMissing() {
new RealControlPointValidationForcingHandler(this, _courseControl).execute(true);
}
void forceControlPointValidation() {
new RealControlPointValidationForcingHandler(this, _courseControl).execute(false);
}
void skipCurrentControlPoint() {
new GPSControlPointValidationForcingHandler(this, _courseControl).execute();
}
static String _getTimeAsString( int millis ) {
int hours = TimeUnit.MILLISECONDS.toHours(millis);
int minutes = TimeUnit.MILLISECONDS.toMinutes(millis) - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millis));
int seconds = TimeUnit.MILLISECONDS.toSeconds(millis) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis));
return String.format(Locale.US, TIME_FORMAT_AS_STRING, hours, minutes, seconds);
}
void displayMessage() {
_startMenuItem.displayMessage();
}
void hideMessage() {
_startMenuItem.hideMessage();
}
void displayInfoBox( String text, InfoBox.PostAction postAction ) {
new InfoBox(this, text, postAction).show();
}
}
import 'package:Vikazimut_flutter/map_choice/map_proxy.dart';
import 'package:Vikazimut_flutter/result/Result.dart';
import 'package:flutter/material.dart';
import 'package:Vikazimut_flutter/map/geodesic_point.dart';
import 'package:Vikazimut_flutter/map/orienteering_map.dart';
class Course extends StatefulWidget {
Course(MapProxy mapProxi);
class Course {
static final String GPS_COORDINATE_TIMED_FORMAT = "%.6f,%.6f,%d,%.1f";
@override
CourseState createState() => new CourseState();
}
final OrienteeringMap _map;
final CourseLegs _courseSections;
int _assistanceCount;
String _gpsRoute;
int _nextControlPointIndex;
Chronometer _chronometer;
/*
import 'package:geolocator/geolocator.dart';
StreamSubscription<Position> positionStream = Geolocator.getPositionStream(locationOptions).listen(
(Position position) {
print(position == null ? 'Unknown' : position.latitude.toString() + ', ' + position.longitude.toString());
});
*/
class CourseState extends State<Course> {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: new Text("Course Page"),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.device_hub),
tooltip: 'Code QR',
onPressed: () {
// Navigator.push(context, MaterialPageRoute(builder: (context) => Result()));
},
),
IconButton(
icon: const Icon(Icons.stop),
tooltip: 'Abandonner',
onPressed: () {
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => Result()));
},
),
]),
backgroundColor: Colors.white,
body: Container(
width: double.infinity,
height: double.infinity,
color: Colors.yellow,
child: CustomPaint(painter: FaceOutlinePainter()),
),
);
}
}
Course() {
_map = null;
_courseSections = null;
}
Course( OrienteeringMap map ) {
_map = map;
_courseSections = new fr.ensicaen.vikazimut.course.CourseLegs(_map.getControlPointsCount());
}
bool isAllIntermediateControlPointsValidated() {
return _courseSections.isAllIntermediateControlPointsValidated();
}
void setChronometer( Chronometer chronometer ) {
_chronometer = chronometer;
}
void startChronometer() {
_nextControlPointIndex = 1;
_chronometer.setBase(SystemClock.elapsedRealtime());
_chronometer.start();
}
void stopChronometer() {
_chronometer.stop();
}
void setGpsRoute( List<GeodesicPoint> wayPoints ) {
// Use relative times to get compressed times (memory saving)
StringBuilder gpxString = new StringBuilder();
for (GeodesicPoint geodesicPoint : wayPoints) {
double latitude = geodesicPoint.getLatitude();
double longitude = geodesicPoint.getLongitude();
int relativePunchedTime = geodesicPoint.getTimeInMillis();
double altitude = geodesicPoint.getAltitude();