Merge remote-tracking branch 'origin/master' into leaderboardView2

This commit is contained in:
fmittag committed 2013-08-30 15:28:06 +02:00
commit daee1914a3
108 files changed
+2161 -933

No files matched your search

@@ -142,7 +142,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
} }
private TimePoint loadTimePoint(DBObject object, FieldNames field) { private TimePoint loadTimePoint(DBObject object, FieldNames field) {
return new MillisecondsTimePoint((Long) object.get(field.name())); return new MillisecondsTimePoint(((Number) object.get(field.name())).longValue());
} }
/** /**
@@ -1006,14 +1006,15 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
RaceLog result = new RaceLogImpl(RaceLogImpl.class.getSimpleName(), identifier.getIdentifier()); RaceLog result = new RaceLogImpl(RaceLogImpl.class.getSimpleName(), identifier.getIdentifier());
try { try {
BasicDBObject query = new BasicDBObject(); BasicDBObject query = new BasicDBObject();
query.put(FieldNames.RACE_LOG_IDENTIFIER.name(), MongoUtils.escapeDollarAndDot(identifier.getIdentifier().toString())); query.put(FieldNames.RACE_LOG_IDENTIFIER.name(), TripleSerializer.serialize(identifier.getIdentifier()));
DBCollection raceLog = database.getCollection(CollectionNames.RACE_LOGS.name()); loadRaceLogEvents(result, query);
for (DBObject o : raceLog.find(query)) {
RaceLogEvent raceLogEvent = loadRaceLogEvent((DBObject) o.get(FieldNames.RACE_LOG_EVENT.name()));
if (raceLogEvent != null) // query for obsolete events
result.add(raceLogEvent); query.put(FieldNames.RACE_LOG_IDENTIFIER.name(), MongoUtils.escapeDollarAndDot(String.format("%s.%s.%s",
} identifier.getTemplate().getParentObjectName(),
identifier.getRaceColumnName(),
identifier.getFleetName())));
loadRaceLogEvents(result, query);
} catch (Throwable t) { } catch (Throwable t) {
// something went wrong during DB access; report, then use empty new race log // something went wrong during DB access; report, then use empty new race log
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load recorded race log data. Check MongoDB settings."); logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load recorded race log data. Check MongoDB settings.");
@@ -1022,6 +1023,16 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
return result; return result;
} }
private void loadRaceLogEvents(RaceLog result, BasicDBObject query) {
DBCollection raceLog = database.getCollection(CollectionNames.RACE_LOGS.name());
for (DBObject o : raceLog.find(query)) {
RaceLogEvent raceLogEvent = loadRaceLogEvent((DBObject) o.get(FieldNames.RACE_LOG_EVENT.name()));
if (raceLogEvent != null) {
result.add(raceLogEvent);
}
}
}
public RaceLogEvent loadRaceLogEvent(DBObject dbObject) { public RaceLogEvent loadRaceLogEvent(DBObject dbObject) {
TimePoint timePoint = loadTimePoint(dbObject); TimePoint timePoint = loadTimePoint(dbObject);
TimePoint createdAt = loadTimePoint(dbObject, FieldNames.RACE_LOG_EVENT_CREATED_AT); TimePoint createdAt = loadTimePoint(dbObject, FieldNames.RACE_LOG_EVENT_CREATED_AT);
@@ -1190,7 +1201,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
private CourseBase loadCourseData(BasicDBList dbCourseList, String courseName) { private CourseBase loadCourseData(BasicDBList dbCourseList, String courseName) {
if (courseName == null) { if (courseName == null) {
courseName = "Course template"; courseName = "Course";
} }
CourseBase courseData = new CourseDataImpl(courseName); CourseBase courseData = new CourseDataImpl(courseName);
int i = 0; int i = 0;
@@ -536,7 +536,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogFlagEvent flagEvent) { public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogFlagEvent flagEvent) {
BasicDBObject result = new BasicDBObject(); BasicDBObject result = new BasicDBObject();
result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), MongoUtils.escapeDollarAndDot(raceLogIdentifier.getIdentifier().toString())); result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), TripleSerializer.serialize(raceLogIdentifier.getIdentifier()));
result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogFlagEvent(flagEvent)); result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogFlagEvent(flagEvent));
return result; return result;
@@ -544,7 +544,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogStartTimeEvent startTimeEvent) { public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogStartTimeEvent startTimeEvent) {
BasicDBObject result = new BasicDBObject(); BasicDBObject result = new BasicDBObject();
result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), MongoUtils.escapeDollarAndDot(raceLogIdentifier.getIdentifier().toString())); result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), TripleSerializer.serialize(raceLogIdentifier.getIdentifier()));
result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogStartTimeEvent(startTimeEvent)); result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogStartTimeEvent(startTimeEvent));
return result; return result;
@@ -552,7 +552,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogPassChangeEvent passChangeEvent) { public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogPassChangeEvent passChangeEvent) {
BasicDBObject result = new BasicDBObject(); BasicDBObject result = new BasicDBObject();
result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), MongoUtils.escapeDollarAndDot(raceLogIdentifier.getIdentifier().toString())); result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), TripleSerializer.serialize(raceLogIdentifier.getIdentifier()));
result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogPassChangeEvent(passChangeEvent)); result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogPassChangeEvent(passChangeEvent));
return result; return result;
@@ -560,7 +560,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogRaceStatusEvent raceStatusEvent) { public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogRaceStatusEvent raceStatusEvent) {
BasicDBObject result = new BasicDBObject(); BasicDBObject result = new BasicDBObject();
result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), MongoUtils.escapeDollarAndDot(raceLogIdentifier.getIdentifier().toString())); result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), TripleSerializer.serialize(raceLogIdentifier.getIdentifier()));
result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogRaceStatusEvent(raceStatusEvent)); result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogRaceStatusEvent(raceStatusEvent));
return result; return result;
@@ -568,63 +568,63 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogCourseAreaChangedEvent courseAreaChangedEvent) { public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogCourseAreaChangedEvent courseAreaChangedEvent) {
BasicDBObject result = new BasicDBObject(); BasicDBObject result = new BasicDBObject();
result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), MongoUtils.escapeDollarAndDot(raceLogIdentifier.getIdentifier().toString())); result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), TripleSerializer.serialize(raceLogIdentifier.getIdentifier()));
result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogCourseAreaChangedEvent(courseAreaChangedEvent)); result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogCourseAreaChangedEvent(courseAreaChangedEvent));
return result; return result;
} }
public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogCourseDesignChangedEvent courseDesignChangedEvent) { public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogCourseDesignChangedEvent courseDesignChangedEvent) {
BasicDBObject result = new BasicDBObject(); BasicDBObject result = new BasicDBObject();
result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), MongoUtils.escapeDollarAndDot(raceLogIdentifier.getIdentifier().toString())); result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), TripleSerializer.serialize(raceLogIdentifier.getIdentifier()));
result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogCourseDesignChangedEvent(courseDesignChangedEvent)); result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogCourseDesignChangedEvent(courseDesignChangedEvent));
return result; return result;
} }
public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogFinishPositioningListChangedEvent finishPositioningListChangedEvent) { public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogFinishPositioningListChangedEvent finishPositioningListChangedEvent) {
BasicDBObject result = new BasicDBObject(); BasicDBObject result = new BasicDBObject();
result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), MongoUtils.escapeDollarAndDot(raceLogIdentifier.getIdentifier().toString())); result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), TripleSerializer.serialize(raceLogIdentifier.getIdentifier()));
result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogFinishPositioningListChangedEvent(finishPositioningListChangedEvent)); result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogFinishPositioningListChangedEvent(finishPositioningListChangedEvent));
return result; return result;
} }
public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogFinishPositioningConfirmedEvent finishPositioningConfirmedEvent) { public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogFinishPositioningConfirmedEvent finishPositioningConfirmedEvent) {
BasicDBObject result = new BasicDBObject(); BasicDBObject result = new BasicDBObject();
result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), MongoUtils.escapeDollarAndDot(raceLogIdentifier.getIdentifier().toString())); result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), TripleSerializer.serialize(raceLogIdentifier.getIdentifier()));
result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogFinishPositioningConfirmedEvent(finishPositioningConfirmedEvent)); result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogFinishPositioningConfirmedEvent(finishPositioningConfirmedEvent));
return result; return result;
} }
public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogPathfinderEvent pathfinderEvent) { public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogPathfinderEvent pathfinderEvent) {
BasicDBObject result = new BasicDBObject(); BasicDBObject result = new BasicDBObject();
result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), MongoUtils.escapeDollarAndDot(raceLogIdentifier.getIdentifier().toString())); result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), TripleSerializer.serialize(raceLogIdentifier.getIdentifier()));
result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogPathfinderEvent(pathfinderEvent)); result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogPathfinderEvent(pathfinderEvent));
return result; return result;
} }
public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogGateLineOpeningTimeEvent gateLineOpeningTimeEvent) { public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogGateLineOpeningTimeEvent gateLineOpeningTimeEvent) {
BasicDBObject result = new BasicDBObject(); BasicDBObject result = new BasicDBObject();
result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), MongoUtils.escapeDollarAndDot(raceLogIdentifier.getIdentifier().toString())); result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), TripleSerializer.serialize(raceLogIdentifier.getIdentifier()));
result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogGateLineOpeningTimeEvent(gateLineOpeningTimeEvent)); result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogGateLineOpeningTimeEvent(gateLineOpeningTimeEvent));
return result; return result;
} }
public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogStartProcedureChangedEvent event) { public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogStartProcedureChangedEvent event) {
DBObject result = new BasicDBObject(); DBObject result = new BasicDBObject();
result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), MongoUtils.escapeDollarAndDot(raceLogIdentifier.getIdentifier().toString())); result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), TripleSerializer.serialize(raceLogIdentifier.getIdentifier()));
result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogStartProcedureChangedEvent(event)); result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogStartProcedureChangedEvent(event));
return result; return result;
} }
public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogProtestStartTimeEvent event) { public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogProtestStartTimeEvent event) {
DBObject result = new BasicDBObject(); DBObject result = new BasicDBObject();
result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), MongoUtils.escapeDollarAndDot(raceLogIdentifier.getIdentifier().toString())); result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), TripleSerializer.serialize(raceLogIdentifier.getIdentifier()));
result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogProtestStartTimeEvent(event)); result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogProtestStartTimeEvent(event));
return result; return result;
} }
public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogWindFixEvent event) { public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogWindFixEvent event) {
DBObject result = new BasicDBObject(); DBObject result = new BasicDBObject();
result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), MongoUtils.escapeDollarAndDot(raceLogIdentifier.getIdentifier().toString())); result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), TripleSerializer.serialize(raceLogIdentifier.getIdentifier()));
result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogWindFix(event)); result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogWindFix(event));
return result; return result;
} }
@@ -0,0 +1,15 @@
package com.sap.sailing.domain.persistence.impl;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.sap.sailing.domain.common.impl.Util.Triple;
public class TripleSerializer {
public static DBObject serialize(Triple<String, String, String> value) {
DBObject container = new BasicDBObject();
container.put("a", value.getA());
container.put("b", value.getB());
container.put("c", value.getC());
return container;
}
}
@@ -831,4 +831,10 @@ public class MockedTrackedRace implements DynamicTrackedRace {
// TODO Auto-generated method stub // TODO Auto-generated method stub
} }
@Override
public TimePoint getStartTimeReceived() {
// TODO Auto-generated method stub
return null;
}
} }
@@ -539,4 +539,10 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
} }
@Override
public TimePoint getStartTimeReceived() {
// TODO Auto-generated method stub
return null;
}
} }
@@ -11,5 +11,6 @@ public interface TracTracConnectionConstants {
final int PORT_STORED = PORT_LIVE + 1; //For official usage use 4401; final int PORT_STORED = PORT_LIVE + 1; //For official usage use 4401;
final String HIDDEN_STATUS = "HIDDEN"; final String HIDDEN_STATUS = "HIDDEN";
final Object REPLAY_STATUS = "REPLAY";
} }
@@ -41,6 +41,7 @@ public class TracTracCourseDesignUpdateHandler extends UpdateHandler implements
@Override @Override
public void courseDesignChanged(CourseBase newCourseDesign) throws MalformedURLException, IOException { public void courseDesignChanged(CourseBase newCourseDesign) throws MalformedURLException, IOException {
if (!isActive()) { if (!isActive()) {
logger.info("Not sending course update to TracTrac because no URL has been provided.");
return; return;
} }
@@ -582,14 +582,29 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
updateStatusOfTrackedRaces(); updateStatusOfTrackedRaces();
} else { } else {
// ask RacingEventService to cleanly stop and unregister this tracker // ask RacingEventService to cleanly stop and unregister this tracker
for (RaceDefinition race : getRaces()) { // if the race has all data loaded. Doing this asynchronously because
// stopping can take longer and if you're loading many races in parallel
// this can slow down loading extremely
for (final RaceDefinition race : getRaces()) {
DynamicTrackedRace trackedRace = getTrackedRegatta().getExistingTrackedRace(race);
final Regatta regatta = getRegatta();
if (trackedRace.getStatus().getLoadingProgress() == 1.0) {
Thread raceStopper = new Thread(new Runnable() {
@Override
public void run() {
try { try {
trackedRegattaRegistry.stopTracking(getRegatta(), race); trackedRegattaRegistry.stopTracking(regatta, race);
} catch (Exception e) { } catch (Exception e) {
logger.log(Level.SEVERE, "Error trying to stop tracker for race "+race.getName()+ logger.log(Level.SEVERE, "Error trying to stop tracker for race "+race.getName()+
" in regatta "+getRegatta().getName(), e); " in regatta "+getRegatta().getName(), e);
} }
} }
});
raceStopper.start();
} else {
logger.log(Level.SEVERE, "Not stopping race "+race.getName()+" because it has not all data loaded! Progress: " + trackedRace.getStatus().getLoadingProgress());
}
}
} }
} }
@@ -2,13 +2,15 @@ package com.sap.sailing.domain.racelog;
import java.io.Serializable; import java.io.Serializable;
import com.sap.sailing.domain.common.impl.Util.Triple;
public interface RaceLogIdentifier extends Serializable { public interface RaceLogIdentifier extends Serializable {
String getRaceColumnName(); String getRaceColumnName();
String getFleetName(); String getFleetName();
Serializable getIdentifier(); Triple<String, String, String> getIdentifier();
RaceLogIdentifierTemplate getTemplate(); RaceLogIdentifierTemplate getTemplate();
@@ -1,8 +1,8 @@
package com.sap.sailing.domain.racelog.impl; package com.sap.sailing.domain.racelog.impl;
import java.io.Serializable;
import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.common.impl.Util.Triple;
import com.sap.sailing.domain.racelog.RaceLogIdentifier; import com.sap.sailing.domain.racelog.RaceLogIdentifier;
import com.sap.sailing.domain.racelog.RaceLogIdentifierTemplate; import com.sap.sailing.domain.racelog.RaceLogIdentifierTemplate;
@@ -21,8 +21,8 @@ public class RaceLogIdentifierImpl implements RaceLogIdentifier {
} }
@Override @Override
public Serializable getIdentifier() { public Triple<String, String, String> getIdentifier() {
return String.format("%s.%s.%s", template.getParentObjectName(), raceColumnName, fleetName); return new Util.Triple<String, String, String>(template.getParentObjectName(), raceColumnName, fleetName);
} }
@Override @Override
@@ -575,4 +575,9 @@ public interface TrackedRace extends Serializable {
* If the competitor hasn't started yet, <code>null</code> is returned. * If the competitor hasn't started yet, <code>null</code> is returned.
*/ */
Distance getDistanceFromStarboardSideOfStartLineWhenPassingStart(Competitor competitor); Distance getDistanceFromStarboardSideOfStartLineWhenPassingStart(Competitor competitor);
/**
* Start time received by the tracking infrastructure. To determine real start time use {@link #getStartOfRace()}.
*/
TimePoint getStartTimeReceived();
} }
@@ -1,6 +1,9 @@
package com.sap.sailing.domain.tracking.impl; package com.sap.sailing.domain.tracking.impl;
import java.util.logging.Logger;
import com.sap.sailing.domain.base.CourseBase; import com.sap.sailing.domain.base.CourseBase;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.impl.WindSourceImpl; import com.sap.sailing.domain.common.impl.WindSourceImpl;
@@ -21,19 +24,22 @@ import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent;
import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; import com.sap.sailing.domain.racelog.RaceLogWindFixEvent;
import com.sap.sailing.domain.racelog.analyzing.impl.LastPublishedCourseDesignFinder; import com.sap.sailing.domain.racelog.analyzing.impl.LastPublishedCourseDesignFinder;
import com.sap.sailing.domain.racelog.analyzing.impl.RaceStatusAnalyzer; import com.sap.sailing.domain.racelog.analyzing.impl.RaceStatusAnalyzer;
import com.sap.sailing.domain.racelog.analyzing.impl.StartTimeFinder;
import com.sap.sailing.domain.racelog.analyzing.impl.WindFixesFinder; import com.sap.sailing.domain.racelog.analyzing.impl.WindFixesFinder;
import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.Wind; import com.sap.sailing.domain.tracking.Wind;
public class DynamicTrackedRaceLogListener implements RaceLogEventVisitor { public class DynamicTrackedRaceLogListener implements RaceLogEventVisitor {
public static final Logger log = Logger.getLogger(DynamicTrackedRaceLogListener.class.getName());
private DynamicTrackedRace trackedRace; private DynamicTrackedRace trackedRace;
private final WindSource raceCommitteeWindSource; private final WindSource raceCommitteeWindSource;
private RaceStatusAnalyzer statusAnalyzer; private RaceStatusAnalyzer statusAnalyzer;
private LastPublishedCourseDesignFinder courseDesignFinder; private LastPublishedCourseDesignFinder courseDesignFinder;
private StartTimeFinder startTimeFinder;
public DynamicTrackedRaceLogListener(DynamicTrackedRace trackedRace) { public DynamicTrackedRaceLogListener(DynamicTrackedRace trackedRace) {
this.trackedRace = trackedRace; this.trackedRace = trackedRace;
@@ -105,20 +111,34 @@ public class DynamicTrackedRaceLogListener implements RaceLogEventVisitor {
// Because this code can be triggered by an obsolete (delayed) event... // Because this code can be triggered by an obsolete (delayed) event...
// ... onCourseDesignChangedByRaceCommittee() might be called more than once. // ... onCourseDesignChangedByRaceCommittee() might be called more than once.
trackedRace.onCourseDesignChangedByRaceCommittee(courseDesign); trackedRace.onCourseDesignChangedByRaceCommittee(courseDesign);
} else {
log.info("Could not find any course design update on race log of " + trackedRace.getRace().getName() + "! Not sending out any events.");
}
}
private void analyzeStartTime() {
/* start time will be set by StartTimeAnalyzer in TrackedRace.getStartTime() */
trackedRace.invalidateStartTime();
TimePoint startTime = startTimeFinder.analyze();
if (startTime != null) {
/* invoke listeners with received start time, this will also trigger tractrac update */
trackedRace.onStartTimeChangedByRaceCommittee(startTime);
} else {
log.info("Could not find any valid start time on race log of " + trackedRace.getRace().getName() + "! Not sending out any events.");
} }
} }
@Override @Override
public void visit(RaceLogPassChangeEvent event) { public void visit(RaceLogPassChangeEvent event) {
trackedRace.invalidateStartTime(); trackedRace.invalidateStartTime();
/* this will send tractrac the original start time */
trackedRace.onStartTimeChangedByRaceCommittee(trackedRace.getStartTimeReceived());
} }
@Override @Override
public void visit(RaceLogStartTimeEvent event) { public void visit(RaceLogStartTimeEvent event) {
/* start time will be set by StartTimeAnalyzer in TrackedRace.getStartTime() */ analyzeStartTime();
trackedRace.invalidateStartTime();
/* invoke listeners with received start time, this will also trigger tractrac update */
trackedRace.onStartTimeChangedByRaceCommittee(event.getStartTime());
} }
@Override @Override
@@ -495,7 +495,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
} }
} }
if (startTime == null) { if (startTime == null) {
startTime = startTimeReceived; startTime = getStartTimeReceived();
// If not null, check if the first mark passing for the start line is too much after the // If not null, check if the first mark passing for the start line is too much after the
// startTimeReceived; if so, return an adjusted, later start time. // startTimeReceived; if so, return an adjusted, later start time.
// If no official start time was received, try to estimate the start time using the mark passings for // If no official start time was received, try to estimate the start time using the mark passings for
@@ -1356,7 +1356,8 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
} }
} }
protected TimePoint getStartTimeReceived() { @Override
public TimePoint getStartTimeReceived() {
return startTimeReceived; return startTimeReceived;
} }
@@ -833,4 +833,10 @@ public class MockedTrackedRace implements DynamicTrackedRace {
} }
@Override
public TimePoint getStartTimeReceived() {
// TODO Auto-generated method stub
return null;
}
} }
@@ -706,3 +706,8 @@ video {
max-width: 100%; max-width: 100%;
height: auto; height: auto;
} }
.pointeredLink {
cursor: pointer;
cursor: hand;
}
@@ -1,6 +1,8 @@
package com.sap.sailing.gwt.ui.masterdataimport; package com.sap.sailing.gwt.ui.masterdataimport;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickEvent;
@@ -8,6 +10,8 @@ import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler; import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.http.client.Request; import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder; import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback; import com.google.gwt.http.client.RequestCallback;
@@ -15,7 +19,6 @@ import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response; import com.google.gwt.http.client.Response;
import com.google.gwt.json.client.JSONArray; import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONParser; import com.google.gwt.json.client.JSONParser;
import com.google.gwt.json.client.JSONString;
import com.google.gwt.regexp.shared.RegExp; import com.google.gwt.regexp.shared.RegExp;
import com.google.gwt.user.client.Window; import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
@@ -41,6 +44,8 @@ public class MasterDataImportPanel extends VerticalPanel {
private Button importLeaderboardGroupsButton; private Button importLeaderboardGroupsButton;
private Button fetchIdsButton; private Button fetchIdsButton;
private List<String> allLeaderboardGroupNames;
private RegExp urlValidator; private RegExp urlValidator;
private RegExp urlPlusTldValidator; private RegExp urlPlusTldValidator;
@@ -113,7 +118,7 @@ public class MasterDataImportPanel extends VerticalPanel {
protected void importLeaderboardGroups() { protected void importLeaderboardGroups() {
String[] groupNames = createLeaderBoardGroupNamesFromListBox(); String[] groupNames = createLeaderBoardGroupNamesFromListBox();
if (groupNames.length >= 1) { if (groupNames.length >= 1) {
changeAllButtonState(false); disableAllButtons();
boolean override = overrideSwitch.getValue(); boolean override = overrideSwitch.getValue();
sailingService.importMasterData(currentHost, groupNames, override, sailingService.importMasterData(currentHost, groupNames, override,
new AsyncCallback<MasterDataImportObjectCreationCount>() { new AsyncCallback<MasterDataImportObjectCreationCount>() {
@@ -135,13 +140,13 @@ public class MasterDataImportPanel extends VerticalPanel {
} }
showSuccessAlert(leaderboardsCreated, leaderboardGroupsCreated, eventsCreated, showSuccessAlert(leaderboardsCreated, leaderboardGroupsCreated, eventsCreated,
regattasCreated); regattasCreated);
changeAllButtonState(true); changeButtonStateAccordingToApplicationState();
} }
@Override @Override
public void onFailure(Throwable caught) { public void onFailure(Throwable caught) {
showErrorAlert(caught.getLocalizedMessage()); showErrorAlert(caught.getLocalizedMessage());
changeAllButtonState(true); changeButtonStateAccordingToApplicationState();
} }
}); });
} else { } else {
@@ -150,12 +155,34 @@ public class MasterDataImportPanel extends VerticalPanel {
} }
private void changeAllButtonState(boolean enabled) { private void disableAllButtons() {
boolean enabled = false;
importLeaderboardGroupsButton.setEnabled(enabled); importLeaderboardGroupsButton.setEnabled(enabled);
overrideSwitch.setEnabled(enabled); overrideSwitch.setEnabled(enabled);
fetchIdsButton.setEnabled(enabled); fetchIdsButton.setEnabled(enabled);
} }
private void changeButtonStateAccordingToApplicationState() {
if (leaderboardgroupListBox.getItemCount() > 0 && countSelectedItems() > 0) {
importLeaderboardGroupsButton.setEnabled(true);
} else {
importLeaderboardGroupsButton.setEnabled(false);
}
overrideSwitch.setEnabled(true);
fetchIdsButton.setEnabled(true);
}
private int countSelectedItems() {
int count = 0;
for (int i = 0; i < leaderboardgroupListBox.getItemCount(); i++) {
if (leaderboardgroupListBox.isItemSelected(i)) {
count++;
}
}
return count;
}
protected void showSuccessAlert(int leaderboardsCreated, int leaderboardGroupsCreated, int eventsCreated, protected void showSuccessAlert(int leaderboardsCreated, int leaderboardGroupsCreated, int eventsCreated,
int regattasCreated) { int regattasCreated) {
Window.alert(stringMessages.importSuccess(leaderboardGroupsCreated, leaderboardsCreated, eventsCreated, Window.alert(stringMessages.importSuccess(leaderboardGroupsCreated, leaderboardsCreated, eventsCreated,
@@ -187,7 +214,7 @@ public class MasterDataImportPanel extends VerticalPanel {
showErrorAlert(stringMessages.importUrlInvalid(getLgsUrl)); showErrorAlert(stringMessages.importUrlInvalid(getLgsUrl));
return; return;
} }
changeAllButtonState(false); disableAllButtons();
RequestBuilder getLgsRequestBuilder = new RequestBuilder(RequestBuilder.GET, getLgsUrl); RequestBuilder getLgsRequestBuilder = new RequestBuilder(RequestBuilder.GET, getLgsUrl);
getLgsRequestBuilder.setCallback(new RequestCallback() { getLgsRequestBuilder.setCallback(new RequestCallback() {
@@ -196,41 +223,38 @@ public class MasterDataImportPanel extends VerticalPanel {
if (response.getStatusCode() != 200) { if (response.getStatusCode() != 200) {
showErrorAlert(stringMessages.importGetLeaderboardsFailed(response.getStatusCode(), getLgsUrl)); showErrorAlert(stringMessages.importGetLeaderboardsFailed(response.getStatusCode(), getLgsUrl));
} }
int itemCount = leaderboardgroupListBox.getItemCount(); clearListBox();
for (int i = itemCount - 1; i >= 0; i--) {
leaderboardgroupListBox.removeItem(i);
}
String body = response.getText(); String body = response.getText();
if (body == null || body.isEmpty()) { if (body == null || body.isEmpty()) {
showErrorAlert(stringMessages.importNoDataReturned()); showErrorAlert(stringMessages.importNoDataReturned());
changeAllButtonState(true); changeButtonStateAccordingToApplicationState();
return; return;
} }
JSONArray leaderboardGroups = JSONParser.parseStrict(body).isArray(); JSONArray leaderboardGroups = JSONParser.parseStrict(body).isArray();
leaderboardgroupListBox.setVisibleItemCount(leaderboardGroups.size()); List<String> toSort = new ArrayList<String>();
for (int i = 0; i < leaderboardGroups.size(); i++) { for (int i = 0; i < leaderboardGroups.size(); i++) {
JSONString leaderboardGroupName = leaderboardGroups.get(i).isString(); toSort.add(leaderboardGroups.get(i).isString().stringValue());
leaderboardgroupListBox.addItem(leaderboardGroupName.stringValue());
} }
if (leaderboardGroups.size() > 1) { Collections.sort(toSort);
importLeaderboardGroupsButton.setEnabled(true); allLeaderboardGroupNames = toSort;
} else { leaderboardgroupListBox.setVisibleItemCount(leaderboardGroups.size());
importLeaderboardGroupsButton.setEnabled(false); for (String lgName : toSort) {
leaderboardgroupListBox.addItem(lgName);
} }
changeAllButtonState(true); changeButtonStateAccordingToApplicationState();
} }
@Override @Override
public void onError(Request request, Throwable exception) { public void onError(Request request, Throwable exception) {
showErrorAlert(stringMessages.importServerError()); showErrorAlert(stringMessages.importServerError());
changeAllButtonState(true); changeButtonStateAccordingToApplicationState();
} }
}); });
try { try {
getLgsRequestBuilder.send(); getLgsRequestBuilder.send();
} catch (RequestException e) { } catch (RequestException e) {
showErrorAlert(stringMessages.importServerError()); showErrorAlert(stringMessages.importServerError());
changeAllButtonState(true); changeButtonStateAccordingToApplicationState();
} }
} }
@@ -267,7 +291,15 @@ public class MasterDataImportPanel extends VerticalPanel {
private void addContentToLeftPanel(VerticalPanel contentPanel) { private void addContentToLeftPanel(VerticalPanel contentPanel) {
contentPanel.add(new Label(stringMessages.importLeaderboardGroups())); contentPanel.add(new Label(stringMessages.importLeaderboardGroups()));
HorizontalPanel filterPanel = new HorizontalPanel();
filterPanel.add(new Label(stringMessages.filterName() + ":"));
TextBox filterBox = new TextBox();
setFilterHandler(filterBox);
filterPanel.add(filterBox);
contentPanel.add(filterPanel);
leaderboardgroupListBox = new ListBox(true); leaderboardgroupListBox = new ListBox(true);
addSelectionChangedListener();
contentPanel.add(leaderboardgroupListBox); contentPanel.add(leaderboardgroupListBox);
overrideSwitch = new CheckBox(stringMessages.importOverrideSwitchLabel()); overrideSwitch = new CheckBox(stringMessages.importOverrideSwitchLabel());
@@ -279,4 +311,48 @@ public class MasterDataImportPanel extends VerticalPanel {
contentPanel.add(importLeaderboardGroupsButton); contentPanel.add(importLeaderboardGroupsButton);
} }
private void addSelectionChangedListener() {
leaderboardgroupListBox.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
changeButtonStateAccordingToApplicationState();
}
});
}
private void setFilterHandler(final TextBox filterBox) {
filterBox.addKeyUpHandler(new KeyUpHandler() {
@Override
public void onKeyUp(KeyUpEvent event) {
clearListBox();
int visibleNameCount = 0;
List<String> filterTexts = Arrays.asList(filterBox.getText().split(" "));
for(String name : allLeaderboardGroupNames) {
boolean containsAllFilterTexts = true;
for (String filterText : filterTexts) {
if (!name.toUpperCase().contains(filterText.toUpperCase())) {
containsAllFilterTexts = false;
break;
}
}
if (containsAllFilterTexts) {
leaderboardgroupListBox.addItem(name);
visibleNameCount++;
}
}
leaderboardgroupListBox.setVisibleItemCount(visibleNameCount);
changeButtonStateAccordingToApplicationState();
}
});
}
public void clearListBox() {
int itemCount = leaderboardgroupListBox.getItemCount();
for (int i = itemCount - 1; i >= 0; i--) {
leaderboardgroupListBox.removeItem(i);
}
}
} }
@@ -9,11 +9,9 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.google.gwt.cell.client.ClickableTextCell;
import com.google.gwt.cell.client.FieldUpdater; import com.google.gwt.cell.client.FieldUpdater;
import com.google.gwt.cell.client.SafeHtmlCell; import com.google.gwt.cell.client.SafeHtmlCell;
import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.BrowserEvents;
import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.i18n.client.DateTimeFormat;
@@ -38,8 +36,6 @@ import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.CellPreviewEvent;
import com.google.gwt.view.client.CellPreviewEvent.Handler;
import com.google.gwt.view.client.ListDataProvider; import com.google.gwt.view.client.ListDataProvider;
import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.common.LeaderboardNameConstants;
import com.sap.sailing.domain.common.NauticalSide; import com.sap.sailing.domain.common.NauticalSide;
@@ -57,6 +53,7 @@ import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.Timer; import com.sap.sailing.gwt.ui.client.Timer;
import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sailing.gwt.ui.client.shared.components.Component;
import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent;
import com.sap.sailing.gwt.ui.shared.ClickableSafeHtmlCell;
import com.sap.sailing.gwt.ui.shared.CourseAreaDTO; import com.sap.sailing.gwt.ui.shared.CourseAreaDTO;
import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.RaceGroupDTO; import com.sap.sailing.gwt.ui.shared.RaceGroupDTO;
@@ -145,14 +142,6 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component
regattaOverviewDataProvider = new ListDataProvider<RegattaOverviewEntryDTO>(); regattaOverviewDataProvider = new ListDataProvider<RegattaOverviewEntryDTO>();
regattaOverviewTable = createRegattaTable(); regattaOverviewTable = createRegattaTable();
regattaOverviewTable.addCellPreviewHandler(new Handler<RegattaOverviewEntryDTO>() {
@Override
public void onCellPreview(CellPreviewEvent<RegattaOverviewEntryDTO> event) {
if (BrowserEvents.CLICK.equals(event.getNativeEvent().getType()) && entryClickedHandler != null){
entryClickedHandler.onEntryClicked(event.getValue());
}
}
});
mainPanel.add(regattaOverviewTable); mainPanel.add(regattaOverviewTable);
setWidget(mainPanel); setWidget(mainPanel);
@@ -379,13 +368,24 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component
}); });
TextColumn<RegattaOverviewEntryDTO> raceStatusColumn = new TextColumn<RegattaOverviewEntryDTO>() { Column<RegattaOverviewEntryDTO, SafeHtml> raceStatusColumn = new Column<RegattaOverviewEntryDTO, SafeHtml>(new ClickableSafeHtmlCell()) {
@Override @Override
public String getValue(RegattaOverviewEntryDTO entryDTO) { public SafeHtml getValue(final RegattaOverviewEntryDTO entryDTO) {
return flagInterpreter.getMeaningOfRaceStateAndFlags(entryDTO.raceInfo.lastStatus, entryDTO.raceInfo.lastUpperFlag, String status = flagInterpreter.getMeaningOfRaceStateAndFlags(entryDTO.raceInfo.lastStatus, entryDTO.raceInfo.lastUpperFlag,
entryDTO.raceInfo.lastLowerFlag, entryDTO.raceInfo.isLastFlagDisplayed); entryDTO.raceInfo.lastLowerFlag, entryDTO.raceInfo.isLastFlagDisplayed);
SafeHtmlBuilder sb = new SafeHtmlBuilder();
sb.appendHtmlConstant("<a class=\"pointeredLink\">");
sb.appendEscaped(status);
sb.appendHtmlConstant("</a>");
return sb.toSafeHtml();
} }
}; };
raceStatusColumn.setFieldUpdater(new FieldUpdater<RegattaOverviewEntryDTO, SafeHtml>() {
@Override
public void update(int index, RegattaOverviewEntryDTO object, SafeHtml value) {
entryClickedHandler.onEntryClicked(object);
}
});
raceStatusColumn.setSortable(true); raceStatusColumn.setSortable(true);
regattaOverviewListHandler.setComparator(raceStatusColumn, new Comparator<RegattaOverviewEntryDTO>() { regattaOverviewListHandler.setComparator(raceStatusColumn, new Comparator<RegattaOverviewEntryDTO>() {
@@ -397,20 +397,24 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component
}); });
raceStatusColumn.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER); raceStatusColumn.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
Column<RegattaOverviewEntryDTO, String> raceCourseColumn = new Column<RegattaOverviewEntryDTO, String>(new ClickableTextCell()) { Column<RegattaOverviewEntryDTO, SafeHtml> raceCourseColumn = new Column<RegattaOverviewEntryDTO, SafeHtml>(new ClickableSafeHtmlCell()) {
@Override @Override
public String getValue(RegattaOverviewEntryDTO entryDTO) { public SafeHtml getValue(final RegattaOverviewEntryDTO entryDTO) {
String courseName = "-"; SafeHtmlBuilder sb = new SafeHtmlBuilder();
if (entryDTO.raceInfo.lastCourseName != null) { if (entryDTO.raceInfo.lastCourseName != null) {
courseName = entryDTO.raceInfo.lastCourseName; String courseName = entryDTO.raceInfo.lastCourseName;
sb.appendHtmlConstant("<a class=\"pointeredLink\">");
sb.appendEscaped(courseName);
sb.appendHtmlConstant("</a>");
return sb.toSafeHtml();
} else {
return sb.toSafeHtml();
} }
return courseName;
} }
}; };
raceCourseColumn.setFieldUpdater(new FieldUpdater<RegattaOverviewEntryDTO, String>() { raceCourseColumn.setFieldUpdater(new FieldUpdater<RegattaOverviewEntryDTO, SafeHtml>() {
@Override @Override
public void update(int index, RegattaOverviewEntryDTO object, String value) { public void update(int index, RegattaOverviewEntryDTO object, SafeHtml value) {
if (object.raceInfo.lastCourseDesign.waypoints.size() > 0) { if (object.raceInfo.lastCourseDesign.waypoints.size() > 0) {
DialogBox courseViewDialogBox = createCourseViewDialogBox(object.raceInfo); DialogBox courseViewDialogBox = createCourseViewDialogBox(object.raceInfo);
courseViewDialogBox.center(); courseViewDialogBox.center();
@@ -419,7 +423,6 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component
courseViewDialogBox.show(); courseViewDialogBox.show();
} }
} }
}); });
raceCourseColumn.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER); raceCourseColumn.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
@@ -94,12 +94,12 @@ import com.sap.sailing.domain.common.RegattaIdentifier;
import com.sap.sailing.domain.common.RegattaName; import com.sap.sailing.domain.common.RegattaName;
import com.sap.sailing.domain.common.RegattaNameAndRaceName; import com.sap.sailing.domain.common.RegattaNameAndRaceName;
import com.sap.sailing.domain.common.RegattaScoreCorrections; import com.sap.sailing.domain.common.RegattaScoreCorrections;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.RegattaScoreCorrections.ScoreCorrectionForCompetitorInRace; import com.sap.sailing.domain.common.RegattaScoreCorrections.ScoreCorrectionForCompetitorInRace;
import com.sap.sailing.domain.common.RegattaScoreCorrections.ScoreCorrectionsForRace; import com.sap.sailing.domain.common.RegattaScoreCorrections.ScoreCorrectionsForRace;
import com.sap.sailing.domain.common.ScoreCorrectionProvider; import com.sap.sailing.domain.common.ScoreCorrectionProvider;
import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSource;
@@ -811,11 +811,15 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
logger.info("Loaded race " + record.getName() + " in " + record.getEventName() + " start:" + record.getRaceStartTime() + " trackingStart:" + record.getTrackingStartTime() + " trackingEnd:" + record.getTrackingEndTime()); logger.info("Loaded race " + record.getName() + " in " + record.getEventName() + " start:" + record.getRaceStartTime() + " trackingStart:" + record.getTrackingStartTime() + " trackingEnd:" + record.getTrackingEndTime());
// note that the live URI may be null for races that were put into replay mode // note that the live URI may be null for races that were put into replay mode
final String effectiveLiveURI; final String effectiveLiveURI;
if (!record.getRaceStatus().equals(TracTracConnectionConstants.REPLAY_STATUS)) {
if (liveURI == null || liveURI.trim().length() == 0) { if (liveURI == null || liveURI.trim().length() == 0) {
effectiveLiveURI = record.getLiveURI() == null ? null : record.getLiveURI().toString(); effectiveLiveURI = record.getLiveURI() == null ? null : record.getLiveURI().toString();
} else { } else {
effectiveLiveURI = liveURI; effectiveLiveURI = liveURI;
} }
} else {
effectiveLiveURI = null;
}
final String effectiveStoredURI; final String effectiveStoredURI;
if (storedURI == null || storedURI.trim().length() == 0) { if (storedURI == null || storedURI.trim().length() == 0) {
effectiveStoredURI = record.getStoredURI().toString(); effectiveStoredURI = record.getStoredURI().toString();
@@ -0,0 +1,62 @@
package com.sap.sailing.gwt.ui.shared;
import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
/**
* Cell that is clickable and renders its contents as HTML.
* Especially useful if you want to render a ClickableTextCell
* in a CellTable as an anchor.
*
* Use it like this:
* <pre>
* Column<RegattaOverviewEntryDTO, SafeHtml> raceStatusColumn = new Column<RegattaOverviewEntryDTO, SafeHtml>(new ClickableSafeHtmlCell()) {
* @Override
* public SafeHtml getValue(final RegattaOverviewEntryDTO entryDTO) {
* String status = "my string";
* SafeHtmlBuilder sb = new SafeHtmlBuilder();
* sb.appendHtmlConstant("<a>");
* sb.appendEscaped(status);
* sb.appendHtmlConstant("</a>");
* return sb.toSafeHtml();
* }
* };
* </pre>
*
* @author Simon Pamies
*/
public class ClickableSafeHtmlCell extends AbstractCell<SafeHtml> {
public ClickableSafeHtmlCell() {
super("click", "keydown");
}
@Override
public void onBrowserEvent(Context context, Element parent, SafeHtml value, NativeEvent event,
ValueUpdater<SafeHtml> valueUpdater) {
super.onBrowserEvent(context, parent, value, event, valueUpdater);
if ("click".equals(event.getType())) {
onEnterKeyDown(context, parent, value, event, valueUpdater);
}
}
@Override
protected void onEnterKeyDown(Context context, Element parent, SafeHtml value,
NativeEvent event, ValueUpdater<SafeHtml> valueUpdater) {
if (valueUpdater != null) {
valueUpdater.update(value);
}
}
@Override
public void render(Context context, SafeHtml value, SafeHtmlBuilder sb) {
if (value != null) {
sb.append(value);
}
}
}
@@ -21,6 +21,7 @@ import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.common.impl.Util.Triple;
import com.sap.sailing.domain.common.racelog.Flags; import com.sap.sailing.domain.common.racelog.Flags;
import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus;
import com.sap.sailing.domain.persistence.MongoFactory; import com.sap.sailing.domain.persistence.MongoFactory;
@@ -58,7 +59,7 @@ public class StoreAndLoadRaceLogEventsTest extends AbstractMongoDBTest {
@Before @Before
public void setUp() { public void setUp() {
logIdentifier = mock(RaceLogIdentifier.class); logIdentifier = mock(RaceLogIdentifier.class);
when(logIdentifier.getIdentifier()).thenReturn(UUID.randomUUID()); when(logIdentifier.getIdentifier()).thenReturn(new Triple<String, String, String>("a", "b", UUID.randomUUID().toString()));
} }
public void assertBaseFields(RaceLogEvent expectedEvent, RaceLogEvent actualEvent) { public void assertBaseFields(RaceLogEvent expectedEvent, RaceLogEvent actualEvent) {
@@ -0,0 +1,152 @@
package com.sap.sailing.mongodb.test;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.Serializable;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.UUID;
import org.bson.types.ObjectId;
import org.junit.Test;
import com.mongodb.DBObject;
import com.mongodb.MongoException;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.common.racelog.Flags;
import com.sap.sailing.domain.persistence.MongoFactory;
import com.sap.sailing.domain.persistence.impl.DomainObjectFactoryImpl;
import com.sap.sailing.domain.persistence.impl.MongoObjectFactoryImpl;
import com.sap.sailing.domain.racelog.RaceLog;
import com.sap.sailing.domain.racelog.RaceLogEventFactory;
import com.sap.sailing.domain.racelog.RaceLogFlagEvent;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
import com.sap.sailing.domain.racelog.RaceLogIdentifierTemplate;
import com.sap.sailing.domain.racelog.impl.RaceLogIdentifierImpl;
public class StoreAndLoadRaceLogsTest extends AbstractMongoDBTest {
protected MongoObjectFactoryImpl mongoFactory = (MongoObjectFactoryImpl) MongoFactory.INSTANCE
.getMongoObjectFactory(getMongoService());
protected DomainObjectFactoryImpl domainFactory = (DomainObjectFactoryImpl) MongoFactory.INSTANCE
.getDomainObjectFactory(getMongoService());
protected RaceLogEventFactory eventFactory = RaceLogEventFactory.INSTANCE;
public StoreAndLoadRaceLogsTest() throws UnknownHostException, MongoException {
super();
}
@Test
public void testStoreAndLoadSingleRaceLog() {
RaceLogIdentifier identifier = createIdentifier("A.A", "B.B", "C.C");
RaceLogFlagEvent event1 = createRaceLogFlagEvent(UUID.randomUUID());
RaceLogFlagEvent event2 = createRaceLogFlagEvent(UUID.randomUUID());
DBObject dbEvent1 = mongoFactory.storeRaceLogEntry(identifier, event1);
mongoFactory.getRaceLogCollection().insert(dbEvent1);
DBObject dbEvent2 = mongoFactory.storeRaceLogEntry(identifier, event2);
mongoFactory.getRaceLogCollection().insert(dbEvent2);
RaceLog raceLog = domainFactory.loadRaceLog(identifier);
try {
raceLog.lockForRead();
assertEquals(2, Util.size(raceLog.getRawFixes()));
} finally {
raceLog.unlockAfterRead();
}
}
@Test
public void testStoreAndLoadCollidingRaceLogs() {
// RaceLog A.A B.B C.C
RaceLogIdentifier identifier = createIdentifier("A.A", "B.B", "C.C");
RaceLogFlagEvent event1 = createRaceLogFlagEvent(UUID.randomUUID());
RaceLogFlagEvent event2 = createRaceLogFlagEvent(UUID.randomUUID());
DBObject dbEvent1 = mongoFactory.storeRaceLogEntry(identifier, event1);
mongoFactory.getRaceLogCollection().insert(dbEvent1);
DBObject dbEvent2 = mongoFactory.storeRaceLogEntry(identifier, event2);
mongoFactory.getRaceLogCollection().insert(dbEvent2);
// RaceLog A.A.B B.C C
RaceLogIdentifier collidingIdentifier = createIdentifier("A.A.B", "B.C", "C");
RaceLogFlagEvent event3 = createRaceLogFlagEvent(UUID.randomUUID());
DBObject dbEvent3 = mongoFactory.storeRaceLogEntry(collidingIdentifier, event3);
mongoFactory.getRaceLogCollection().insert(dbEvent3);
RaceLog raceLog = domainFactory.loadRaceLog(identifier);
try {
raceLog.lockForRead();
assertEquals(2, Util.size(raceLog.getRawFixes()));
} finally {
raceLog.unlockAfterRead();
}
}
@Test
public void testdLoadObsoleteIdentifier() {
// Identifier is A.A B.B C.C
DBObject dbEvent1 = (DBObject) com.mongodb.util.JSON.parse(createOldFormatRaceLogEventJson(UUID.randomUUID()));
mongoFactory.getRaceLogCollection().insert(dbEvent1);
DBObject dbEvent2 = (DBObject) com.mongodb.util.JSON.parse(createOldFormatRaceLogEventJson(UUID.randomUUID()));
mongoFactory.getRaceLogCollection().insert(dbEvent2);
RaceLog raceLog = domainFactory.loadRaceLog(createIdentifier("A.A", "B.B", "C.C"));
try {
raceLog.lockForRead();
assertEquals(2, Util.size(raceLog.getRawFixes()));
} finally {
raceLog.unlockAfterRead();
}
}
@Test
public void testdLoadObsoleteAndNewIdentifier() {
// event1 stored with obsolete identifier
DBObject dbEvent1 = (DBObject) com.mongodb.util.JSON.parse(createOldFormatRaceLogEventJson(UUID.randomUUID()));
mongoFactory.getRaceLogCollection().insert(dbEvent1);
// event2 stored with new identifier
RaceLogIdentifier identifier = createIdentifier("A.A", "B.B", "C.C");
RaceLogFlagEvent event2 = createRaceLogFlagEvent(UUID.randomUUID());
DBObject dbEvent2 = mongoFactory.storeRaceLogEntry(identifier, event2);
mongoFactory.getRaceLogCollection().insert(dbEvent2);
RaceLog raceLog = domainFactory.loadRaceLog(identifier);
try {
raceLog.lockForRead();
assertEquals(2, Util.size(raceLog.getRawFixes()));
} finally {
raceLog.unlockAfterRead();
}
}
private String createOldFormatRaceLogEventJson(Serializable id) {
return "{ \"_id\" : { \"$oid\" : \""+ new ObjectId().toStringMongod() + "\"} , \"RACE_LOG_IDENTIFIER\" : \"A%2EA%2EB%2EB%2EC%2EC\" , \"RACE_LOG_EVENT\" : "
+ "{ \"TIME_AS_MILLIS\" : 42 , \"RACE_LOG_EVENT_CREATED_AT\" : 1376058090051 , \"RACE_LOG_EVENT_ID\" : { \"$uuid\" : \"" + id.toString() + "\"} , "
+ "\"RACE_LOG_EVENT_PASS_ID\" : 42 , \"RACE_LOG_EVENT_INVOLVED_BOATS\" : [ ] , \"RACE_LOG_EVENT_CLASS\" : \"RaceLogFlagEvent\" , \"RACE_LOG_EVENT_FLAG_UPPER\" : \"ALPHA\" , "
+ "\"RACE_LOG_EVENT_FLAG_LOWER\" : \"BRAVO\" , \"RACE_LOG_EVENT_FLAG_DISPLAYED\" : \"true\"}}";
}
private RaceLogIdentifier createIdentifier(String groupName, String raceColumnName, String fleetName) {
RaceLogIdentifierTemplate template = mock(RaceLogIdentifierTemplate.class);
when(template.getParentObjectName()).thenReturn(groupName);
when(template.getRaceColumnName()).thenReturn(raceColumnName);
Fleet fleet = mock(Fleet.class);
when(fleet.getName()).thenReturn(fleetName);
return new RaceLogIdentifierImpl(template, raceColumnName, fleet);
}
private RaceLogFlagEvent createRaceLogFlagEvent(Serializable id) {
return eventFactory.createFlagEvent(new MillisecondsTimePoint(42), id, Collections.<Competitor> emptyList(),
42, Flags.ALPHA, Flags.BRAVO, true);
}
}
@@ -28,7 +28,7 @@ import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent;
import com.sap.sailing.domain.racelog.RaceLogEventFactory; import com.sap.sailing.domain.racelog.RaceLogEventFactory;
import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException;
import com.sap.sailing.server.gateway.deserialization.coursedata.impl.ControlPointDeserializer; import com.sap.sailing.server.gateway.deserialization.coursedata.impl.ControlPointDeserializer;
import com.sap.sailing.server.gateway.deserialization.coursedata.impl.CourseDataDeserializer; import com.sap.sailing.server.gateway.deserialization.coursedata.impl.CourseBaseDeserializer;
import com.sap.sailing.server.gateway.deserialization.coursedata.impl.GateDeserializer; import com.sap.sailing.server.gateway.deserialization.coursedata.impl.GateDeserializer;
import com.sap.sailing.server.gateway.deserialization.coursedata.impl.MarkDeserializer; import com.sap.sailing.server.gateway.deserialization.coursedata.impl.MarkDeserializer;
import com.sap.sailing.server.gateway.deserialization.coursedata.impl.WaypointDeserializer; import com.sap.sailing.server.gateway.deserialization.coursedata.impl.WaypointDeserializer;
@@ -61,7 +61,7 @@ public class RaceLogCourseDesignChangedEventSerializerTest {
new CourseBaseJsonSerializer(new WaypointJsonSerializer(new ControlPointJsonSerializer( new CourseBaseJsonSerializer(new WaypointJsonSerializer(new ControlPointJsonSerializer(
new MarkJsonSerializer(), new GateJsonSerializer(new MarkJsonSerializer()))))); new MarkJsonSerializer(), new GateJsonSerializer(new MarkJsonSerializer())))));
deserializer = new RaceLogCourseDesignChangedEventDeserializer(new CompetitorDeserializer(factory), deserializer = new RaceLogCourseDesignChangedEventDeserializer(new CompetitorDeserializer(factory),
new CourseDataDeserializer(new WaypointDeserializer(new ControlPointDeserializer(new MarkDeserializer( new CourseBaseDeserializer(new WaypointDeserializer(new ControlPointDeserializer(new MarkDeserializer(
factory), new GateDeserializer(factory, new MarkDeserializer(factory)))))); factory), new GateDeserializer(factory, new MarkDeserializer(factory))))));
now = MillisecondsTimePoint.now(); now = MillisecondsTimePoint.now();
@@ -16,11 +16,11 @@ import com.sap.sailing.server.gateway.serialization.coursedata.impl.CourseBaseJs
/** /**
* Deserializer for CourseData. * Deserializer for CourseData.
*/ */
public class CourseDataDeserializer implements JsonDeserializer<CourseBase> { public class CourseBaseDeserializer implements JsonDeserializer<CourseBase> {
private final WaypointDeserializer waypointDeserializer; private final WaypointDeserializer waypointDeserializer;
public CourseDataDeserializer(WaypointDeserializer waypointDeserializer) { public CourseBaseDeserializer(WaypointDeserializer waypointDeserializer) {
this.waypointDeserializer = waypointDeserializer; this.waypointDeserializer = waypointDeserializer;
} }
@@ -17,8 +17,8 @@ public class Helpers {
if (object instanceof JSONArray) { if (object instanceof JSONArray) {
return (JSONArray) object; return (JSONArray) object;
} }
throw new JsonDeserializationException(String.format("Expected a JSONArray, got %s.", object.getClass() throw new JsonDeserializationException(String.format("Expected a JSONArray, got %s.", object != null ? object.getClass()
.getName())); .getName() : ""));
} }
public static JSONObject toJSONObjectSafe(Object object) throws JsonDeserializationException { public static JSONObject toJSONObjectSafe(Object object) throws JsonDeserializationException {
@@ -11,14 +11,14 @@ import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.racelog.RaceLogEvent; import com.sap.sailing.domain.racelog.RaceLogEvent;
import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException;
import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; import com.sap.sailing.server.gateway.deserialization.JsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.coursedata.impl.CourseDataDeserializer; import com.sap.sailing.server.gateway.deserialization.coursedata.impl.CourseBaseDeserializer;
import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogCourseDesignChangedEventSerializer; import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogCourseDesignChangedEventSerializer;
public class RaceLogCourseDesignChangedEventDeserializer extends BaseRaceLogEventDeserializer { public class RaceLogCourseDesignChangedEventDeserializer extends BaseRaceLogEventDeserializer {
private final CourseDataDeserializer courseDataDeserializer; private final CourseBaseDeserializer courseDataDeserializer;
public RaceLogCourseDesignChangedEventDeserializer(JsonDeserializer<Competitor> competitorDeserializer, CourseDataDeserializer courseDataDeserializer) { public RaceLogCourseDesignChangedEventDeserializer(JsonDeserializer<Competitor> competitorDeserializer, CourseBaseDeserializer courseDataDeserializer) {
super(competitorDeserializer); super(competitorDeserializer);
this.courseDataDeserializer = courseDataDeserializer; this.courseDataDeserializer = courseDataDeserializer;
} }
@@ -8,7 +8,7 @@ import com.sap.sailing.domain.racelog.RaceLogEvent;
import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException;
import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; import com.sap.sailing.server.gateway.deserialization.JsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.coursedata.impl.ControlPointDeserializer; import com.sap.sailing.server.gateway.deserialization.coursedata.impl.ControlPointDeserializer;
import com.sap.sailing.server.gateway.deserialization.coursedata.impl.CourseDataDeserializer; import com.sap.sailing.server.gateway.deserialization.coursedata.impl.CourseBaseDeserializer;
import com.sap.sailing.server.gateway.deserialization.coursedata.impl.GateDeserializer; import com.sap.sailing.server.gateway.deserialization.coursedata.impl.GateDeserializer;
import com.sap.sailing.server.gateway.deserialization.coursedata.impl.MarkDeserializer; import com.sap.sailing.server.gateway.deserialization.coursedata.impl.MarkDeserializer;
import com.sap.sailing.server.gateway.deserialization.coursedata.impl.WaypointDeserializer; import com.sap.sailing.server.gateway.deserialization.coursedata.impl.WaypointDeserializer;
@@ -40,7 +40,7 @@ public class RaceLogEventDeserializer implements JsonDeserializer<RaceLogEvent>
new RaceLogRaceStatusEventDeserializer(competitorDeserializer), new RaceLogRaceStatusEventDeserializer(competitorDeserializer),
new RaceLogCourseAreaChangedEventDeserializer(competitorDeserializer), new RaceLogCourseAreaChangedEventDeserializer(competitorDeserializer),
new RaceLogCourseDesignChangedEventDeserializer(competitorDeserializer, new RaceLogCourseDesignChangedEventDeserializer(competitorDeserializer,
new CourseDataDeserializer( new CourseBaseDeserializer(
new WaypointDeserializer( new WaypointDeserializer(
new ControlPointDeserializer( new ControlPointDeserializer(
new MarkDeserializer(domainFactory), new GateDeserializer(domainFactory, new MarkDeserializer(domainFactory)))))), new MarkDeserializer(domainFactory), new GateDeserializer(domainFactory, new MarkDeserializer(domainFactory)))))),
@@ -28,7 +28,7 @@ public class RaceLogSerializer implements JsonSerializer<RaceLog> {
events.add(itemSerializer.serialize(event)); events.add(itemSerializer.serialize(event));
} }
result.put(FIELD_EVENTS, events); result.put(FIELD_EVENTS, events);
result.put(FIELD_RACELOG_IDENTIFIER, object.getId()); result.put(FIELD_RACELOG_IDENTIFIER, object.getId().toString());
} finally { } finally {
object.unlockAfterRead(); object.unlockAfterRead();
} }
@@ -9,7 +9,7 @@ import com.sap.sailing.server.RacingEventService;
public abstract class AbstractSeriesOperation<ResultType> extends AbstractRacingEventServiceOperation<ResultType> { public abstract class AbstractSeriesOperation<ResultType> extends AbstractRacingEventServiceOperation<ResultType> {
private static final long serialVersionUID = -7786133902346491275L; private static final long serialVersionUID = -7786133902346491275L;
private Logger logger = Logger.getLogger(AbstractSeriesOperation.class.getName()); private static final Logger logger = Logger.getLogger(AbstractSeriesOperation.class.getName());
private final RegattaIdentifier regattaIdentifier; private final RegattaIdentifier regattaIdentifier;
private final String seriesName; private final String seriesName;
@@ -500,4 +500,10 @@ public class DummyTrackedRace implements TrackedRace {
// TODO Auto-generated method stub // TODO Auto-generated method stub
} }
@Override
public TimePoint getStartTimeReceived() {
// TODO Auto-generated method stub
return null;
}
} }
@@ -19,25 +19,12 @@
<div id='mainContent'> <div id='mainContent'>
<h1 id='pageHeadline'>Extreme Sailing Series 2013</h1> <h1 id='pageHeadline'>Extreme Sailing Series 2013</h1>
<h2>Next Event</h2> <h2>Next Event</h2>
<p>
Act 5 - Porto, Portugal
</p>
<p>
July 25th - 28th
</p>
<!--<p>
Act 6 - Cardiff, England
</p>
<p>
August 22th - 25th
</p>
<p> <p>
Act 7 - Nice, France Act 7 - Nice, France
</p> </p>
<p> <p>
October 3th - 6th September 2th - 6th
</p> </p>
-->
</div> </div>
<div id='navigation'> <div id='navigation'>
<a class='navButton overall' href='http://ess40-2013.sapsailing.com/gwt/Leaderboard.html?name=Extreme%20Sailing%20Series%202013%20Overall&amp;showRaceDetails=false&amp;refreshIntervalMillis=10000&amp;displayName=Overall%20Standings&amp;leaderboardGroupName=Extreme%20Sailing%20Series%202013&amp;root=leaderboardGroupPanel'> <a class='navButton overall' href='http://ess40-2013.sapsailing.com/gwt/Leaderboard.html?name=Extreme%20Sailing%20Series%202013%20Overall&amp;showRaceDetails=false&amp;refreshIntervalMillis=10000&amp;displayName=Overall%20Standings&amp;leaderboardGroupName=Extreme%20Sailing%20Series%202013&amp;root=leaderboardGroupPanel'>
@@ -48,10 +35,10 @@
</a> </a>
<a class='navButton event' href='http://ess40-2013.sapsailing.com/gwt/Leaderboard.html?name=ESS%202013%20Nice%20%28Extreme40%29&displayName=Nice&delayToLiveMillis=3000&refreshIntervalMillis=10000&legDetail=AVERAGE_SPEED_OVER_GROUND_IN_KNOTS&legDetail=DISTANCE_TRAVELED&legDetail=RANK_GAIN&raceDetail=DISPLAY_LEGS&maneuverDetail=TACK&maneuverDetail=JIBE&maneuverDetail=PENALTY_CIRCLE&lastN=3'> <a class='navButton event' href='http://ess40-2013.sapsailing.com/gwt/Leaderboard.html?name=ESS%202013%20Nice%20%28Extreme40%29&displayName=Nice&delayToLiveMillis=3000&refreshIntervalMillis=10000&legDetail=AVERAGE_SPEED_OVER_GROUND_IN_KNOTS&legDetail=DISTANCE_TRAVELED&legDetail=RANK_GAIN&raceDetail=DISPLAY_LEGS&maneuverDetail=TACK&maneuverDetail=JIBE&maneuverDetail=PENALTY_CIRCLE&lastN=3'>
Nice Nice
</a> </a>-->
<a class='navButton event' href='http://ess40-2013.sapsailing.com/gwt/Leaderboard.html?name=ESS%202013%20Cardiff%20%28Extreme40%29&displayName=Cardiff&delayToLiveMillis=3000&refreshIntervalMillis=10000&legDetail=AVERAGE_SPEED_OVER_GROUND_IN_KNOTS&legDetail=DISTANCE_TRAVELED&legDetail=RANK_GAIN&raceDetail=DISPLAY_LEGS&maneuverDetail=TACK&maneuverDetail=JIBE&maneuverDetail=PENALTY_CIRCLE&lastN=3'> <a class='navButton event' href='http://ess40-2013.sapsailing.com/gwt/Leaderboard.html?name=ESS%202013%20Cardiff%20%28Extreme40%29&displayName=Cardiff&delayToLiveMillis=3000&refreshIntervalMillis=10000&legDetail=AVERAGE_SPEED_OVER_GROUND_IN_KNOTS&legDetail=DISTANCE_TRAVELED&legDetail=RANK_GAIN&raceDetail=DISPLAY_LEGS&maneuverDetail=TACK&maneuverDetail=JIBE&maneuverDetail=PENALTY_CIRCLE&lastN=3'>
Cardiff Cardiff
</a>--> </a>
<a class='navButton event' href='http://ess40-2013.sapsailing.com/gwt/Leaderboard.html?name=ESS%202013%20Porto%20%28Extreme40%29&displayName=Porto&delayToLiveMillis=3000&refreshIntervalMillis=10000&legDetail=AVERAGE_SPEED_OVER_GROUND_IN_KNOTS&legDetail=DISTANCE_TRAVELED&legDetail=RANK_GAIN&raceDetail=DISPLAY_LEGS&maneuverDetail=TACK&maneuverDetail=JIBE&maneuverDetail=PENALTY_CIRCLE&lastN=3'> <a class='navButton event' href='http://ess40-2013.sapsailing.com/gwt/Leaderboard.html?name=ESS%202013%20Porto%20%28Extreme40%29&displayName=Porto&delayToLiveMillis=3000&refreshIntervalMillis=10000&legDetail=AVERAGE_SPEED_OVER_GROUND_IN_KNOTS&legDetail=DISTANCE_TRAVELED&legDetail=RANK_GAIN&raceDetail=DISPLAY_LEGS&maneuverDetail=TACK&maneuverDetail=JIBE&maneuverDetail=PENALTY_CIRCLE&lastN=3'>
Porto Porto
</a> </a>
+47 -27
View File
@@ -33,22 +33,64 @@
<div class='innerContent'> <div class='innerContent'>
<!-- Uncomment the following in case there are events in live state and add the timeBar plus raceSection --> <!-- Uncomment the following in case there are events in live state and add the timeBar plus raceSection -->
<!--
<h2 class='articleSubheadline'>Live</h2> <!--<h2 class='articleSubheadline'>Live</h2>
<br/> <br/>
--> <div class="timeBar">
</div>-->
<!-- Uncomment the following in case there are upcoming and add the timeBar plus raceSection --> <!-- Uncomment the following in case there are upcoming and add the timeBar plus raceSection -->
<!--
<h2 class='articleSubheadline'>Upcoming</h2> <h2 class='articleSubheadline'>Upcoming</h2>
<div class="timeBar"> <div class="timeBar">
<div class='raceSection'>
<h5 class='date'>
Sep 2 - 7
</h5>
<div class='raceTimepoint'></div>
<a href='http://ess40-2013.sapsailing.com' target='_blank'>
<ul class='racePanel'>
<li class='raceName'>
Extreme Sailing Series 2013
</li>
<li class='raceLocation'>
Nice, France
</li>
</ul>
</a>
<div class='raceDescription'>
<p>Extreme Sailing Series 2013</p>
<a href='http://ess40-2013.sapsailing.com' target='_blank'>SAP Sailing Analytics</a><br/>
<a href='http://www.extremesailingseries.com' target='_blank'>Official website</a>
</div>
</div>
</div> </div>
<br/> <br/>
-->
<h2 class='articleSubheadline'>2013</h2> <h2 class='articleSubheadline'>2013</h2>
<div class="timeBar"> <div class="timeBar">
<div class='raceSection'>
<h5 class='date'>
Aug 23 - 26
</h5>
<div class='raceTimepoint'></div>
<a href='http://ess40-2013.sapsailing.com' target='_blank'>
<ul class='racePanel'>
<li class='raceName'>
Extreme Sailing Series 2013
</li>
<li class='raceLocation'>
Cardiff, Wales
</li>
</ul>
</a>
<div class='raceDescription'>
<p>Extreme Sailing Series 2013</p>
<a href='http://ess40-2013.sapsailing.com' target='_blank'>SAP Sailing Analytics</a><br/>
<a href='http://www.extremesailingseries.com' target='_blank'>Official website</a>
</div>
</div>
<div class='raceSection'> <div class='raceSection'>
<h5 class='date'> <h5 class='date'>
July 19 - 28 July 19 - 28
@@ -111,28 +153,6 @@
</div> </div>
</div> </div>
<div class='raceSection'>
<h5 class='date'>
July 25 - 28
</h5>
<div class='raceTimepoint'></div>
<a href='http://ess40-2013.sapsailing.com' target='_blank'>
<ul class='racePanel'>
<li class='raceName'>
Extreme Sailing Series 2013
</li>
<li class='raceLocation'>
Porto, Portugal
</li>
</ul>
</a>
<div class='raceDescription'>
<p>Extreme Sailing Series 2013</p>
<a href='http://ess40-2013.sapsailing.com' target='_blank'>SAP Sailing Analytics</a><br/>
<a href='http://www.extremesailingseries.com' target='_blank'>Official website</a>
</div>
</div>
<div class='raceSection'> <div class='raceSection'>
<h5 class='date'> <h5 class='date'>
May 1 - 5 May 1 - 5
@@ -2,7 +2,7 @@
monitor.interval = 30000 monitor.interval = 30000
# time the monitoring server waits before polling, in milliseconds # time the monitoring server waits before polling, in milliseconds
monitor.gracetime = 10000 monitor.gracetime = 60000
# time between interval checks, in milliseconds # time between interval checks, in milliseconds
monitor.pause = 1000 monitor.pause = 1000
@@ -45,7 +45,8 @@
<activity <activity
android:name=".ui.activities.LoginActivity" android:name=".ui.activities.LoginActivity"
android:screenOrientation="landscape" android:screenOrientation="landscape"
android:launchMode="singleTop" > android:launchMode="standard"
android:finishOnTaskLaunch="true" >
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

@@ -2,10 +2,15 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" > <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<PreferenceCategory android:title="@string/settings_category_general" > <PreferenceCategory android:title="@string/settings_category_general" >
<Preference
android:key="languagePref"
android:title="@string/settings_general_language_header"
android:summary="@string/settings_general_language_description"/>
<EditTextPreference <EditTextPreference
android:key="courseAreasPref"
android:name="@string/settings_general_manageareas_header" android:name="@string/settings_general_manageareas_header"
android:defaultValue="@string/settings_general_manageareas_default" android:defaultValue="@string/settings_general_manageareas_default"
android:key="courseAreasPref"
android:summary="@string/settings_general_manageareas_description" android:summary="@string/settings_general_manageareas_description"
android:title="@string/settings_general_manageareas_header" /> android:title="@string/settings_general_manageareas_header" />
</PreferenceCategory> </PreferenceCategory>
@@ -16,7 +16,7 @@
<TextView android:id="@+id/Welter.Cell.OneRowNoImage.txtTitle" <TextView android:id="@+id/Welter.Cell.OneRowNoImage.txtTitle"
android:padding="5dp" android:padding="5dp"
android:textColor="@drawable/text_selector" android:textColor="@drawable/text_selector"
android:textSize="10pt" android:textSize="@dimen/Welter.BigTitleTextSize"
android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_centerVertical="true"> android:layout_centerVertical="true">
</TextView> </TextView>
@@ -15,14 +15,14 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:textSize="10pt" android:alpha="1" > android:textSize="@dimen/Welter.RegularTextSize" android:alpha="1" >
</TextView> </TextView>
<ImageButton android:id="@+id/Welter.Cell.OneRowNoImageLabel.btnJury" <ImageButton android:id="@+id/Welter.Cell.OneRowNoImageLabel.btnJury"
android:src="@drawable/bravo_mini" android:src="@drawable/bravo_mini"
android:padding="5dp" android:padding="5dp"
android:textColor="@color/welter_red" android:textColor="@color/welter_red"
android:textSize="10pt" android:textSize="@dimen/Welter.RegularTextSize"
android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_width="wrap_content" android:layout_height="wrap_content"
android:contentDescription="@string/flag_jury" android:contentDescription="@string/flag_jury"
android:layout_centerVertical="true" android:layout_alignParentRight="true" /> android:layout_centerVertical="true" android:layout_alignParentRight="true" />
@@ -17,9 +17,9 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:text="Title of the first line" android:text="@string/empty"
android:textColor="@drawable/text_selector" android:textColor="@drawable/text_selector"
android:textSize="9pt" > android:textSize="@dimen/Welter.RegularTextSize" >
</TextView> </TextView>
<TextView <TextView
@@ -30,8 +30,8 @@
android:paddingLeft="6pt" android:paddingLeft="6pt"
android:layout_below="@+id/Welter.Cell.TwoRowNoImage.lineOne" android:layout_below="@+id/Welter.Cell.TwoRowNoImage.lineOne"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:text="Title of the second line" android:text="@string/empty"
android:textSize="8pt" > android:textSize="@dimen/Welter.ProgressTextSize" >
</TextView> </TextView>
</RelativeLayout> </RelativeLayout>
@@ -2,23 +2,23 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="horizontal" android:background="@drawable/background"
android:descendantFocusability="beforeDescendants" android:descendantFocusability="beforeDescendants"
android:focusableInTouchMode="true" android:focusableInTouchMode="true"
android:background="@drawable/background" > android:orientation="horizontal" >
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="horizontal"
android:layout_margin="18dp" android:layout_margin="18dp"
android:background="@color/welter_extralight_gray_transparent"> android:background="@color/welter_extralight_gray_transparent"
android:orientation="horizontal" >
<LinearLayout <LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="18dp"> android:layout_margin="18dp"
android:orientation="vertical" >
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -26,50 +26,6 @@
android:text="@string/enter_wind_direction" android:text="@string/enter_wind_direction"
android:textAppearance="@style/TextAppearance.Welter.BigTitleLabel" /> android:textAppearance="@style/TextAppearance.Welter.BigTitleLabel" />
<com.sap.sailing.racecommittee.app.ui.views.CompassView
android:id="@+id/compassView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
</com.sap.sailing.racecommittee.app.ui.views.CompassView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/enter_wind_speed"
android:textAppearance="@style/TextAppearance.Welter.BigTitleLabel" />
<SeekBar
android:id="@+id/seekbar_wind_speed"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:progress="100"
android:max="250"
android:padding="18dp" />
</LinearLayout>
<LinearLayout
android:layout_width="2dp"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_margin="18dp"
android:background="@color/welter_deep_gray">
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="18dp"
android:layout_marginRight="26dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="9dp"
android:text="@string/check_all_data"
android:textAppearance="@style/TextAppearance.Welter.BigTitleLabel" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -79,9 +35,38 @@
android:id="@+id/editTextWindDirection" android:id="@+id/editTextWindDirection"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:hint="@string/in_degrees" android:hint="@string/in_degrees"
android:minWidth="200dp" /> android:inputType="numberDecimal"
android:minWidth="200dp"
android:textSize="24sp" />
<com.sap.sailing.racecommittee.app.ui.views.CompassView
android:id="@+id/compassView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
</com.sap.sailing.racecommittee.app.ui.views.CompassView>
</LinearLayout>
<LinearLayout
android:layout_width="2dp"
android:layout_height="match_parent"
android:layout_margin="18dp"
android:background="@color/welter_deep_gray"
android:orientation="vertical" >
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="18dp"
android:layout_marginRight="26dp"
android:orientation="vertical" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/enter_wind_speed"
android:textAppearance="@style/TextAppearance.Welter.BigTitleLabel" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -92,49 +77,42 @@
android:id="@+id/editTextWindSpeed" android:id="@+id/editTextWindSpeed"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:hint="@string/in_knots" android:hint="@string/in_knots"
android:minWidth="200dp"> android:inputType="numberDecimal"
</EditText> android:minWidth="200dp"
android:textSize="24sp" />
<SeekBar
android:id="@+id/seekbar_wind_speed"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="500"
android:padding="10dp"
android:progress="100" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/your_position" /> android:layout_marginBottom="9dp"
android:text="@string/check_all_data"
android:textAppearance="@style/TextAppearance.Welter.BigTitleLabel" />
<EditText <TextView
android:id="@+id/et_position_lat" android:id="@+id/textWaitingForGPS"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/latitude" android:text="@string/waiting_for_gps"
android:minWidth="200dp" android:textAppearance="@style/TextAppearance.Welter.TitleLabel"
android:clickable="false" android:textColor="@color/welter_red"
android:cursorVisible="false" android:textSize="24sp" />
android:focusable="false"
android:focusableInTouchMode="false">
</EditText>
<EditText
android:id="@+id/et_position_lon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/longitude"
android:minWidth="200dp"
android:clickable="false"
android:cursorVisible="false"
android:focusable="false"
android:focusableInTouchMode="false">
</EditText>
<Button <Button
android:id="@+id/btn_wind_send" android:id="@+id/btn_wind_send"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/store_wind" android:layout_gravity="center"
android:layout_gravity="center"/> android:text="@string/store_wind" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
@@ -6,7 +6,9 @@
<string name="course_design_number_waypoints">Kurs hat %s Wegpunkte</string> <string name="course_design_number_waypoints">Kurs hat %s Wegpunkte</string>
<string name="choose_running_course">Wähle Kurs aus</string> <string name="choose_running_course">Wähle Kurs aus</string>
<string name="service_info">SAP Race Committee Service</string> <string name="service_info">SAP Race Committee Service</string>
<string name="service_title">Verwaltet alle Rennen auf gewählter Rennbahn.</string> <string name="service_text_no_races">Warte auf Rennen&#8230;</string>
<string name="service_text_num_races">Aktuell %d Rennen verwaltet.</string>
<string name="app_name">SAP Race Committee Cockpit</string> <string name="app_name">SAP Race Committee Cockpit</string>
<string name="set_time">Start</string> <string name="set_time">Start</string>
<string name="reset_time">Startzeit ändern</string> <string name="reset_time">Startzeit ändern</string>
@@ -179,6 +181,7 @@
<string name="wind_speed_direction_not_a_valid_number">Die Windgeschwindigkeit oder -richtung ist keine valide Zahl. Bitte korrigieren Sie den Wert.</string> <string name="wind_speed_direction_not_a_valid_number">Die Windgeschwindigkeit oder -richtung ist keine valide Zahl. Bitte korrigieren Sie den Wert.</string>
<string name="wind_location_or_fields_not_valid">Die eingegebene Position, Windgeschwindigkeit oder -richtung ist leer. Bitte korrigieren Sie die Werte.</string> <string name="wind_location_or_fields_not_valid">Die eingegebene Position, Windgeschwindigkeit oder -richtung ist leer. Bitte korrigieren Sie die Werte.</string>
<string name="your_position">Aktuelle Position</string> <string name="your_position">Aktuelle Position</string>
<string name="waiting_for_gps">Warte auf GPS Signal&#8230;</string>
<string name="store_wind_entry">4. Windeingaben speichern</string> <string name="store_wind_entry">4. Windeingaben speichern</string>
<string name="store_wind">Windeingaben speichern</string> <string name="store_wind">Windeingaben speichern</string>
@@ -186,6 +189,8 @@
<string name="settings_category_general">Allgemeine Einstellungen</string> <string name="settings_category_general">Allgemeine Einstellungen</string>
<string name="settings_general_manageareas_header">Verwaltete Rennbahnen</string> <string name="settings_general_manageareas_header">Verwaltete Rennbahnen</string>
<string name="settings_general_manageareas_description">Verwaltete Rennbahnen einstellen (Komma-getrennt)</string> <string name="settings_general_manageareas_description">Verwaltete Rennbahnen einstellen (Komma-getrennt)</string>
<string name="settings_general_language_header">Sprache auswählen</string>
<string name="settings_general_language_description">Sprache auswählen. Eventuell muss die App neugestartet werden.</string>
<string name="settings_category_race">Renneinstellungen</string> <string name="settings_category_race">Renneinstellungen</string>
<string name="override_default_start_procedure_type_header">Standard Startprozedur überschreiben</string> <string name="override_default_start_procedure_type_header">Standard Startprozedur überschreiben</string>
@@ -196,7 +201,7 @@
<string name="settings_time_frame_description">Zeit in Minuten einstellen, welches Zeitlimit angibt</string> <string name="settings_time_frame_description">Zeit in Minuten einstellen, welches Zeitlimit angibt</string>
<string name="settings_category_advanced">Erweiterte Einstellungen</string> <string name="settings_category_advanced">Erweiterte Einstellungen</string>
<string name="settings_advanced_service_header">Wählen sie die Server URL aus.</string> <string name="settings_advanced_service_header">Wählen sie die Server URL aus. Nach Änderung muss die Option \'Neuladen\' gewählt werden.</string>
<string name="settings_advanced_service_description">Webservice URL einstellen.</string> <string name="settings_advanced_service_description">Webservice URL einstellen.</string>
<string name="settings_advanced_mail_description">Empfänger für Bilder der Ergebnislisten.</string> <string name="settings_advanced_mail_description">Empfänger für Bilder der Ergebnislisten.</string>
<string name="settings_advanced_mail_header">Empfänger Ergebnislisten</string> <string name="settings_advanced_mail_header">Empfänger Ergebnislisten</string>
@@ -233,4 +238,8 @@
<string name="number">Nummer</string> <string name="number">Nummer</string>
<string name="snap">Fotos schießen</string> <string name="snap">Fotos schießen</string>
<string name="logout_dialog_title">Möchten Sie sich abmelden?</string>
<string name="logout_dialog_message">Wenn Sie sich abmelden wird kein Rennen mehr verwaltet.</string>
<string name="logout">Abmelden</string>
</resources> </resources>
@@ -6,7 +6,9 @@
<string name="course_design_number_waypoints">Course has %s waypoints</string> <string name="course_design_number_waypoints">Course has %s waypoints</string>
<string name="choose_running_course">Choose running course</string> <string name="choose_running_course">Choose running course</string>
<string name="service_info">SAP Race Committee Service</string> <string name="service_info">SAP Race Committee Service</string>
<string name="service_title">Monitoring all races on selected event course.</string> <string name="service_text_no_races">Waiting for races to monitor&#8230;</string>
<string name="service_text_num_races">Currently monitoring %d races.</string>
<string name="app_name">SAP Race Committee Cockpit</string> <string name="app_name">SAP Race Committee Cockpit</string>
<string name="set_time">Start</string> <string name="set_time">Start</string>
<string name="reset_time">Restart</string> <string name="reset_time">Restart</string>
@@ -167,7 +169,7 @@
<string name="results_mail_subject">[Race Committee] %s</string> <string name="results_mail_subject">[Race Committee] %s</string>
<string name="results_mail_body">Results for race %s are attached.</string> <string name="results_mail_body">Results for race %s are attached.</string>
<string name="wind_direction_degrees">Wind direction (degrees)</string> <string name="wind_direction_degrees">Wind direction from (degrees)</string>
<string name="wind_speed_knots">Wind speed (knots)</string> <string name="wind_speed_knots">Wind speed (knots)</string>
<string name="enter_wind_direction">1. Enter the wind direction</string> <string name="enter_wind_direction">1. Enter the wind direction</string>
<string name="enter_wind_speed">2. Enter the wind speed</string> <string name="enter_wind_speed">2. Enter the wind speed</string>
@@ -178,7 +180,7 @@
<string name="longitude">Longitude</string> <string name="longitude">Longitude</string>
<string name="wind_speed_direction_not_a_valid_number">The wind speed or direction is not a valid number. Please correct the value.</string> <string name="wind_speed_direction_not_a_valid_number">The wind speed or direction is not a valid number. Please correct the value.</string>
<string name="wind_location_or_fields_not_valid">The entered location, wind speed or direction is empty. Please correct the values.</string> <string name="wind_location_or_fields_not_valid">The entered location, wind speed or direction is empty. Please correct the values.</string>
<string name="your_position">Your position</string> <string name="waiting_for_gps">Waiting for GPS Signal&#8230;</string>
<string name="store_wind_entry">4. Store wind entry</string> <string name="store_wind_entry">4. Store wind entry</string>
<string name="store_wind">Store wind</string> <string name="store_wind">Store wind</string>
@@ -186,6 +188,8 @@
<string name="settings_category_general">General Settings</string> <string name="settings_category_general">General Settings</string>
<string name="settings_general_manageareas_header">Managed Course Areas</string> <string name="settings_general_manageareas_header">Managed Course Areas</string>
<string name="settings_general_manageareas_description">Enter course area names managed by you (comma-separated)</string> <string name="settings_general_manageareas_description">Enter course area names managed by you (comma-separated)</string>
<string name="settings_general_language_header">Select Language</string>
<string name="settings_general_language_description">Select the language. You might need to restart the app to have changes applied.</string>
<string name="settings_category_race">Race Settings</string> <string name="settings_category_race">Race Settings</string>
<string name="override_default_start_procedure_type_header">Override default start procedure type</string> <string name="override_default_start_procedure_type_header">Override default start procedure type</string>
@@ -262,4 +266,8 @@
<string name="confirm_positionings">Confirm positionings</string> <string name="confirm_positionings">Confirm positionings</string>
<string name="snap">Snap</string> <string name="snap">Snap</string>
<string name="logout_dialog_title">Are you sure want to logout?</string>
<string name="logout_dialog_message">Logging out stops automatic monitoring for all races.</string>
<string name="logout">Logout</string>
</resources> </resources>
@@ -6,6 +6,7 @@ import com.sap.sailing.racecommittee.app.ui.fragments.raceinfo.SetStartTimeRaceF
public class AppConstants { public class AppConstants {
public static final boolean IS_DATA_OFFLINE = false; public static final boolean IS_DATA_OFFLINE = false;
public static final boolean ENABLE_LIFECYCLE_LOGGING = false;
// Intent extra fields // Intent extra fields
public final static String COURSE_AREA_UUID_KEY = "courseUuid"; public final static String COURSE_AREA_UUID_KEY = "courseUuid";
@@ -109,13 +109,13 @@ public class AppPreferences {
return Arrays.asList(managedCourseAreas); return Arrays.asList(managedCourseAreas);
} }
public static double getWindBearing(Context context) { public static double getWindBearingFromDirection(Context context) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
long windBearingAsLong = sp.getLong(PREFERENCE_WIND_BEARING, 0); long windBearingAsLong = sp.getLong(PREFERENCE_WIND_BEARING, 0);
return Double.longBitsToDouble(windBearingAsLong); return Double.longBitsToDouble(windBearingAsLong);
} }
public static void setWindBearing(Context context, double enteredWindBearing) { public static void setWindBearingFromDirection(Context context, double enteredWindBearing) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
long windBearingAsLong = Double.doubleToLongBits(enteredWindBearing); long windBearingAsLong = Double.doubleToLongBits(enteredWindBearing);
sp.edit().putLong(PREFERENCE_WIND_BEARING, windBearingAsLong).apply(); sp.edit().putLong(PREFERENCE_WIND_BEARING, windBearingAsLong).apply();
@@ -4,13 +4,14 @@ import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.Date; import java.util.Date;
import com.sap.sailing.racecommittee.app.logging.ExLog;
import com.sap.sailing.racecommittee.app.logging.FileLoggingTask;
import android.app.Application; import android.app.Application;
import android.content.Context; import android.content.Context;
import android.util.Log; import android.util.Log;
import com.sap.sailing.racecommittee.app.logging.ExLog;
import com.sap.sailing.racecommittee.app.logging.FileLoggingTask;
import com.sap.sailing.racecommittee.app.logging.LifecycleLogger;
/** /**
* Register a additional exception handler for uncaught exception to have some crash logging. * Register a additional exception handler for uncaught exception to have some crash logging.
*/ */
@@ -27,12 +28,12 @@ public class RaceApplication extends Application {
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
ExLog.i(TAG, "Application is starting"); ExLog.i(TAG, "Application is starting");
Thread.setDefaultUncaughtExceptionHandler(new LoggingExceptionHandler(Thread Thread.setDefaultUncaughtExceptionHandler(new LoggingExceptionHandler(Thread
.getDefaultUncaughtExceptionHandler())); .getDefaultUncaughtExceptionHandler()));
LifecycleLogger.enableLifecycleLogging(AppConstants.ENABLE_LIFECYCLE_LOGGING);
stringContext = new StringContext(new WeakReference<Context>(getApplicationContext())); stringContext = new StringContext(new WeakReference<Context>(getApplicationContext()));
} }
@@ -4,18 +4,23 @@ import com.sap.sailing.racecommittee.app.AppConstants;
import android.content.Context; import android.content.Context;
/**
* Base class for all data managers. Use {@link DataManager#create(Context)} for creating your {@link DataManager}.
*/
public abstract class DataManager implements ReadonlyDataManager { public abstract class DataManager implements ReadonlyDataManager {
public static ReadonlyDataManager create(Context context) { public static ReadonlyDataManager create(Context context) {
if (AppConstants.IS_DATA_OFFLINE) { if (AppConstants.IS_DATA_OFFLINE) {
return new OfflineDataManager(InMemoryDataStore.INSTANCE); return new OfflineDataManager(context, InMemoryDataStore.INSTANCE);
} }
return new OnlineDataManager(context, InMemoryDataStore.INSTANCE); return new OnlineDataManager(context, InMemoryDataStore.INSTANCE);
} }
protected Context context;
protected DataStore dataStore; protected DataStore dataStore;
public DataManager(DataStore dataStore) { public DataManager(Context context, DataStore dataStore) {
this.context = context;
this.dataStore = dataStore; this.dataStore = dataStore;
} }
@@ -23,4 +28,8 @@ public abstract class DataManager implements ReadonlyDataManager {
return dataStore; return dataStore;
} }
public Context getContext() {
return context;
}
} }
@@ -7,6 +7,10 @@ import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Callable;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.Context;
import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.CourseArea;
@@ -31,6 +35,8 @@ import com.sap.sailing.domain.racelog.RaceLogEventFactory;
import com.sap.sailing.domain.racelog.impl.RaceLogEventFactoryImpl; import com.sap.sailing.domain.racelog.impl.RaceLogEventFactoryImpl;
import com.sap.sailing.domain.racelog.impl.RaceLogImpl; import com.sap.sailing.domain.racelog.impl.RaceLogImpl;
import com.sap.sailing.racecommittee.app.data.clients.LoadClient; import com.sap.sailing.racecommittee.app.data.clients.LoadClient;
import com.sap.sailing.racecommittee.app.data.loaders.DataLoaderResult;
import com.sap.sailing.racecommittee.app.data.loaders.ImmediateDataLoaderCallbacks;
import com.sap.sailing.racecommittee.app.domain.ManagedRace; import com.sap.sailing.racecommittee.app.domain.ManagedRace;
import com.sap.sailing.racecommittee.app.domain.impl.ManagedRaceIdentifierImpl; import com.sap.sailing.racecommittee.app.domain.impl.ManagedRaceIdentifierImpl;
import com.sap.sailing.racecommittee.app.domain.impl.ManagedRaceImpl; import com.sap.sailing.racecommittee.app.domain.impl.ManagedRaceImpl;
@@ -39,8 +45,8 @@ public class OfflineDataManager extends DataManager {
private static boolean isInitialized = false; private static boolean isInitialized = false;
public OfflineDataManager(DataStore dataStore) { public OfflineDataManager(Context context, DataStore dataStore) {
super(dataStore); super(context, dataStore);
if (!isInitialized) { if (!isInitialized) {
isInitialized = true; isInitialized = true;
@@ -49,7 +55,8 @@ public class OfflineDataManager extends DataManager {
} }
private void fillDataStore(DataStore dataStore) { private void fillDataStore(DataStore dataStore) {
dataStore.addEvent(new EventBaseImpl("Extreme Sailing Series 2012 (Cardiff)", "Cardiff", "", true, "DUMBUUIDA")); dataStore
.addEvent(new EventBaseImpl("Extreme Sailing Series 2012 (Cardiff)", "Cardiff", "", true, "DUMBUUIDA"));
dataStore.addEvent(new EventBaseImpl("Extreme Sailing Series 2012 (Nice)", "Nice", "", true, "DUMBUUIDB")); dataStore.addEvent(new EventBaseImpl("Extreme Sailing Series 2012 (Nice)", "Nice", "", true, "DUMBUUIDB"));
dataStore.addEvent(new EventBaseImpl("Extreme Sailing Series 2012 (Rio)", "Rio", "", true, "DUMBUUIDC")); dataStore.addEvent(new EventBaseImpl("Extreme Sailing Series 2012 (Rio)", "Rio", "", true, "DUMBUUIDC"));
EventBase newEvent = new EventBaseImpl("Extreme Sailing Series 2013 (Muscat)", "Muscat", "", true, "FIXUUID"); EventBase newEvent = new EventBaseImpl("Extreme Sailing Series 2013 (Muscat)", "Muscat", "", true, "FIXUUID");
@@ -59,11 +66,8 @@ public class OfflineDataManager extends DataManager {
SeriesWithRows qualifying = new SeriesWithRowsImpl("Qualifying", false, null); SeriesWithRows qualifying = new SeriesWithRowsImpl("Qualifying", false, null);
SeriesWithRows medal = new SeriesWithRowsImpl("Medal", true, null); SeriesWithRows medal = new SeriesWithRowsImpl("Medal", true, null);
RaceGroup raceGroup = new RaceGroupImpl( RaceGroup raceGroup = new RaceGroupImpl("ESS", new BoatClassImpl("X40", false), null, Arrays.asList(qualifying,
"ESS", medal));
new BoatClassImpl("X40", false),
null,
Arrays.asList(qualifying, medal));
List<Competitor> competitors = new ArrayList<Competitor>(); List<Competitor> competitors = new ArrayList<Competitor>();
competitors.add(new CompetitorImpl(UUID.randomUUID(), "SAP Extreme Sailing Team", null, null)); competitors.add(new CompetitorImpl(UUID.randomUUID(), "SAP Extreme Sailing Team", null, null));
@@ -74,66 +78,39 @@ public class OfflineDataManager extends DataManager {
RaceLogEventFactory factory = new RaceLogEventFactoryImpl(); RaceLogEventFactory factory = new RaceLogEventFactoryImpl();
RaceLog log = new RaceLogImpl(UUID.randomUUID()); RaceLog log = new RaceLogImpl(UUID.randomUUID());
log.add(factory.createStartTimeEvent( log.add(factory.createStartTimeEvent(new MillisecondsTimePoint(new Date().getTime() - 2000), 1,
new MillisecondsTimePoint(new Date().getTime() - 2000),
1,
new MillisecondsTimePoint(new Date().getTime() - 1000))); new MillisecondsTimePoint(new Date().getTime() - 1000)));
log.add(factory.createRaceStatusEvent( log.add(factory.createRaceStatusEvent(new MillisecondsTimePoint(new Date().getTime()), 1,
new MillisecondsTimePoint(new Date().getTime()),
1,
RaceLogRaceStatus.FINISHING)); RaceLogRaceStatus.FINISHING));
ManagedRace q1 = new ManagedRaceImpl( ManagedRace q1 = new ManagedRaceImpl(new ManagedRaceIdentifierImpl("A.B", new FleetImpl("A"), qualifying,
new ManagedRaceIdentifierImpl( raceGroup), StartProcedureType.ESS, log);
"Q1",
new FleetImpl("Default"),
qualifying,
raceGroup),
StartProcedureType.ESS,
log);
log = new RaceLogImpl(UUID.randomUUID()); log = new RaceLogImpl(UUID.randomUUID());
/*log.add(factory.createStartTimeEvent( /*
new MillisecondsTimePoint(new Date()), * log.add(factory.createStartTimeEvent( new MillisecondsTimePoint(new Date()), 1, RaceLogRaceStatus.SCHEDULED,
1, * new MillisecondsTimePoint(new Date().getTime() + 100000)));
RaceLogRaceStatus.SCHEDULED,
new MillisecondsTimePoint(new Date().getTime() + 100000)));
*/ */
ManagedRace q2 = new ManagedRaceImpl( ManagedRace q2 = new ManagedRaceImpl(new ManagedRaceIdentifierImpl("B", new FleetImpl("A.A"), qualifying,
new ManagedRaceIdentifierImpl( raceGroup), StartProcedureType.ESS, log);
"Q2",
new FleetImpl("Default"),
qualifying,
raceGroup),
StartProcedureType.ESS,
log);
log = new RaceLogImpl(UUID.randomUUID()); log = new RaceLogImpl(UUID.randomUUID());
/*log.add(factory.createRaceStatusEvent( /*
new MillisecondsTimePoint(new Date()), * log.add(factory.createRaceStatusEvent( new MillisecondsTimePoint(new Date()), 5,
5, * RaceLogRaceStatus.FINISHED));
RaceLogRaceStatus.FINISHED));*/ */
ManagedRace q3 = new ManagedRaceImpl( ManagedRace q3 = new ManagedRaceImpl(new ManagedRaceIdentifierImpl("Q3", new FleetImpl("Default"), qualifying,
new ManagedRaceIdentifierImpl( raceGroup), StartProcedureType.ESS, log);
"Q3", /*
new FleetImpl("Default"), * ManagedRace m1 = new ManagedRaceImpl( new ManagedRaceIdentifierImpl( "M1", new FleetImpl("Default"), medal,
qualifying, * raceGroup), null);
raceGroup), */
StartProcedureType.ESS,
log);
/*ManagedRace m1 = new ManagedRaceImpl(
new ManagedRaceIdentifierImpl(
"M1",
new FleetImpl("Default"),
medal,
raceGroup),
null);*/
dataStore.addRace(q1); dataStore.addRace(q1);
dataStore.addRace(q2); dataStore.addRace(q2);
dataStore.addRace(q3); dataStore.addRace(q3);
//dataStore.addRace(m1); // dataStore.addRace(m1);
Mark m1 = new MarkImpl("Red"); Mark m1 = new MarkImpl("Red");
Mark m2 = new MarkImpl("Green"); Mark m2 = new MarkImpl("Green");
@@ -144,33 +121,74 @@ public class OfflineDataManager extends DataManager {
dataStore.addMark(m3); dataStore.addMark(m3);
} }
public void loadEvents(LoadClient<Collection<EventBase>> client) { @Override
client.onLoadSucceded(dataStore.getEvents()); public LoaderCallbacks<DataLoaderResult<Collection<EventBase>>> createEventsLoader(
LoadClient<Collection<EventBase>> callback) {
return new ImmediateDataLoaderCallbacks<Collection<EventBase>>(context, callback,
new Callable<Collection<EventBase>>() {
@Override
public Collection<EventBase> call() throws Exception {
return dataStore.getEvents();
} }
});
public void loadCourseAreas(Serializable parentEventId,
LoadClient<Collection<CourseArea>> client) {
client.onLoadSucceded(dataStore.getCourseAreas(dataStore.getEvent(parentEventId)));
}
public void loadRaces(Serializable courseAreaId,
LoadClient<Collection<ManagedRace>> client) {
client.onLoadSucceded(dataStore.getRaces());
} }
@Override @Override
public void loadMarks(ManagedRace managedRace, LoadClient<Collection<Mark>> client) { public LoaderCallbacks<DataLoaderResult<Collection<CourseArea>>> createCourseAreasLoader(
client.onLoadSucceded(dataStore.getMarks()); final Serializable parentEventId, LoadClient<Collection<CourseArea>> callback) {
return new ImmediateDataLoaderCallbacks<Collection<CourseArea>>(context, callback,
new Callable<Collection<CourseArea>>() {
@Override
public Collection<CourseArea> call() throws Exception {
return dataStore.getCourseAreas(dataStore.getEvent(parentEventId));
}
});
} }
@Override @Override
public void loadCourse(ManagedRace managedRace, LoadClient<CourseBase> client) { public LoaderCallbacks<DataLoaderResult<Collection<ManagedRace>>> createRacesLoader(Serializable courseAreaId,
client.onLoadSucceded(managedRace.getCourseOnServer()); LoadClient<Collection<ManagedRace>> callback) {
return new ImmediateDataLoaderCallbacks<Collection<ManagedRace>>(context, callback,
new Callable<Collection<ManagedRace>>() {
@Override
public Collection<ManagedRace> call() throws Exception {
return dataStore.getRaces();
}
});
} }
@Override @Override
public void loadCompetitors(ManagedRace managedRace, LoadClient<Collection<Competitor>> client) { public LoaderCallbacks<DataLoaderResult<Collection<Mark>>> createMarksLoader(ManagedRace managedRace,
client.onLoadSucceded((Collection<Competitor>) managedRace.getCompetitors()); LoadClient<Collection<Mark>> callback) {
return new ImmediateDataLoaderCallbacks<Collection<Mark>>(context, callback, new Callable<Collection<Mark>>() {
@Override
public Collection<Mark> call() throws Exception {
return dataStore.getMarks();
}
});
}
@Override
public LoaderCallbacks<DataLoaderResult<CourseBase>> createCourseLoader(final ManagedRace managedRace,
LoadClient<CourseBase> callback) {
return new ImmediateDataLoaderCallbacks<CourseBase>(context, callback, new Callable<CourseBase>() {
@Override
public CourseBase call() throws Exception {
return managedRace.getCourseOnServer();
}
});
}
@Override
public LoaderCallbacks<DataLoaderResult<Collection<Competitor>>> createCompetitorsLoader(
final ManagedRace managedRace, LoadClient<Collection<Competitor>> callback) {
return new ImmediateDataLoaderCallbacks<Collection<Competitor>>(context, callback,
new Callable<Collection<Competitor>>() {
@Override
public Collection<Competitor> call() throws Exception {
return managedRace.getCompetitors();
}
});
} }
} }
@@ -1,13 +1,15 @@
package com.sap.sailing.racecommittee.app.data; package com.sap.sailing.racecommittee.app.data;
import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.net.MalformedURLException; import java.net.URL;
import java.net.URI;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.Callable;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.Context; import android.content.Context;
import android.content.Loader;
import android.os.Bundle;
import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Competitor;
@@ -19,14 +21,18 @@ import com.sap.sailing.domain.base.SharedDomainFactory;
import com.sap.sailing.racecommittee.app.AppPreferences; import com.sap.sailing.racecommittee.app.AppPreferences;
import com.sap.sailing.racecommittee.app.data.clients.LoadClient; import com.sap.sailing.racecommittee.app.data.clients.LoadClient;
import com.sap.sailing.racecommittee.app.data.handlers.CompetitorsDataHandler; import com.sap.sailing.racecommittee.app.data.handlers.CompetitorsDataHandler;
import com.sap.sailing.racecommittee.app.data.handlers.CourseDataHandler; import com.sap.sailing.racecommittee.app.data.handlers.CourseBaseHandler;
import com.sap.sailing.racecommittee.app.data.handlers.DataHandler; import com.sap.sailing.racecommittee.app.data.handlers.DataHandler;
import com.sap.sailing.racecommittee.app.data.handlers.EventsDataHandler; import com.sap.sailing.racecommittee.app.data.handlers.EventsDataHandler;
import com.sap.sailing.racecommittee.app.data.handlers.ManagedRacesDataHandler; import com.sap.sailing.racecommittee.app.data.handlers.ManagedRacesDataHandler;
import com.sap.sailing.racecommittee.app.data.handlers.MarksDataHandler; import com.sap.sailing.racecommittee.app.data.handlers.MarksDataHandler;
import com.sap.sailing.racecommittee.app.data.loaders.DataLoader; import com.sap.sailing.racecommittee.app.data.loaders.DataLoaderCallbacks;
import com.sap.sailing.racecommittee.app.data.loaders.DataLoaderCallbacks.LoaderCreator;
import com.sap.sailing.racecommittee.app.data.loaders.DataLoaderResult;
import com.sap.sailing.racecommittee.app.data.loaders.ImmediateDataLoaderCallbacks;
import com.sap.sailing.racecommittee.app.data.loaders.OnlineDataLoader;
import com.sap.sailing.racecommittee.app.data.parsers.CompetitorsDataParser; import com.sap.sailing.racecommittee.app.data.parsers.CompetitorsDataParser;
import com.sap.sailing.racecommittee.app.data.parsers.CourseDataParser; import com.sap.sailing.racecommittee.app.data.parsers.CourseBaseParser;
import com.sap.sailing.racecommittee.app.data.parsers.DataParser; import com.sap.sailing.racecommittee.app.data.parsers.DataParser;
import com.sap.sailing.racecommittee.app.data.parsers.EventsDataParser; import com.sap.sailing.racecommittee.app.data.parsers.EventsDataParser;
import com.sap.sailing.racecommittee.app.data.parsers.ManagedRacesDataParser; import com.sap.sailing.racecommittee.app.data.parsers.ManagedRacesDataParser;
@@ -34,9 +40,10 @@ import com.sap.sailing.racecommittee.app.data.parsers.MarksDataParser;
import com.sap.sailing.racecommittee.app.domain.ManagedRace; import com.sap.sailing.racecommittee.app.domain.ManagedRace;
import com.sap.sailing.racecommittee.app.domain.ManagedRaceIdentifier; import com.sap.sailing.racecommittee.app.domain.ManagedRaceIdentifier;
import com.sap.sailing.racecommittee.app.domain.impl.DomainFactoryImpl; import com.sap.sailing.racecommittee.app.domain.impl.DomainFactoryImpl;
import com.sap.sailing.racecommittee.app.logging.ExLog;
import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; import com.sap.sailing.server.gateway.deserialization.JsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.coursedata.impl.ControlPointDeserializer; import com.sap.sailing.server.gateway.deserialization.coursedata.impl.ControlPointDeserializer;
import com.sap.sailing.server.gateway.deserialization.coursedata.impl.CourseDataDeserializer; import com.sap.sailing.server.gateway.deserialization.coursedata.impl.CourseBaseDeserializer;
import com.sap.sailing.server.gateway.deserialization.coursedata.impl.GateDeserializer; import com.sap.sailing.server.gateway.deserialization.coursedata.impl.GateDeserializer;
import com.sap.sailing.server.gateway.deserialization.coursedata.impl.MarkDeserializer; import com.sap.sailing.server.gateway.deserialization.coursedata.impl.MarkDeserializer;
import com.sap.sailing.server.gateway.deserialization.coursedata.impl.WaypointDeserializer; import com.sap.sailing.server.gateway.deserialization.coursedata.impl.WaypointDeserializer;
@@ -58,25 +65,10 @@ import com.sap.sailing.server.gateway.deserialization.racelog.impl.RaceLogEventD
* Enables accessing of data. * Enables accessing of data.
*/ */
public class OnlineDataManager extends DataManager { public class OnlineDataManager extends DataManager {
// private static final String TAG = OnlineDataManager.class.getName(); private static final String TAG = OnlineDataManager.class.getName();
private Context context;
OnlineDataManager(Context context, DataStore dataStore) { OnlineDataManager(Context context, DataStore dataStore) {
super(dataStore); super(context, dataStore);
this.context = context;
}
public Context getContext() {
return context;
}
public void loadEvents(LoadClient<Collection<EventBase>> client) {
if (dataStore.getEvents().isEmpty()) {
reloadEvents(client);
} else {
client.onLoadSucceded(dataStore.getEvents());
}
} }
public void addEvents(Collection<EventBase> events) { public void addEvents(Collection<EventBase> events) {
@@ -85,89 +77,88 @@ public class OnlineDataManager extends DataManager {
} }
} }
protected void reloadEvents(LoadClient<Collection<EventBase>> client) {
SharedDomainFactory domainFactory = DomainFactoryImpl.INSTANCE;
DataParser<Collection<EventBase>> parser = new EventsDataParser(new EventBaseJsonDeserializer(
new VenueJsonDeserializer(new CourseAreaJsonDeserializer(domainFactory))));
DataHandler<Collection<EventBase>> handler = new EventsDataHandler(this, client);
try {
new DataLoader<Collection<EventBase>>(context, URI.create(AppPreferences.getServerBaseURL(context)
+ "/sailingserver/events"), parser, handler).forceLoad();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void loadCourseAreas(final Serializable parentEventId, final LoadClient<Collection<CourseArea>> client) {
if (dataStore.hasEvent(parentEventId)) {
EventBase event = dataStore.getEvent(parentEventId);
client.onLoadSucceded(dataStore.getCourseAreas(event));
} else {
reloadEvents(new LoadClient<Collection<EventBase>>() {
public void onLoadSucceded(Collection<EventBase> data) {
if (dataStore.hasEvent(parentEventId)) {
EventBase event = dataStore.getEvent(parentEventId);
client.onLoadSucceded(dataStore.getCourseAreas(event));
} else {
client.onLoadFailed(new DataLoadingException(String.format(
"There was no event object found for id %s.", parentEventId)));
}
}
public void onLoadFailed(Exception reason) {
client.onLoadFailed(new DataLoadingException(
String.format(
"There was no event object found for id %s. While reloading the events an error occured: %s",
parentEventId, reason), reason));
}
});
}
}
public void addRaces(Collection<ManagedRace> data) { public void addRaces(Collection<ManagedRace> data) {
for (ManagedRace race : data) { for (ManagedRace race : data) {
dataStore.addRace(race); dataStore.addRace(race);
} }
} }
public void loadRaces(Serializable courseAreaId, LoadClient<Collection<ManagedRace>> client) {
if (!dataStore.hasCourseArea(courseAreaId)) {
client.onLoadFailed(new DataLoadingException(String.format("No course area found with id %s", courseAreaId)));
return;
}
SharedDomainFactory domainFactory = DomainFactoryImpl.INSTANCE;
JsonDeserializer<BoatClass> boatClassDeserializer = new BoatClassJsonDeserializer(domainFactory);
DataParser<Collection<ManagedRace>> parser = new ManagedRacesDataParser(context, new RaceGroupDeserializer(
boatClassDeserializer, new SeriesWithRowsDeserializer(new RaceRowDeserializer(new FleetDeserializer(
new ColorDeserializer()), new RaceCellDeserializer(
new RaceLogDeserializer(RaceLogEventDeserializer.create(domainFactory)))))));
DataHandler<Collection<ManagedRace>> handler = new ManagedRacesDataHandler(this, client);
try {
new DataLoader<Collection<ManagedRace>>(context, URI.create(AppPreferences.getServerBaseURL(context)
+ "/sailingserver/rc/racegroups?courseArea=" + courseAreaId.toString()), parser, handler)
.forceLoad();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void addMarks(Collection<Mark> marks) { public void addMarks(Collection<Mark> marks) {
for (Mark mark : marks) { for (Mark mark : marks) {
dataStore.addMark(mark); dataStore.addMark(mark);
} }
} }
public void loadMarks(ManagedRace managedRace, LoadClient<Collection<Mark>> client) { @Override
public LoaderCallbacks<DataLoaderResult<Collection<EventBase>>> createEventsLoader(
LoadClient<Collection<EventBase>> callback) {
return new DataLoaderCallbacks<Collection<EventBase>>(callback, new LoaderCreator<Collection<EventBase>>() {
@Override
public Loader<DataLoaderResult<Collection<EventBase>>> create(int id, Bundle args) throws Exception {
SharedDomainFactory domainFactory = DomainFactoryImpl.INSTANCE;
DataParser<Collection<EventBase>> parser = new EventsDataParser(new EventBaseJsonDeserializer(
new VenueJsonDeserializer(new CourseAreaJsonDeserializer(domainFactory))));
DataHandler<Collection<EventBase>> handler = new EventsDataHandler(OnlineDataManager.this);
ExLog.i(TAG, "getEventsLoader created new loader...");
return new OnlineDataLoader<Collection<EventBase>>(context, new URL(AppPreferences
.getServerBaseURL(context) + "/sailingserver/events"), parser, handler);
}
});
}
@Override
public LoaderCallbacks<DataLoaderResult<Collection<CourseArea>>> createCourseAreasLoader(
final Serializable parentEventId, LoadClient<Collection<CourseArea>> callback) {
return new ImmediateDataLoaderCallbacks<Collection<CourseArea>>(context, callback,
new Callable<Collection<CourseArea>>() {
@Override
public Collection<CourseArea> call() throws Exception {
if (dataStore.hasEvent(parentEventId)) {
EventBase event = dataStore.getEvent(parentEventId);
return dataStore.getCourseAreas(event);
} else {
throw new DataLoadingException(String.format(
"Unable to load course areas for unknown event %s.", parentEventId));
}
}
});
}
@Override
public LoaderCallbacks<DataLoaderResult<Collection<ManagedRace>>> createRacesLoader(final Serializable courseAreaId,
LoadClient<Collection<ManagedRace>> callback) {
return new DataLoaderCallbacks<Collection<ManagedRace>>(callback, new LoaderCreator<Collection<ManagedRace>>() {
@Override
public Loader<DataLoaderResult<Collection<ManagedRace>>> create(int id, Bundle args) throws Exception {
SharedDomainFactory domainFactory = DomainFactoryImpl.INSTANCE;
JsonDeserializer<BoatClass> boatClassDeserializer = new BoatClassJsonDeserializer(domainFactory);
DataParser<Collection<ManagedRace>> parser = new ManagedRacesDataParser(context,
new RaceGroupDeserializer(boatClassDeserializer, new SeriesWithRowsDeserializer(
new RaceRowDeserializer(new FleetDeserializer(new ColorDeserializer()),
new RaceCellDeserializer(new RaceLogDeserializer(
RaceLogEventDeserializer.create(domainFactory)))))));
DataHandler<Collection<ManagedRace>> handler = new ManagedRacesDataHandler(OnlineDataManager.this);
return new OnlineDataLoader<Collection<ManagedRace>>(context, new URL(AppPreferences
.getServerBaseURL(context)
+ "/sailingserver/rc/racegroups?courseArea="
+ courseAreaId.toString()), parser, handler);
}
});
}
@Override
public LoaderCallbacks<DataLoaderResult<Collection<Mark>>> createMarksLoader(final ManagedRace managedRace,
LoadClient<Collection<Mark>> callback) {
return new DataLoaderCallbacks<Collection<Mark>>(callback, new LoaderCreator<Collection<Mark>>() {
@Override
public Loader<DataLoaderResult<Collection<Mark>>> create(int id, Bundle args) throws Exception {
SharedDomainFactory domainFactory = DomainFactoryImpl.INSTANCE; SharedDomainFactory domainFactory = DomainFactoryImpl.INSTANCE;
JsonDeserializer<Mark> markDeserializer = new MarkDeserializer(domainFactory); JsonDeserializer<Mark> markDeserializer = new MarkDeserializer(domainFactory);
DataParser<Collection<Mark>> parser = new MarksDataParser(markDeserializer); DataParser<Collection<Mark>> parser = new MarksDataParser(markDeserializer);
DataHandler<Collection<Mark>> handler = new MarksDataHandler(this, client); DataHandler<Collection<Mark>> handler = new MarksDataHandler(OnlineDataManager.this);
ManagedRaceIdentifier identifier = managedRace.getIdentifier(); ManagedRaceIdentifier identifier = managedRace.getIdentifier();
@@ -175,29 +166,27 @@ public class OnlineDataManager extends DataManager {
String raceColumnName = URLEncoder.encode(identifier.getRaceName()); String raceColumnName = URLEncoder.encode(identifier.getRaceName());
String fleetName = URLEncoder.encode(identifier.getFleet().getName()); String fleetName = URLEncoder.encode(identifier.getFleet().getName());
try { return new OnlineDataLoader<Collection<Mark>>(context, new URL(AppPreferences
new DataLoader<Collection<Mark>>(context, URI.create(AppPreferences.getServerBaseURL(context) .getServerBaseURL(context)
+ "/sailingserver/rc/marks?leaderboard=" + raceGroupName + "&raceColumn=" + raceColumnName + "/sailingserver/rc/marks?leaderboard="
+ "&fleet=" + fleetName), parser, handler) + raceGroupName
.forceLoad(); + "&raceColumn=" + raceColumnName + "&fleet=" + fleetName), parser, handler);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} }
});
} }
@Override @Override
public void loadCourse(ManagedRace managedRace, LoadClient<CourseBase> client) { public LoaderCallbacks<DataLoaderResult<CourseBase>> createCourseLoader(final ManagedRace managedRace,
LoadClient<CourseBase> callback) {
return new DataLoaderCallbacks<CourseBase>(callback, new LoaderCreator<CourseBase>() {
@Override
public Loader<DataLoaderResult<CourseBase>> create(int id, Bundle args) throws Exception {
SharedDomainFactory domainFactory = DomainFactoryImpl.INSTANCE; SharedDomainFactory domainFactory = DomainFactoryImpl.INSTANCE;
JsonDeserializer<CourseBase> courseBaseDeserializer = new CourseDataDeserializer( JsonDeserializer<CourseBase> courseBaseDeserializer = new CourseBaseDeserializer(
new WaypointDeserializer( new WaypointDeserializer(new ControlPointDeserializer(new MarkDeserializer(domainFactory),
new ControlPointDeserializer( new GateDeserializer(domainFactory, new MarkDeserializer(domainFactory)))));
new MarkDeserializer(domainFactory), DataParser<CourseBase> parser = new CourseBaseParser(courseBaseDeserializer);
new GateDeserializer(domainFactory, DataHandler<CourseBase> handler = new CourseBaseHandler(OnlineDataManager.this, managedRace);
new MarkDeserializer(domainFactory)))));
DataParser<CourseBase> parser = new CourseDataParser(courseBaseDeserializer);
DataHandler<CourseBase> handler = new CourseDataHandler(this, client, managedRace);
ManagedRaceIdentifier identifier = managedRace.getIdentifier(); ManagedRaceIdentifier identifier = managedRace.getIdentifier();
@@ -205,24 +194,25 @@ public class OnlineDataManager extends DataManager {
String raceColumnName = URLEncoder.encode(identifier.getRaceName()); String raceColumnName = URLEncoder.encode(identifier.getRaceName());
String fleetName = URLEncoder.encode(identifier.getFleet().getName()); String fleetName = URLEncoder.encode(identifier.getFleet().getName());
try { return new OnlineDataLoader<CourseBase>(context, new URL(AppPreferences.getServerBaseURL(context)
new DataLoader<CourseBase>(context, URI.create(AppPreferences.getServerBaseURL(context) + "/sailingserver/rc/currentcourse?leaderboard=" + raceGroupName + "&raceColumn="
+ "/sailingserver/rc/currentcourse?leaderboard=" + raceGroupName + "&raceColumn=" + raceColumnName + raceColumnName + "&fleet=" + fleetName), parser, handler);
+ "&fleet=" + fleetName), parser, handler)
.forceLoad();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} }
});
} }
@Override @Override
public void loadCompetitors(ManagedRace managedRace, LoadClient<Collection<Competitor>> client) { public LoaderCallbacks<DataLoaderResult<Collection<Competitor>>> createCompetitorsLoader(
final ManagedRace managedRace, LoadClient<Collection<Competitor>> callback) {
return new DataLoaderCallbacks<Collection<Competitor>>(callback, new LoaderCreator<Collection<Competitor>>() {
@Override
public Loader<DataLoaderResult<Collection<Competitor>>> create(int id, Bundle args) throws Exception {
ExLog.i(TAG, String.format("Creating Competitor-OnlineDataLoader %d", id));
SharedDomainFactory domainFactory = DomainFactoryImpl.INSTANCE; SharedDomainFactory domainFactory = DomainFactoryImpl.INSTANCE;
JsonDeserializer<Competitor> competitorDeserializer = new CompetitorDeserializer(domainFactory); JsonDeserializer<Competitor> competitorDeserializer = new CompetitorDeserializer(domainFactory);
DataParser<Collection<Competitor>> parser = new CompetitorsDataParser(competitorDeserializer); DataParser<Collection<Competitor>> parser = new CompetitorsDataParser(competitorDeserializer);
DataHandler<Collection<Competitor>> handler = new CompetitorsDataHandler(this, client, managedRace); DataHandler<Collection<Competitor>> handler = new CompetitorsDataHandler(OnlineDataManager.this,
managedRace);
ManagedRaceIdentifier identifier = managedRace.getIdentifier(); ManagedRaceIdentifier identifier = managedRace.getIdentifier();
@@ -230,15 +220,12 @@ public class OnlineDataManager extends DataManager {
String raceColumnName = URLEncoder.encode(identifier.getRaceName()); String raceColumnName = URLEncoder.encode(identifier.getRaceName());
String fleetName = URLEncoder.encode(identifier.getFleet().getName()); String fleetName = URLEncoder.encode(identifier.getFleet().getName());
try { return new OnlineDataLoader<Collection<Competitor>>(context, new URL(AppPreferences
new DataLoader<Collection<Competitor>>(context, URI.create(AppPreferences.getServerBaseURL(context) .getServerBaseURL(context)
+ "/sailingserver/rc/competitors?leaderboard=" + raceGroupName + "&raceColumn=" + raceColumnName + "/sailingserver/rc/competitors?leaderboard="
+ "&fleet=" + fleetName), parser, handler) + raceGroupName
.forceLoad(); + "&raceColumn=" + raceColumnName + "&fleet=" + fleetName), parser, handler);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} }
});
} }
} }
@@ -3,27 +3,118 @@ package com.sap.sailing.racecommittee.app.data;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collection; import java.util.Collection;
import android.app.LoaderManager;
import android.app.LoaderManager.LoaderCallbacks;
import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.base.CourseBase; import com.sap.sailing.domain.base.CourseBase;
import com.sap.sailing.domain.base.EventBase; import com.sap.sailing.domain.base.EventBase;
import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.racecommittee.app.data.clients.LoadClient; import com.sap.sailing.racecommittee.app.data.clients.LoadClient;
import com.sap.sailing.racecommittee.app.data.loaders.DataLoaderResult;
import com.sap.sailing.racecommittee.app.domain.ManagedRace; import com.sap.sailing.racecommittee.app.domain.ManagedRace;
/**
* <p>
* Interface for everything read related when working with data.
* </p>
*
* <p>
* The result values of this interface's methods are meant to be used in conjunction with the Android's
* {@link LoaderManager} facility. Everything that is loaded by this interface is stored and cached in the underlying
* {@link DataStore}.
* </p>
*
* <p>
* If there are cached results your {@link LoadClient}'s callback methods might be called twice (first cached results;
* afterwards remote results). See {@link LoadClient} for restrictions that apply to your data handling code.
* </p>
*/
public interface ReadonlyDataManager { public interface ReadonlyDataManager {
/**
* Gets the underlying data store.
*
* @return the {@link DataStore}.
*/
public DataStore getDataStore(); public DataStore getDataStore();
public void loadEvents(LoadClient<Collection<EventBase>> client); /**
* Creates a new {@link LoaderCallbacks} object for loading {@link EventBase}s.
*
* @param callback
* {@link LoadClient} implementing your data handling code.
* @return {@link LoaderCallbacks} to be used in
* {@link LoaderManager#initLoader(int, android.os.Bundle, LoaderCallbacks)} or
* {@link LoaderManager#restartLoader(int, android.os.Bundle, LoaderCallbacks)}.
*/
public LoaderCallbacks<DataLoaderResult<Collection<EventBase>>> createEventsLoader(
LoadClient<Collection<EventBase>> callback);
public void loadCourseAreas(Serializable parentEventId, LoadClient<Collection<CourseArea>> client); /**
* Creates a new {@link LoaderCallbacks} object for loading {@link CourseArea}s.
*
* @param callback
* {@link LoadClient} implementing your data handling code.
* @return {@link LoaderCallbacks} to be used in
* {@link LoaderManager#initLoader(int, android.os.Bundle, LoaderCallbacks)} or
* {@link LoaderManager#restartLoader(int, android.os.Bundle, LoaderCallbacks)}.
*/
public LoaderCallbacks<DataLoaderResult<Collection<CourseArea>>> createCourseAreasLoader(
Serializable parentEventId, LoadClient<Collection<CourseArea>> callback);
public void loadRaces(Serializable courseAreaId, LoadClient<Collection<ManagedRace>> client); /**
* Creates a new {@link LoaderCallbacks} object for loading {@link ManagedRace}s of a specific {@link CourseArea}.
*
* @param courseAreaId
* the {@link CourseArea}'s Identifier.
* @param callback
* {@link LoadClient} implementing your data handling code.
* @return {@link LoaderCallbacks} to be used in
* {@link LoaderManager#initLoader(int, android.os.Bundle, LoaderCallbacks)} or
* {@link LoaderManager#restartLoader(int, android.os.Bundle, LoaderCallbacks)}.
*/
public LoaderCallbacks<DataLoaderResult<Collection<ManagedRace>>> createRacesLoader(Serializable courseAreaId,
LoadClient<Collection<ManagedRace>> callback);
public void loadMarks(ManagedRace managedRace, LoadClient<Collection<Mark>> client); /**
* Creates a new {@link LoaderCallbacks} object for loading a race's {@link Mark}s.
*
* @param managedRace
* the {@link ManagedRace}.
* @param callback
* {@link LoadClient} implementing your data handling code.
* @return {@link LoaderCallbacks} to be used in
* {@link LoaderManager#initLoader(int, android.os.Bundle, LoaderCallbacks)} or
* {@link LoaderManager#restartLoader(int, android.os.Bundle, LoaderCallbacks)}.
*/
public LoaderCallbacks<DataLoaderResult<Collection<Mark>>> createMarksLoader(ManagedRace managedRace,
LoadClient<Collection<Mark>> callback);
public void loadCourse(ManagedRace managedRace, LoadClient<CourseBase> client); /**
* Creates a new {@link LoaderCallbacks} object for loading a race's {@link CourseBase}.
*
* @param managedRace
* the {@link ManagedRace}.
* @param callback
* {@link LoadClient} implementing your data handling code.
* @return {@link LoaderCallbacks} to be used in
* {@link LoaderManager#initLoader(int, android.os.Bundle, LoaderCallbacks)} or
* {@link LoaderManager#restartLoader(int, android.os.Bundle, LoaderCallbacks)}.
*/
public LoaderCallbacks<DataLoaderResult<CourseBase>> createCourseLoader(ManagedRace managedRace,
LoadClient<CourseBase> callback);
public void loadCompetitors(ManagedRace managedRace, LoadClient<Collection<Competitor>> client); /**
* Creates a new {@link LoaderCallbacks} object for loading {@link Competitor}s.
*
* @param callback
* {@link LoadClient} implementing your data handling code.
* @return {@link LoaderCallbacks} to be used in
* {@link LoaderManager#initLoader(int, android.os.Bundle, LoaderCallbacks)} or
* {@link LoaderManager#restartLoader(int, android.os.Bundle, LoaderCallbacks)}.
*/
public LoaderCallbacks<DataLoaderResult<Collection<Competitor>>> createCompetitorsLoader(ManagedRace managedRace,
LoadClient<Collection<Competitor>> callback);
} }
@@ -1,6 +1,33 @@
package com.sap.sailing.racecommittee.app.data.clients; package com.sap.sailing.racecommittee.app.data.clients;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.Loader;
/**
* Interface to hide complexity of {@link LoaderCallbacks#onLoadFinished(Loader, Object)}. Keep in mind that all
* restrictions that apply on the LoaderCallback's method also apply to all methods of this interface.
*
* @param <T>
* loaded data type.
*/
public interface LoadClient<T> { public interface LoadClient<T> {
/**
* Called when a {@link Loader} returned a failure to its {@link LoaderCallbacks#onLoadFinished(Loader, Object)}.
*
* @param reason
* the loading has failed.
*/
public void onLoadFailed(Exception reason); public void onLoadFailed(Exception reason);
public void onLoadSucceded(T data);
/**
* Called when a {@link Loader} returned successfully to its {@link LoaderCallbacks#onLoadFinished(Loader, Object)}.
*
* @param data
* that was loaded.
* @param isCached
* <code>true</code> if returned data is cached data.
*/
public void onLoadSucceded(T data, boolean isCached);
} }
@@ -4,21 +4,29 @@ import java.util.Collection;
import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.racecommittee.app.data.OnlineDataManager; import com.sap.sailing.racecommittee.app.data.OnlineDataManager;
import com.sap.sailing.racecommittee.app.data.clients.LoadClient;
import com.sap.sailing.racecommittee.app.domain.ManagedRace; import com.sap.sailing.racecommittee.app.domain.ManagedRace;
public class CompetitorsDataHandler extends DataHandler<Collection<Competitor>> { public class CompetitorsDataHandler extends DataHandler<Collection<Competitor>> {
private ManagedRace race; private ManagedRace race;
public CompetitorsDataHandler(OnlineDataManager manager, LoadClient<Collection<Competitor>> client, ManagedRace managedRace) { public CompetitorsDataHandler(OnlineDataManager manager, ManagedRace managedRace) {
super(manager, client); super(manager);
race = managedRace; race = managedRace;
} }
@Override @Override
public void onLoaded(Collection<Competitor> data) { public boolean hasCachedResults() {
super.onLoaded(data); return race.getCompetitors() != null;
}
@Override
public Collection<Competitor> getCachedResults() {
return race.getCompetitors();
}
@Override
public void onResult(Collection<Competitor> data) {
race.setCompetitors(data); race.setCompetitors(data);
} }
@@ -0,0 +1,31 @@
package com.sap.sailing.racecommittee.app.data.handlers;
import com.sap.sailing.domain.base.CourseBase;
import com.sap.sailing.racecommittee.app.data.OnlineDataManager;
import com.sap.sailing.racecommittee.app.domain.ManagedRace;
public class CourseBaseHandler extends DataHandler<CourseBase> {
private ManagedRace race;
public CourseBaseHandler(OnlineDataManager manager, ManagedRace managedRace) {
super(manager);
race = managedRace;
}
@Override
public boolean hasCachedResults() {
return race.getCourseOnServer() != null;
}
@Override
public CourseBase getCachedResults() {
return race.getCourseOnServer();
}
@Override
public void onResult(CourseBase data) {
race.setCourseOnServer(data);
}
}
@@ -1,23 +0,0 @@
package com.sap.sailing.racecommittee.app.data.handlers;
import com.sap.sailing.domain.base.CourseBase;
import com.sap.sailing.racecommittee.app.data.OnlineDataManager;
import com.sap.sailing.racecommittee.app.data.clients.LoadClient;
import com.sap.sailing.racecommittee.app.domain.ManagedRace;
public class CourseDataHandler extends DataHandler<CourseBase> {
private ManagedRace race;
public CourseDataHandler(OnlineDataManager manager, LoadClient<CourseBase> client, ManagedRace managedRace) {
super(manager, client);
race = managedRace;
}
@Override
public void onLoaded(CourseBase data) {
super.onLoaded(data);
race.setCourseOnServer(data);
}
}
@@ -1,22 +1,21 @@
package com.sap.sailing.racecommittee.app.data.handlers; package com.sap.sailing.racecommittee.app.data.handlers;
import com.sap.sailing.racecommittee.app.data.OnlineDataManager; import com.sap.sailing.racecommittee.app.data.OnlineDataManager;
import com.sap.sailing.racecommittee.app.data.clients.LoadClient;
public abstract class DataHandler<T> { public abstract class DataHandler<T> {
protected OnlineDataManager manager; protected OnlineDataManager manager;
protected LoadClient<T> client;
public DataHandler(OnlineDataManager manager, LoadClient<T> client) { public DataHandler(OnlineDataManager manager) {
this.manager = manager; this.manager = manager;
this.client = client;
} }
public void onLoaded(T data) { public abstract void onResult(T data);
client.onLoadSucceded(data);
public boolean hasCachedResults() {
return false;
} }
public void onFailed(Exception reason) { public T getCachedResults() {
client.onLoadFailed(reason); return null;
} }
} }
@@ -4,18 +4,26 @@ import java.util.Collection;
import com.sap.sailing.domain.base.EventBase; import com.sap.sailing.domain.base.EventBase;
import com.sap.sailing.racecommittee.app.data.OnlineDataManager; import com.sap.sailing.racecommittee.app.data.OnlineDataManager;
import com.sap.sailing.racecommittee.app.data.clients.LoadClient;
public class EventsDataHandler extends DataHandler<Collection<EventBase>> { public class EventsDataHandler extends DataHandler<Collection<EventBase>> {
public EventsDataHandler(OnlineDataManager manager, LoadClient<Collection<EventBase>> client) { public EventsDataHandler(OnlineDataManager manager) {
super(manager, client); super(manager);
} }
@Override @Override
public void onLoaded(Collection<EventBase> data) { public void onResult(Collection<EventBase> data) {
super.onLoaded(data);
manager.addEvents(data); manager.addEvents(data);
} }
@Override
public boolean hasCachedResults() {
return !manager.getDataStore().getEvents().isEmpty();
}
@Override
public Collection<EventBase> getCachedResults() {
return manager.getDataStore().getEvents();
}
} }
@@ -3,18 +3,26 @@ package com.sap.sailing.racecommittee.app.data.handlers;
import java.util.Collection; import java.util.Collection;
import com.sap.sailing.racecommittee.app.data.OnlineDataManager; import com.sap.sailing.racecommittee.app.data.OnlineDataManager;
import com.sap.sailing.racecommittee.app.data.clients.LoadClient;
import com.sap.sailing.racecommittee.app.domain.ManagedRace; import com.sap.sailing.racecommittee.app.domain.ManagedRace;
public class ManagedRacesDataHandler extends DataHandler<Collection<ManagedRace>> { public class ManagedRacesDataHandler extends DataHandler<Collection<ManagedRace>> {
public ManagedRacesDataHandler(OnlineDataManager manager, LoadClient<Collection<ManagedRace>> client) { public ManagedRacesDataHandler(OnlineDataManager manager) {
super(manager, client); super(manager);
} }
@Override @Override
public void onLoaded(Collection<ManagedRace> data) { public boolean hasCachedResults() {
super.onLoaded(data); return !manager.getDataStore().getRaces().isEmpty();
}
@Override
public Collection<ManagedRace> getCachedResults() {
return manager.getDataStore().getRaces();
}
@Override
public void onResult(Collection<ManagedRace> data) {
manager.addRaces(data); manager.addRaces(data);
} }
@@ -4,17 +4,25 @@ import java.util.Collection;
import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.racecommittee.app.data.OnlineDataManager; import com.sap.sailing.racecommittee.app.data.OnlineDataManager;
import com.sap.sailing.racecommittee.app.data.clients.LoadClient;
public class MarksDataHandler extends DataHandler<Collection<Mark>> { public class MarksDataHandler extends DataHandler<Collection<Mark>> {
public MarksDataHandler(OnlineDataManager manager, LoadClient<Collection<Mark>> client) { public MarksDataHandler(OnlineDataManager manager) {
super(manager, client); super(manager);
} }
@Override @Override
public void onLoaded(Collection<Mark> data) { public boolean hasCachedResults() {
super.onLoaded(data); return !manager.getDataStore().getMarks().isEmpty();
}
@Override
public Collection<Mark> getCachedResults() {
return manager.getDataStore().getMarks();
}
@Override
public void onResult(Collection<Mark> data) {
manager.addMarks(data); manager.addMarks(data);
} }
@@ -4,17 +4,17 @@ import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.MalformedURLException; import java.net.URL;
import java.net.URI;
public class HttpGetRequest extends HttpRequest { public class HttpGetRequest extends HttpRequest {
public HttpGetRequest(URI requestUri) throws MalformedURLException, IOException { public HttpGetRequest(URL requestUrl) {
super(requestUri.toURL()); super(requestUrl);
} }
@Override @Override
protected InputStream execute(HttpURLConnection connection) throws IOException { protected BufferedInputStream execute(HttpURLConnection connection) throws IOException {
return new BufferedInputStream(connection.getInputStream()); InputStream stream = connection.getInputStream();
return new BufferedInputStream(stream);
} }
} }
@@ -3,11 +3,9 @@ package com.sap.sailing.racecommittee.app.data.http;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.MalformedURLException; import java.net.URL;
import java.net.URI;
import java.nio.charset.Charset; import java.nio.charset.Charset;
public class HttpJsonPostRequest extends HttpRequest { public class HttpJsonPostRequest extends HttpRequest {
@@ -15,14 +13,13 @@ public class HttpJsonPostRequest extends HttpRequest {
private String requestBody; private String requestBody;
public HttpJsonPostRequest(URI requestUri, String body) throws MalformedURLException, IOException { public HttpJsonPostRequest(URL requestUrl, String body) {
super(requestUri.toURL()); super(requestUrl);
this.requestBody = body; this.requestBody = body;
} }
@Override @Override
protected InputStream execute(HttpURLConnection connection) throws IOException { protected BufferedInputStream execute(HttpURLConnection connection) throws IOException {
// connection.setDoInput(true);
connection.setDoOutput(true); connection.setDoOutput(true);
connection.setChunkedStreamingMode(0); connection.setChunkedStreamingMode(0);
@@ -1,5 +1,8 @@
package com.sap.sailing.racecommittee.app.data.http; package com.sap.sailing.racecommittee.app.data.http;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@@ -28,25 +31,24 @@ public abstract class HttpRequest {
throw new IOException(String.format("Request response had no valid status.")); throw new IOException(String.format("Request response had no valid status."));
} }
private HttpURLConnection connection; private URL url;
public HttpRequest(URL url) throws IOException { public HttpRequest(URL url) {
this((HttpURLConnection) url.openConnection()); this.url = url;
} }
public HttpRequest(HttpURLConnection connection) { protected abstract BufferedInputStream execute(HttpURLConnection connection) throws IOException;
this.connection = connection;
}
protected abstract InputStream execute(HttpURLConnection connection) throws IOException;
public InputStream execute() throws Exception { public InputStream execute() throws Exception {
ExLog.i(TAG, String.format("(Request %d) Executing HTTP request on %s.", this.hashCode(), connection.getURL())); ExLog.i(TAG, String.format("(Request %d) Executing HTTP request on %s.", this.hashCode(), url));
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(5000); connection.setConnectTimeout(5000);
connection.setReadTimeout(15000); connection.setReadTimeout(15000);
connection.setRequestProperty("connection", "close");
InputStream stream = null; BufferedInputStream stream = null;
try { try {
stream = execute(connection); stream = execute(connection);
} catch (FileNotFoundException fnfe) { } catch (FileNotFoundException fnfe) {
@@ -55,12 +57,21 @@ public abstract class HttpRequest {
} }
validateHttpResponse(connection); validateHttpResponse(connection);
byte[] streamData = copyStream(stream);
connection.disconnect();
ExLog.i(TAG, String.format("(Request %d) HTTP request executed.", this.hashCode())); ExLog.i(TAG, String.format("(Request %d) HTTP request executed.", this.hashCode()));
return stream; return new ByteArrayInputStream(streamData);
} }
public void disconnect() { private byte[] copyStream(BufferedInputStream stream) throws IOException {
connection.disconnect(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
int len;
byte[] buffer = new byte[4096];
while ((len = stream.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
return bos.toByteArray();
} }
} }
@@ -1,80 +0,0 @@
package com.sap.sailing.racecommittee.app.data.loaders;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URI;
import android.content.AsyncTaskLoader;
import android.content.Context;
import com.sap.sailing.racecommittee.app.data.handlers.DataHandler;
import com.sap.sailing.racecommittee.app.data.http.HttpGetRequest;
import com.sap.sailing.racecommittee.app.data.http.HttpRequest;
import com.sap.sailing.racecommittee.app.data.parsers.DataParser;
import com.sap.sailing.racecommittee.app.logging.ExLog;
public class DataLoader<T> extends AsyncTaskLoader<T> {
private static final String TAG = DataLoader.class.getName();
protected DataParser<T> dataParser;
protected DataHandler<T> dataHandler;
protected HttpRequest httpRequest;
protected volatile Exception lastException;
public DataLoader(Context context, URI httpGetUri, DataParser<T> dataParser, DataHandler<T> dataHandler)
throws MalformedURLException, IOException {
this(context, new HttpGetRequest(httpGetUri), dataParser, dataHandler);
}
public DataLoader(Context context, HttpRequest request, DataParser<T> dataParser, DataHandler<T> dataHandler) {
super(context);
this.httpRequest = request;
this.dataParser = dataParser;
this.dataHandler = dataHandler;
}
@Override
public T loadInBackground() {
lastException = null;
try {
return loadDataInBackground();
} catch (Exception e) {
lastException = e;
ExLog.e(TAG, String.format("Exception while loading data:\n%s", e.toString()));
StringWriter stringWriter = new StringWriter();
PrintWriter stream = new PrintWriter(stringWriter);
e.printStackTrace(stream);
ExLog.e(TAG, stringWriter.toString());
}
return null;
}
private T loadDataInBackground() throws Exception {
Reader reader = null;
try {
InputStream inputStream = httpRequest.execute();
reader = new InputStreamReader(inputStream);
return dataParser.parse(reader);
} finally {
if (reader != null) {
reader.close();
}
httpRequest.disconnect();
}
}
@Override
public void deliverResult(T data) {
if (data == null) {
dataHandler.onFailed(lastException);
} else {
dataHandler.onLoaded(data);
}
}
}
@@ -0,0 +1,67 @@
package com.sap.sailing.racecommittee.app.data.loaders;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.Loader;
import android.os.Bundle;
import com.sap.sailing.racecommittee.app.data.clients.LoadClient;
import com.sap.sailing.racecommittee.app.logging.ExLog;
/**
* <p>
* {@link LoaderCallbacks} wrapper to be used with {@link LoadClient}s.
* </p>
*
* <p>
* On result {@link DataLoaderResult#isSuccessful()} is checked. If true
* {@link LoadClient#onLoadSucceded(Object, boolean)} is called; other {@link LoadClient#onLoadFailed(Exception)} is
* called.
* </p>
*
* <p>
* There is currently no handling of a {@link Loader}'s reset.
* </p>
*
* @param <T>
* result type.
*/
public class DataLoaderCallbacks<T> implements LoaderCallbacks<DataLoaderResult<T>> {
private static String TAG = DataLoaderCallbacks.class.getName();
public interface LoaderCreator<T> {
Loader<DataLoaderResult<T>> create(int id, Bundle args) throws Exception;
}
private LoadClient<T> clientCallback;
private LoaderCreator<T> loaderCreator;
public DataLoaderCallbacks(LoadClient<T> clientCallback, LoaderCreator<T> loaderCreator) {
this.clientCallback = clientCallback;
this.loaderCreator = loaderCreator;
}
@Override
public Loader<DataLoaderResult<T>> onCreateLoader(int id, Bundle args) {
try {
return loaderCreator.create(id, args);
} catch (Exception e) {
ExLog.ex(TAG, e);
}
throw new IllegalStateException("Exception while creating a loader.");
}
@Override
public void onLoadFinished(Loader<DataLoaderResult<T>> loader, DataLoaderResult<T> result) {
if (result.isSuccessful()) {
clientCallback.onLoadSucceded(result.getResult(), result.isResultCached());
} else {
clientCallback.onLoadFailed(result.getException());
}
}
@Override
public void onLoaderReset(Loader<DataLoaderResult<T>> loader) {
// currently we ignore loader resets...
}
}
@@ -0,0 +1,54 @@
package com.sap.sailing.racecommittee.app.data.loaders;
/**
* <p>
* Wrapper class for data loading results.
* </p>
*
* <p>
* A {@link DataLoaderResult} may be successful or failed. It carries either the failure's {@link Exception} or the
* actual result.
* </p>
*
* @param <T>
* result type.
*/
public class DataLoaderResult<T> {
// private static final String TAG = DataLoaderResult.class.getName();
private final Exception exception;
private final boolean cached;
private final T result;
public DataLoaderResult(T result, boolean isCached) {
this.result = result;
this.cached = isCached;
this.exception = null;
}
public DataLoaderResult(Exception exception) {
this.exception = exception;
this.cached = false;
this.result = null;
}
public boolean isSuccessful() {
return exception == null;
}
public Exception getException() {
return exception;
}
public T getResult() {
return result;
}
public boolean isResultCached() {
if (!isSuccessful()) {
throw new IllegalStateException("Failures cannot be cached!");
}
return cached;
}
}
@@ -0,0 +1,40 @@
package com.sap.sailing.racecommittee.app.data.loaders;
import java.util.concurrent.Callable;
import android.content.Context;
import android.content.Loader;
/**
* A {@link Loader} that (immediately) returns a {@link Callable}'s result. All results are announced as non-cached.
*
* @param <T> result type.
*/
public class ImmediateDataLoader<T> extends Loader<DataLoaderResult<T>> {
private Callable<T> resultCallable;
public ImmediateDataLoader(Context context, Callable<T> resultCallable) {
super(context);
this.resultCallable = resultCallable;
}
@Override
protected void onStartLoading() {
publishResult();
}
@Override
protected void onForceLoad() {
publishResult();
}
private void publishResult() {
try {
// We act as a never caching DataLoader to ensure that it looks like remote access!
deliverResult(new DataLoaderResult<T>(resultCallable.call(), false));
} catch (Exception e) {
deliverResult(new DataLoaderResult<T>(e));
}
}
}
@@ -0,0 +1,25 @@
package com.sap.sailing.racecommittee.app.data.loaders;
import java.util.concurrent.Callable;
import android.content.Context;
import android.content.Loader;
import android.os.Bundle;
import com.sap.sailing.racecommittee.app.data.clients.LoadClient;
/**
* Simple wrapper for a {@link DataLoaderCallbacks} creating an {@link ImmediateDataLoader}.
* @param <T> the result type.
*/
public class ImmediateDataLoaderCallbacks<T> extends DataLoaderCallbacks<T> {
public ImmediateDataLoaderCallbacks(final Context context, LoadClient<T> clientCallback,
final Callable<T> loadingFunc) {
super(clientCallback, new LoaderCreator<T>() {
@Override
public Loader<DataLoaderResult<T>> create(int id, Bundle args) throws Exception {
return new ImmediateDataLoader<T>(context, loadingFunc);
}
});
}
}
@@ -0,0 +1,110 @@
package com.sap.sailing.racecommittee.app.data.loaders;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import android.content.AsyncTaskLoader;
import android.content.Context;
import android.content.Loader;
import com.sap.sailing.racecommittee.app.data.handlers.DataHandler;
import com.sap.sailing.racecommittee.app.data.http.HttpGetRequest;
import com.sap.sailing.racecommittee.app.data.http.HttpRequest;
import com.sap.sailing.racecommittee.app.data.parsers.DataParser;
import com.sap.sailing.racecommittee.app.logging.ExLog;
/**
* <p>
* A {@link Loader} that loads data by accessing a remote resource.
* </p>
*
* <p>
* An {@link OnlineDataLoader} may return cached results as announced by its {@link DataHandler}. Call
* {@link OnlineDataLoader#forceLoad()} to ensure that the remote resource is checked for new data.
* </p>
*
* <p>
* The data returned by the remote resource is parsed by the given {@link DataParser} and may be cached through your
* implementation of {@link DataHandler#onResult(Object)}.
* </p>
*
* @param <T>
* result type.
*/
public class OnlineDataLoader<T> extends AsyncTaskLoader<DataLoaderResult<T>> {
private static final String TAG = OnlineDataLoader.class.getName();
protected DataParser<T> dataParser;
protected DataHandler<T> dataHandler;
protected HttpRequest httpRequest;
/**
* Initializes a new {@link OnlineDataLoader} which initiates HTTP GET requests to load the remote data.
*/
public OnlineDataLoader(Context context, URL requestUrl, DataParser<T> dataParser, DataHandler<T> dataHandler) {
this(context, new HttpGetRequest(requestUrl), dataParser, dataHandler);
}
/**
* Initializes a new {@link OnlineDataLoader} which executes the given {@link HttpRequest} to load the remote data.
*/
public OnlineDataLoader(Context context, HttpRequest request, DataParser<T> dataParser, DataHandler<T> dataHandler) {
super(context);
this.httpRequest = request;
this.dataParser = dataParser;
this.dataHandler = dataHandler;
ExLog.i(TAG, String.format("Loader created: %s", Integer.toHexString(this.hashCode())));
}
@Override
protected void onStartLoading() {
if (dataHandler.hasCachedResults()) {
ExLog.i(TAG, String.format("Using cached results... %s", Integer.toHexString(this.hashCode())));
deliverResult(new DataLoaderResult<T>(dataHandler.getCachedResults(), true));
} else {
ExLog.i(TAG, String.format("No cached results. Forcing load now %s", Integer.toHexString(this.hashCode())));
forceLoad();
}
}
@Override
protected void onForceLoad() {
ExLog.i(TAG, String.format("Forcing load %s", Integer.toHexString(this.hashCode())));
super.onForceLoad();
}
@Override
public void deliverResult(DataLoaderResult<T> result) {
if (result.isSuccessful()) {
dataHandler.onResult(result.getResult());
}
super.deliverResult(result);
}
@Override
public DataLoaderResult<T> loadInBackground() {
try {
return new DataLoaderResult<T>(loadDataInBackground(), false);
} catch (Exception e) {
ExLog.ex(TAG, e);
return new DataLoaderResult<T>(e);
}
}
private T loadDataInBackground() throws Exception {
Reader reader = null;
try {
InputStream inputStream = httpRequest.execute();
reader = new InputStreamReader(inputStream);
return dataParser.parse(reader);
} finally {
if (reader != null) {
reader.close();
}
}
}
}
@@ -9,11 +9,11 @@ import com.sap.sailing.domain.base.CourseBase;
import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; import com.sap.sailing.server.gateway.deserialization.JsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.impl.Helpers; import com.sap.sailing.server.gateway.deserialization.impl.Helpers;
public class CourseDataParser implements DataParser<CourseBase> { public class CourseBaseParser implements DataParser<CourseBase> {
private JsonDeserializer<CourseBase> deserializer; private JsonDeserializer<CourseBase> deserializer;
public CourseDataParser(JsonDeserializer<CourseBase> deserializer) { public CourseBaseParser(JsonDeserializer<CourseBase> deserializer) {
this.deserializer = deserializer; this.deserializer = deserializer;
} }
@@ -32,7 +32,14 @@ public class FleetIdentifierImpl implements FleetIdentifier {
} }
public Serializable getId() { public Serializable getId() {
return String.format("%s.%s.%s", getRaceGroup().getName(), getSeries().getName(), getFleet().getName()); return String.format("%s.%s.%s",
escapeIdentifierFragment(getRaceGroup().getName()),
escapeIdentifierFragment(getSeries().getName()),
escapeIdentifierFragment(getFleet().getName()));
}
protected String escapeIdentifierFragment(String fragment) {
return fragment.replace(".", "\\.");
} }
} }
@@ -12,11 +12,7 @@ public class ManagedRaceIdentifierImpl extends FleetIdentifierImpl implements Ma
private String raceName; private String raceName;
public ManagedRaceIdentifierImpl( public ManagedRaceIdentifierImpl(String raceName, Fleet fleetWithRaceNames, SeriesBase series, RaceGroup raceGroup) {
String raceName,
Fleet fleetWithRaceNames,
SeriesBase series,
RaceGroup raceGroup) {
super(fleetWithRaceNames, series, raceGroup); super(fleetWithRaceNames, series, raceGroup);
this.raceName = raceName; this.raceName = raceName;
} }
@@ -31,7 +27,7 @@ public class ManagedRaceIdentifierImpl extends FleetIdentifierImpl implements Ma
@Override @Override
public Serializable getId() { public Serializable getId() {
return String.format("%s.%s", super.getId(), getRaceName()); return String.format("%s.%s", super.getId(), escapeIdentifierFragment(getRaceName()));
} }
} }
@@ -1,5 +1,7 @@
package com.sap.sailing.racecommittee.app.logging; package com.sap.sailing.racecommittee.app.logging;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Date; import java.util.Date;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ArrayBlockingQueue;
@@ -128,6 +130,10 @@ public class ExLog {
getInstance().error(tag, msg); getInstance().error(tag, msg);
} }
public synchronized static void ex(String tag, Exception e) {
getInstance().exception(tag, e);
}
private final static String logMsgTemplate = "%s - %s - %s : %s"; private final static String logMsgTemplate = "%s - %s - %s : %s";
private enum LogLevel { private enum LogLevel {
@@ -180,6 +186,19 @@ public class ExLog {
logToFile(LogLevel.ERROR, tag, msg); logToFile(LogLevel.ERROR, tag, msg);
} }
private void exception(String tag, Exception e) {
StringWriter stringWriter = new StringWriter();
PrintWriter stream = new PrintWriter(stringWriter);
e.printStackTrace(stream);
String msg = stringWriter.toString();
if (isTargetActive(Target.LOGCAT))
Log.e(tag, msg);
if (isTargetActive(Target.FILE))
logToFile(LogLevel.ERROR, tag, msg);
}
private boolean isTargetActive(Target logcat) { private boolean isTargetActive(Target logcat) {
return activeLoggingTargets.contains(logcat); return activeLoggingTargets.contains(logcat);
} }
@@ -0,0 +1,21 @@
package com.sap.sailing.racecommittee.app.logging;
/**
* Stolen from API Level 14.
*
*/
public interface LifecycleCallbacks {
void onCreate(Object activityOrFragment);
void onStart(Object activityOrFragment);
void onResume(Object activityOrFragment);
void onPause(Object activityOrFragment);
void onStop(Object activityOrFragment);
void onSaveInstanceState(Object activityOrFragment);
void onDestroy(Object activityOrFragment);
}
@@ -0,0 +1,58 @@
package com.sap.sailing.racecommittee.app.logging;
/**
* Logger to track all activity and fragment lifecycle events.
*/
public class LifecycleLogger implements LifecycleCallbacks {
private static final String TAG = LifecycleLogger.class.getName();
private static boolean isLifecycleLoggingEnabled = false;
public static void enableLifecycleLogging(boolean enabled) {
isLifecycleLoggingEnabled = enabled;
}
private void onEvent(Object activityOrFragment, String action) {
if (isLifecycleLoggingEnabled) {
ExLog.i(TAG,
String.format("%s %s %s", action, activityOrFragment.getClass().getSimpleName(),
Integer.toHexString(System.identityHashCode(activityOrFragment))));
}
}
@Override
public void onCreate(Object activityOrFragment) {
onEvent(activityOrFragment, "Creating");
}
@Override
public void onStart(Object activityOrFragment) {
onEvent(activityOrFragment, "Starting");
}
@Override
public void onResume(Object activityOrFragment) {
onEvent(activityOrFragment, "Resuming");
}
@Override
public void onPause(Object activityOrFragment) {
onEvent(activityOrFragment, "Pausing");
}
@Override
public void onStop(Object activityOrFragment) {
onEvent(activityOrFragment, "Stopping");
}
@Override
public void onSaveInstanceState(Object activityOrFragment) {
onEvent(activityOrFragment, "Saving instance");
}
@Override
public void onDestroy(Object activityOrFragment) {
onEvent(activityOrFragment, "Destroying");
}
}
@@ -9,13 +9,15 @@ import java.util.Map.Entry;
import java.util.UUID; import java.util.UUID;
import android.app.AlarmManager; import android.app.AlarmManager;
import android.app.Notification; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Binder; import android.os.Binder;
import android.os.IBinder; import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
@@ -38,6 +40,7 @@ import com.sap.sailing.server.gateway.serialization.impl.CompetitorJsonSerialize
import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogEventSerializer; import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogEventSerializer;
public class RaceStateService extends Service { public class RaceStateService extends Service {
private final static String TAG = RaceStateService.class.getName(); private final static String TAG = RaceStateService.class.getName();
/** /**
@@ -50,6 +53,7 @@ public class RaceStateService extends Service {
} }
private final static String EXTRAS_SERVICE_ID = RaceStateService.class.getName() + ".serviceId"; private final static String EXTRAS_SERVICE_ID = RaceStateService.class.getName() + ".serviceId";
private final static int NOTIFICATION_ID = 42;
private final IBinder mBinder = new RaceStateServiceBinder(); private final IBinder mBinder = new RaceStateServiceBinder();
@@ -67,6 +71,9 @@ public class RaceStateService extends Service {
private Map<Serializable, List<PendingIntent>> managedIntents; private Map<Serializable, List<PendingIntent>> managedIntents;
private NotificationManager notificationManager;
private NotificationCompat.Builder notificationBuilder;
@Override @Override
public void onCreate() { public void onCreate() {
this.serviceId = UUID.randomUUID(); this.serviceId = UUID.randomUUID();
@@ -78,9 +85,26 @@ public class RaceStateService extends Service {
this.registeredStateEventListeners = new HashMap<ManagedRace, RaceStateEventListener>(); this.registeredStateEventListeners = new HashMap<ManagedRace, RaceStateEventListener>();
this.managedIntents = new HashMap<Serializable, List<PendingIntent>>(); this.managedIntents = new HashMap<Serializable, List<PendingIntent>>();
startForeground(); notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
ExLog.i(TAG, "Started."); setupNotificationBuilder();
startForeground(NOTIFICATION_ID, notificationBuilder.build());
super.onCreate(); super.onCreate();
ExLog.i(TAG, "Started.");
}
private void setupNotificationBuilder() {
Intent launcherIntent = new Intent(this, LoginActivity.class);
launcherIntent.setAction(Intent.ACTION_MAIN);
launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, launcherIntent, 0);
notificationBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.sap_sailing_app_icon)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.sap_sailing_app_icon))
.setContentTitle(getText(R.string.service_info))
.setContentText(getText(R.string.service_text_no_races))
.setContentIntent(contentIntent)
.setOngoing(true);
} }
@Override @Override
@@ -132,27 +156,6 @@ public class RaceStateService extends Service {
ExLog.i(TAG, "All races unregistered."); ExLog.i(TAG, "All races unregistered.");
} }
private void startForeground() {
CharSequence text = getText(R.string.service_title);
// Set the icon, scrolling text and timestamp
Notification notification = new Notification(R.drawable.sap_sailing_app_icon, text, System.currentTimeMillis());
Intent intent = new Intent(this, LoginActivity.class);
// clear all activities above started and reuse if already existing!
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
// The PendingIntent to launch our activity if the user selects this
// notification
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, 0);
// Set the info for the views that show in the notification panel.
notification.setLatestEventInfo(this, getText(R.string.service_info), text, contentIntent);
notification.flags |= Notification.FLAG_NO_CLEAR;
// Send the notification.
this.startForeground(42, notification);
}
private void handleStartCommand(Intent intent) { private void handleStartCommand(Intent intent) {
String action = intent.getAction(); String action = intent.getAction();
ExLog.i(TAG, String.format("Command action '%s' received.", action)); ExLog.i(TAG, String.format("Command action '%s' received.", action));
@@ -197,6 +200,11 @@ public class RaceStateService extends Service {
private void handleClearRaces(Intent intent) { private void handleClearRaces(Intent intent) {
unregisterAllRaces(); unregisterAllRaces();
clearAllRaces(); clearAllRaces();
notificationManager.notify(NOTIFICATION_ID, notificationBuilder
.setNumber(0)
.setContentText(getString(R.string.service_text_no_races))
.build());
} }
private void clearAllRaces() { private void clearAllRaces() {
@@ -211,6 +219,12 @@ public class RaceStateService extends Service {
return; return;
} }
registerRace(race); registerRace(race);
int numRaces = managedIntents.keySet().size();
notificationManager.notify(NOTIFICATION_ID, notificationBuilder
.setNumber(numRaces)
.setContentText(String.format(getString(R.string.service_text_num_races), numRaces))
.build());
} }
private ManagedRace getRaceFromIntent(Intent intent) { private ManagedRace getRaceFromIntent(Intent intent) {
@@ -245,7 +259,7 @@ public class RaceStateService extends Service {
ExLog.i(TAG, "Race " + race.getId() + " registered."); ExLog.i(TAG, "Race " + race.getId() + " registered.");
} else { } else {
ExLog.e(TAG, "Race " + race.getId() + " was already registered. Ignoring."); ExLog.w(TAG, "Race " + race.getId() + " was already registered. Ignoring.");
} }
} }
@@ -1,8 +1,7 @@
package com.sap.sailing.racecommittee.app.services.sending; package com.sap.sailing.racecommittee.app.services.sending;
import java.io.InputStream;
import java.io.Serializable; import java.io.Serializable;
import java.net.URI; import java.net.URL;
import android.content.Intent; import android.content.Intent;
import android.os.AsyncTask; import android.os.AsyncTask;
@@ -44,13 +43,8 @@ public class EventSenderTask extends AsyncTask<Intent, Void, Pair<Intent, Intege
try { try {
ExLog.i(TAG, "Posting event: " + serializedEvent); ExLog.i(TAG, "Posting event: " + serializedEvent);
HttpRequest post = new HttpJsonPostRequest(URI.create(url), serializedEvent.toString()); HttpRequest post = new HttpJsonPostRequest(new URL(url), serializedEvent.toString());
try { post.execute().close();
final InputStream inputStream = post.execute();
inputStream.close();
} finally {
post.disconnect();
}
ExLog.i(TAG, "Post successful for the following event: " + serializedEvent); ExLog.i(TAG, "Post successful for the following event: " + serializedEvent);
} catch (Exception e) { } catch (Exception e) {
ExLog.e(TAG, String.format("Post not successful, exception occured: %s", e.toString())); ExLog.e(TAG, String.format("Post not successful, exception occured: %s", e.toString()));
@@ -2,12 +2,10 @@ package com.sap.sailing.racecommittee.app.ui.activities;
import java.util.Date; import java.util.Date;
import android.app.Activity;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@@ -25,7 +23,7 @@ import com.sap.sailing.racecommittee.app.services.sending.EventSendingService.Ev
/** /**
* Base activity for all race committee cockpit activities enabling basic menu functionality. * Base activity for all race committee cockpit activities enabling basic menu functionality.
*/ */
public abstract class BaseActivity extends Activity { public abstract class BaseActivity extends LoggableActivity {
private class EventSendingServiceConnection implements ServiceConnection, EventSendingServiceLogger { private class EventSendingServiceConnection implements ServiceConnection, EventSendingServiceLogger {
@Override @Override
@@ -77,7 +75,6 @@ public abstract class BaseActivity extends Activity {
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.options_menu_settings: case R.id.options_menu_settings:
ExLog.i(TAG, "Clicked SETTINGS."); ExLog.i(TAG, "Clicked SETTINGS.");
@@ -86,8 +83,7 @@ public abstract class BaseActivity extends Activity {
case R.id.options_menu_reload: case R.id.options_menu_reload:
ExLog.i(TAG, "Clicked RESET."); ExLog.i(TAG, "Clicked RESET.");
InMemoryDataStore.INSTANCE.reset(); InMemoryDataStore.INSTANCE.reset();
fadeActivity(LoginActivity.class, true); return onReset();
return true;
case R.id.options_menu_live: case R.id.options_menu_live:
ExLog.i(TAG, "Clicked LIVE."); ExLog.i(TAG, "Clicked LIVE.");
Toast.makeText(this, getLiveIconText(), Toast.LENGTH_LONG).show(); Toast.makeText(this, getLiveIconText(), Toast.LENGTH_LONG).show();
@@ -110,30 +106,33 @@ public abstract class BaseActivity extends Activity {
return super.onPrepareOptionsMenu(menu); return super.onPrepareOptionsMenu(menu);
} }
@Override
public void onBackPressed() {
super.onBackPressed();
ExLog.i(TAG, String.format("Back pressed on activity %s", this.getClass().getSimpleName()));
}
protected boolean onHomeClicked() { protected boolean onHomeClicked() {
return false; return false;
} }
@Override protected boolean onReset() {
protected void onCreate(Bundle savedInstanceState) { fadeActivity(LoginActivity.class, true);
super.onCreate(savedInstanceState); return true;
ExLog.i(TAG, String.format("Creating activity %s", this.getClass().getSimpleName()));
} }
@Override @Override
protected void onStart() { public void onStart() {
super.onStart(); super.onStart();
ExLog.i(TAG, String.format("Starting activity %s", this.getClass().getSimpleName()));
Intent intent = new Intent(this, EventSendingService.class); Intent intent = new Intent(this, EventSendingService.class);
bindService(intent, sendingServiceConnection, Context.BIND_AUTO_CREATE); bindService(intent, sendingServiceConnection, Context.BIND_AUTO_CREATE);
} }
@Override @Override
protected void onStop() { public void onStop() {
super.onStop(); super.onStop();
ExLog.i(TAG, String.format("Stopping activity %s", this.getClass().getSimpleName()));
if (boundSendingService) { if (boundSendingService) {
unbindService(sendingServiceConnection); unbindService(sendingServiceConnection);
boundSendingService = false; boundSendingService = false;
@@ -0,0 +1,51 @@
package com.sap.sailing.racecommittee.app.ui.activities;
import android.app.Activity;
import android.os.Bundle;
import com.sap.sailing.racecommittee.app.logging.LifecycleLogger;
public abstract class LoggableActivity extends Activity {
private LifecycleLogger lifeLogger;
public LoggableActivity() {
this.lifeLogger = new LifecycleLogger();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
lifeLogger.onCreate(this);
}
@Override
public void onStart() {
super.onStart();
lifeLogger.onStart(this);
}
@Override
public void onResume() {
super.onResume();
lifeLogger.onResume(this);
}
@Override
public void onPause() {
super.onPause();
lifeLogger.onPause(this);
}
@Override
public void onStop() {
super.onStop();
lifeLogger.onStop(this);
}
@Override
public void onDestroy() {
super.onDestroy();
lifeLogger.onDestroy(this);
}
}
@@ -15,7 +15,7 @@ import com.sap.sailing.racecommittee.app.AppConstants;
import com.sap.sailing.racecommittee.app.AppPreferences; import com.sap.sailing.racecommittee.app.AppPreferences;
import com.sap.sailing.racecommittee.app.R; import com.sap.sailing.racecommittee.app.R;
import com.sap.sailing.racecommittee.app.logging.ExLog; import com.sap.sailing.racecommittee.app.logging.ExLog;
import com.sap.sailing.racecommittee.app.ui.fragments.dialogs.BaseDialogFragment; import com.sap.sailing.racecommittee.app.ui.fragments.dialogs.AttachedDialogFragment;
import com.sap.sailing.racecommittee.app.ui.fragments.dialogs.DialogListenerHost; import com.sap.sailing.racecommittee.app.ui.fragments.dialogs.DialogListenerHost;
import com.sap.sailing.racecommittee.app.ui.fragments.dialogs.LoginDialog; import com.sap.sailing.racecommittee.app.ui.fragments.dialogs.LoginDialog;
import com.sap.sailing.racecommittee.app.ui.fragments.lists.CourseAreaListFragment; import com.sap.sailing.racecommittee.app.ui.fragments.lists.CourseAreaListFragment;
@@ -28,6 +28,8 @@ public class LoginActivity extends BaseActivity implements EventSelectedListener
CourseAreaSelectedListenerHost, DialogListenerHost { CourseAreaSelectedListenerHost, DialogListenerHost {
private final static String TAG = LoginActivity.class.getName(); private final static String TAG = LoginActivity.class.getName();
private final static String CourseAreaListFragmentTag = "CourseAreaListFragmentTag";
private LoginDialog loginDialog; private LoginDialog loginDialog;
private CourseArea selectedCourseArea; private CourseArea selectedCourseArea;
@@ -36,6 +38,16 @@ public class LoginActivity extends BaseActivity implements EventSelectedListener
this.selectedCourseArea = null; this.selectedCourseArea = null;
} }
@Override
protected boolean onReset() {
Fragment courseAreaFragment = getFragmentManager().findFragmentByTag(CourseAreaListFragmentTag);
if (courseAreaFragment != null) {
getFragmentManager().beginTransaction().remove(courseAreaFragment).commit();
}
recreate();
return true;
}
/** Called when the activity is first created. */ /** Called when the activity is first created. */
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@@ -49,13 +61,14 @@ public class LoginActivity extends BaseActivity implements EventSelectedListener
// on first create add event list fragment // on first create add event list fragment
if (savedInstanceState == null) { if (savedInstanceState == null) {
ExLog.i(TAG, "Seems to be first start. Creating event fragment.");
addEventListFragment(); addEventListFragment();
} }
} }
private void addEventListFragment() { private void addEventListFragment() {
FragmentTransaction transaction = getFragmentManager().beginTransaction(); FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.add(R.id.login_view_left_container, new EventListFragment()); transaction.replace(R.id.login_view_left_container, new EventListFragment());
transaction.setTransition(FragmentTransaction.TRANSIT_NONE); transaction.setTransition(FragmentTransaction.TRANSIT_NONE);
transaction.commit(); transaction.commit();
} }
@@ -68,7 +81,7 @@ public class LoginActivity extends BaseActivity implements EventSelectedListener
fragment.setArguments(args); fragment.setArguments(args);
FragmentTransaction transaction = getFragmentManager().beginTransaction(); FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setCustomAnimations(R.animator.slide_in, R.animator.slide_out); transaction.setCustomAnimations(R.animator.slide_in, R.animator.slide_out);
transaction.replace(R.id.login_view_right_container, fragment); transaction.replace(R.id.login_view_right_container, fragment, CourseAreaListFragmentTag);
transaction.commit(); transaction.commit();
ExLog.i("LoginActivity", "CourseFragment created."); ExLog.i("LoginActivity", "CourseFragment created.");
} }
@@ -87,7 +100,6 @@ public class LoginActivity extends BaseActivity implements EventSelectedListener
} }
private void showCourseAreaListFragment(Serializable eventId) { private void showCourseAreaListFragment(Serializable eventId) {
Toast.makeText(LoginActivity.this, eventId.toString(), Toast.LENGTH_LONG).show();
addCourseAreaListFragment(eventId); addCourseAreaListFragment(eventId);
} }
@@ -114,7 +126,7 @@ public class LoginActivity extends BaseActivity implements EventSelectedListener
return new DialogResultListener() { return new DialogResultListener() {
@Override @Override
public void onDialogPositiveButton(BaseDialogFragment dialog) { public void onDialogPositiveButton(AttachedDialogFragment dialog) {
switch (loginDialog.getSelectedLoginType()) { switch (loginDialog.getSelectedLoginType()) {
case OFFICER: case OFFICER:
ExLog.i(TAG, "Communication with backend is active."); ExLog.i(TAG, "Communication with backend is active.");
@@ -136,14 +148,13 @@ public class LoginActivity extends BaseActivity implements EventSelectedListener
return; return;
} }
Toast.makeText(LoginActivity.this, selectedCourseArea.getId().toString(), Toast.LENGTH_LONG).show();
Intent message = new Intent(LoginActivity.this, RacingActivity.class); Intent message = new Intent(LoginActivity.this, RacingActivity.class);
message.putExtra(AppConstants.COURSE_AREA_UUID_KEY, selectedCourseArea.getId()); message.putExtra(AppConstants.COURSE_AREA_UUID_KEY, selectedCourseArea.getId());
fadeActivity(message); fadeActivity(message);
} }
@Override @Override
public void onDialogNegativeButton(BaseDialogFragment dialog) { public void onDialogNegativeButton(AttachedDialogFragment dialog) {
/* nothing here... */ /* nothing here... */
} }
}; };
@@ -31,13 +31,16 @@ import com.sap.sailing.racecommittee.app.ui.fragments.RaceInfoFragment;
import com.sap.sailing.racecommittee.app.ui.fragments.lists.ManagedRaceListFragment; import com.sap.sailing.racecommittee.app.ui.fragments.lists.ManagedRaceListFragment;
import com.sap.sailing.racecommittee.app.ui.fragments.lists.ManagedRaceListFragment.FilterMode; import com.sap.sailing.racecommittee.app.ui.fragments.lists.ManagedRaceListFragment.FilterMode;
import com.sap.sailing.racecommittee.app.ui.fragments.raceinfo.RaceInfoListener; import com.sap.sailing.racecommittee.app.ui.fragments.raceinfo.RaceInfoListener;
import com.sap.sailing.racecommittee.app.utils.CollectionUtils;
public class RacingActivity extends BaseActivity implements RaceInfoListener { public class RacingActivity extends SessionActivity implements RaceInfoListener {
// private final static String TAG = RacingActivity.class.getName(); // private final static String TAG = RacingActivity.class.getName();
private final static String ListFragmentTag = RacingActivity.class.getName() + ".ManagedRaceListFragment"; private final static String ListFragmentTag = RacingActivity.class.getName() + ".ManagedRaceListFragment";
private static final String TAG = RacingActivity.class.getName(); private static final String TAG = RacingActivity.class.getName();
private static final int RacesLoaderId = 0;
private class FilterModeSelectionBinder implements OnNavigationListener { private class FilterModeSelectionBinder implements OnNavigationListener {
private ManagedRaceListFragment targetList; private ManagedRaceListFragment targetList;
@@ -68,6 +71,59 @@ public class RacingActivity extends BaseActivity implements RaceInfoListener {
} }
} }
private class RaceLoadClient implements LoadClient<Collection<ManagedRace>> {
private Collection<ManagedRace> lastSeenRaces = null;
private CourseArea courseArea;
public RaceLoadClient(CourseArea courseArea) {
this.courseArea = courseArea;
}
@Override
public void onLoadSucceded(Collection<ManagedRace> data, boolean isCached) {
setProgressBarIndeterminateVisibility(false);
// Let's do the setup stuff only when the data is changed (or its the first time)
if (lastSeenRaces != null && CollectionUtils.isEqualCollection(data, lastSeenRaces)) {
ExLog.i(TAG, "Same races are already loaded...");
} else {
lastSeenRaces = data;
registerOnService(data);
raceListFragment.setupOn(data);
Toast.makeText(RacingActivity.this,
String.format(getString(R.string.racing_load_success), data.size()), Toast.LENGTH_SHORT).show();
}
}
@Override
public void onLoadFailed(Exception reason) {
setProgressBarIndeterminateVisibility(false);
AlertDialog.Builder builder = new AlertDialog.Builder(RacingActivity.this);
builder.setMessage(String.format(getString(R.string.generic_load_failure), reason.getMessage()))
.setTitle(getString(R.string.loading_failure)).setIcon(R.drawable.ic_dialog_alert_holo_light)
.setCancelable(true)
.setPositiveButton(getString(R.string.retry), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
setProgressBarIndeterminateVisibility(true);
ExLog.i(TAG, "Issuing a reload of managed races");
getLoaderManager().restartLoader(RacesLoaderId, null,
dataManager.createRacesLoader(courseArea.getId(), RaceLoadClient.this));
dialog.cancel();
}
}).setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
builder.create().show();
}
};
/** /**
* Selection list of all races * Selection list of all races
*/ */
@@ -80,12 +136,6 @@ public class RacingActivity extends BaseActivity implements RaceInfoListener {
private ReadonlyDataManager dataManager; private ReadonlyDataManager dataManager;
@Override
protected boolean onHomeClicked() {
fadeActivity(LoginActivity.class, true);
return true;
}
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
// features must be requested before anything else // features must be requested before anything else
@@ -109,12 +159,15 @@ public class RacingActivity extends BaseActivity implements RaceInfoListener {
Toast.makeText(this, getString(R.string.racing_course_area_missing), Toast.LENGTH_LONG).show(); Toast.makeText(this, getString(R.string.racing_course_area_missing), Toast.LENGTH_LONG).show();
} else { } else {
setupActionBar(courseArea); setupActionBar(courseArea);
// unload and unregister from all races here!
unloadAllRaces();
loadRaces(courseArea); loadRaces(courseArea);
} }
} }
@Override
public void onBackPressed() {
logoutSession();
}
private ManagedRaceListFragment getOrCreateRaceListFragment() { private ManagedRaceListFragment getOrCreateRaceListFragment() {
Fragment fragment = getFragmentManager().findFragmentByTag(ListFragmentTag); Fragment fragment = getFragmentManager().findFragmentByTag(ListFragmentTag);
// on first create add race list fragment // on first create add race list fragment
@@ -149,8 +202,6 @@ public class RacingActivity extends BaseActivity implements RaceInfoListener {
private void setupActionBar(CourseArea courseArea) { private void setupActionBar(CourseArea courseArea) {
ActionBar actionBar = getActionBar(); ActionBar actionBar = getActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(String.format(getString(R.string.racingview_header), courseArea.getName())); actionBar.setTitle(String.format(getString(R.string.racingview_header), courseArea.getName()));
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
@@ -160,40 +211,12 @@ public class RacingActivity extends BaseActivity implements RaceInfoListener {
new FilterModeSelectionBinder(raceListFragment, actionBar, adapter, ManagedRaceListFragment.FilterMode.values()); new FilterModeSelectionBinder(raceListFragment, actionBar, adapter, ManagedRaceListFragment.FilterMode.values());
} }
private void unloadAllRaces() {
ExLog.i(TAG, "Issuing intent action clear races");
Intent intent = new Intent(AppConstants.INTENT_ACTION_CLEAR_RACES);
this.startService(intent);
}
private void loadRaces(final CourseArea courseArea) { private void loadRaces(final CourseArea courseArea) {
setProgressBarIndeterminateVisibility(true); setProgressBarIndeterminateVisibility(true);
ExLog.i(TAG, "Issuing loading of managed races from data manager"); ExLog.i(TAG, "Issuing loading of managed races from data manager");
dataManager.loadRaces(courseArea.getId(), new LoadClient<Collection<ManagedRace>>() { getLoaderManager().initLoader(RacesLoaderId, null,
public void onLoadSucceded(Collection<ManagedRace> data) { dataManager.createRacesLoader(courseArea.getId(), new RaceLoadClient(courseArea)));
onLoadRacesSucceded(courseArea, data);
}
public void onLoadFailed(Exception reason) {
onLoadRacesFailed(courseArea, reason);
}
});
}
private void onLoadRacesSucceded(CourseArea courseArea, Collection<ManagedRace> data) {
setProgressBarIndeterminateVisibility(false);
registerOnService(data);
raceListFragment.setupOn(data);
Toast.makeText(RacingActivity.this, String.format(getString(R.string.racing_load_success), data.size()),
Toast.LENGTH_SHORT).show();
}
private void onLoadRacesFailed(final CourseArea courseArea, Exception reason) {
setProgressBarIndeterminateVisibility(false);
showLoadFailedDialog(courseArea, reason.getMessage());
} }
private void registerOnService(Collection<ManagedRace> races) { private void registerOnService(Collection<ManagedRace> races) {
@@ -207,26 +230,6 @@ public class RacingActivity extends BaseActivity implements RaceInfoListener {
} }
} }
private void showLoadFailedDialog(final CourseArea courseArea, String message) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(
String.format(getString(R.string.generic_load_failure), message))
.setTitle(getString(R.string.loading_failure)).setIcon(R.drawable.ic_dialog_alert_holo_light).setCancelable(true)
.setPositiveButton(getString(R.string.retry), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
loadRaces(courseArea);
}
}).setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
if (!isFinishing()) {
AlertDialog alert = builder.create();
alert.show();
}
}
public void onRaceItemClicked(ManagedRace managedRace) { public void onRaceItemClicked(ManagedRace managedRace) {
infoFragment = new RaceInfoFragment(); infoFragment = new RaceInfoFragment();
infoFragment.setArguments(RaceFragment.createArguments(managedRace)); infoFragment.setArguments(RaceFragment.createArguments(managedRace));
@@ -29,7 +29,7 @@ import com.sap.sailing.racecommittee.app.ui.adapters.PhotoAdapter;
import com.sap.sailing.racecommittee.app.ui.views.CameraView; import com.sap.sailing.racecommittee.app.ui.views.CameraView;
import com.sap.sailing.racecommittee.app.utils.MailHelper; import com.sap.sailing.racecommittee.app.utils.MailHelper;
public class ResultsCapturingActivity extends BaseActivity { public class ResultsCapturingActivity extends SessionActivity {
private static String ARGUMENTS_KEY_SUBJECT = "subject"; private static String ARGUMENTS_KEY_SUBJECT = "subject";
private static String ARGUMENTS_KEY_TEXT = "text"; private static String ARGUMENTS_KEY_TEXT = "text";
@@ -54,7 +54,7 @@ public class ResultsCapturingActivity extends BaseActivity {
} }
@Override @Override
protected void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setupActionBar(); setupActionBar();
@@ -111,13 +111,13 @@ public class ResultsCapturingActivity extends BaseActivity {
} }
@Override @Override
protected void onResume() { public void onResume() {
super.onResume(); super.onResume();
setupCamera(); setupCamera();
} }
@Override @Override
protected void onPause() { public void onPause() {
super.onPause(); super.onPause();
releaseCamera(); releaseCamera();
} }
@@ -161,12 +161,6 @@ public class ResultsCapturingActivity extends BaseActivity {
currentImageFile = createFinisherImageFile(currentImageIndex++); currentImageFile = createFinisherImageFile(currentImageIndex++);
} }
@Override
protected boolean onHomeClicked() {
fadeActivity(LoginActivity.class, true);
return true;
}
private void setupListView() { private void setupListView() {
TextView header = new TextView(this); TextView header = new TextView(this);
header.setText(getString(R.string.results_capturing_view_list_header)); header.setText(getString(R.string.results_capturing_view_list_header));
@@ -181,7 +175,6 @@ public class ResultsCapturingActivity extends BaseActivity {
private void setupActionBar() { private void setupActionBar() {
ActionBar actionBar = getActionBar(); ActionBar actionBar = getActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(getString(R.string.results_capturing_title)); actionBar.setTitle(getString(R.string.results_capturing_title));
} }
@@ -0,0 +1,75 @@
package com.sap.sailing.racecommittee.app.ui.activities;
import com.sap.sailing.racecommittee.app.AppConstants;
import com.sap.sailing.racecommittee.app.R;
import com.sap.sailing.racecommittee.app.logging.ExLog;
import android.app.ActionBar;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
public class SessionActivity extends BaseActivity {
private static final String TAG = BaseActivity.class.getName();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActionBar actionBar = getActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
}
@Override
public void onResume() {
super.onResume();
ExLog.i(TAG, String.format("Logging in from activity %s", this.getClass().getSimpleName()));
}
@Override
protected boolean onHomeClicked() {
return logoutSession();
}
@Override
protected boolean onReset() {
return logoutSession();
}
protected boolean logoutSession() {
ExLog.i(TAG, String.format("Logging out from activity %s", this.getClass().getSimpleName()));
AlertDialog dialog = new AlertDialog.Builder(this)
.setTitle(getString(R.string.logout_dialog_title))
.setMessage(getString(R.string.logout_dialog_message))
.setPositiveButton(getString(R.string.logout), new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
doLogout();
}
})
.setNegativeButton(getString(R.string.cancel), new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
/* nothing here */
}
})
.create();
dialog.show();
return true;
}
private void doLogout() {
unloadAllRaces();
fadeActivity(LoginActivity.class, true);
}
private void unloadAllRaces() {
ExLog.i(TAG, "Issuing intent action clear races");
Intent intent = new Intent(AppConstants.INTENT_ACTION_CLEAR_RACES);
this.startService(intent);
}
}
@@ -3,6 +3,7 @@ package com.sap.sailing.racecommittee.app.ui.activities;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.preference.CheckBoxPreference; import android.preference.CheckBoxPreference;
import android.preference.ListPreference; import android.preference.ListPreference;
@@ -24,6 +25,11 @@ public class SettingsActivity extends PreferenceActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
addPreferencesFromResource(R.layout.settings_view); addPreferencesFromResource(R.layout.settings_view);
setupStartProcedurePreferences();
setupLanguageButton();
}
private void setupStartProcedurePreferences() {
final ListPreference startProcedurePreference = (ListPreference) findPreference("defaultStartProcedureType"); final ListPreference startProcedurePreference = (ListPreference) findPreference("defaultStartProcedureType");
List<CharSequence> entries = new ArrayList<CharSequence>(); List<CharSequence> entries = new ArrayList<CharSequence>();
@@ -47,8 +53,19 @@ public class SettingsActivity extends PreferenceActivity {
return true; return true;
} }
}); });
}
private void setupLanguageButton() {
Preference button = (Preference)findPreference("languagePref");
button.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference arg0) {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setClassName("com.android.settings", "com.android.settings.LanguageSettings");
startActivity(intent);
return true;
}
});
} }
} }
@@ -15,7 +15,7 @@ import com.sap.sailing.racecommittee.app.R;
public class SystemInformationActivity extends BaseActivity { public class SystemInformationActivity extends BaseActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.system_information_view); setContentView(R.layout.system_information_view);
setupVersionView(); setupVersionView();
@@ -20,6 +20,7 @@ import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.SeekBar; import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.Bearing;
@@ -37,9 +38,8 @@ import com.sap.sailing.racecommittee.app.R;
import com.sap.sailing.racecommittee.app.logging.ExLog; import com.sap.sailing.racecommittee.app.logging.ExLog;
import com.sap.sailing.racecommittee.app.ui.views.CompassView; import com.sap.sailing.racecommittee.app.ui.views.CompassView;
import com.sap.sailing.racecommittee.app.ui.views.CompassView.CompassDirectionListener; import com.sap.sailing.racecommittee.app.ui.views.CompassView.CompassDirectionListener;
import com.sap.sailing.racecommittee.app.utils.GeoUtils;
public class WindActivity extends BaseActivity implements CompassDirectionListener, LocationListener { public class WindActivity extends SessionActivity implements CompassDirectionListener, LocationListener {
private final static int FIVE_SEC = 5000; private final static int FIVE_SEC = 5000;
private final static int EVERY_POSITION_CHANGE = 0; private final static int EVERY_POSITION_CHANGE = 0;
@@ -47,10 +47,9 @@ public class WindActivity extends BaseActivity implements CompassDirectionListen
CompassView compassView; CompassView compassView;
EditText windBearingEditText; EditText windBearingEditText;
EditText windSpeedEditText; EditText windSpeedEditText;
EditText latitudeEditText;
EditText longitudeEditText;
SeekBar windSpeedSeekBar; SeekBar windSpeedSeekBar;
Button sendButton; Button sendButton;
TextView waitingForGpsTextView;
LocationManager locationManager; LocationManager locationManager;
Location currentLocation; Location currentLocation;
@@ -58,17 +57,16 @@ public class WindActivity extends BaseActivity implements CompassDirectionListen
DecimalFormat bearingFormat; DecimalFormat bearingFormat;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.wind_view); setContentView(R.layout.wind_view);
compassView = (CompassView) findViewById(R.id.compassView); compassView = (CompassView) findViewById(R.id.compassView);
windBearingEditText = (EditText) findViewById(R.id.editTextWindDirection); windBearingEditText = (EditText) findViewById(R.id.editTextWindDirection);
windSpeedEditText = (EditText) findViewById(R.id.editTextWindSpeed); windSpeedEditText = (EditText) findViewById(R.id.editTextWindSpeed);
latitudeEditText = (EditText) findViewById(R.id.et_position_lat);
longitudeEditText = (EditText) findViewById(R.id.et_position_lon);
windSpeedSeekBar = (SeekBar) findViewById(R.id.seekbar_wind_speed); windSpeedSeekBar = (SeekBar) findViewById(R.id.seekbar_wind_speed);
sendButton = (Button) findViewById(R.id.btn_wind_send); sendButton = (Button) findViewById(R.id.btn_wind_send);
waitingForGpsTextView = (TextView) findViewById(R.id.textWaitingForGPS);
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
if (!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) { if (!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
@@ -144,9 +142,9 @@ public class WindActivity extends BaseActivity implements CompassDirectionListen
double enteredWindSpeed = AppPreferences.getWindSpeed(this); double enteredWindSpeed = AppPreferences.getWindSpeed(this);
windSpeedSeekBar.setProgress(Double.valueOf(enteredWindSpeed).intValue() * 10); windSpeedSeekBar.setProgress(Double.valueOf(enteredWindSpeed).intValue() * 10);
windSpeedEditText.setText(speedFormat.format(enteredWindSpeed)); windSpeedEditText.setText(speedFormat.format(enteredWindSpeed));
double enteredWindBearing = AppPreferences.getWindBearing(this); double enteredWindBearingFrom = AppPreferences.getWindBearingFromDirection(this);
compassView.setDirection((float)enteredWindBearing); compassView.setDirection((float)enteredWindBearingFrom);
windBearingEditText.setText(bearingFormat.format(enteredWindBearing)); windBearingEditText.setText(bearingFormat.format(enteredWindBearingFrom));
} }
private void buildAlertMessageNoGps() { private void buildAlertMessageNoGps() {
@@ -169,12 +167,14 @@ public class WindActivity extends BaseActivity implements CompassDirectionListen
protected void saveEntriesInPreferences(Wind wind) { protected void saveEntriesInPreferences(Wind wind) {
AppPreferences.setWindBearing(getBaseContext(), wind.getBearing().getDegrees()); // Wind.getBearing() returns a value that assumes that the wind flows in that direction
// But for this app we need to display the direction the wind is coming from
AppPreferences.setWindBearingFromDirection(getBaseContext(), wind.getBearing().reverse().getDegrees());
AppPreferences.setWindSpeed(getBaseContext(), wind.getKnots()); AppPreferences.setWindSpeed(getBaseContext(), wind.getKnots());
} }
@Override @Override
protected void onStart() { public void onStart() {
super.onStart(); super.onStart();
compassView.setDirectionListener(this); compassView.setDirectionListener(this);
@@ -187,7 +187,7 @@ public class WindActivity extends BaseActivity implements CompassDirectionListen
} }
@Override @Override
protected void onPause() { public void onPause() {
locationManager.removeUpdates(this); locationManager.removeUpdates(this);
super.onPause(); super.onPause();
} }
@@ -195,8 +195,7 @@ public class WindActivity extends BaseActivity implements CompassDirectionListen
@Override @Override
public void onLocationChanged(Location location) { public void onLocationChanged(Location location) {
currentLocation = location; currentLocation = location;
latitudeEditText.setText(String.valueOf(GeoUtils.getDegMinSecFormatForDecimalDegree(location.getLatitude()))); waitingForGpsTextView.setText("");
longitudeEditText.setText(String.valueOf(GeoUtils.getDegMinSecFormatForDecimalDegree(location.getLongitude())));
sendButton.setEnabled(true); sendButton.setEnabled(true);
} }
@@ -227,9 +226,10 @@ public class WindActivity extends BaseActivity implements CompassDirectionListen
Position currentPosition = new DegreePosition(currentLocation.getLatitude(), currentLocation.getLongitude()); Position currentPosition = new DegreePosition(currentLocation.getLatitude(), currentLocation.getLongitude());
double windSpeed = Double.valueOf(windSpeedEditText.getText().toString().replace(",", ".")); double windSpeed = Double.valueOf(windSpeedEditText.getText().toString().replace(",", "."));
double windBearing = Double.valueOf(windBearingEditText.getText().toString()); double windBearing = Double.valueOf(windBearingEditText.getText().toString());
Bearing bearing = new DegreeBearingImpl(windBearing); Bearing bearing_from = new DegreeBearingImpl(windBearing);
SpeedWithBearing speedBearing = new KnotSpeedWithBearingImpl(windSpeed, bearing); // this is not a standard bearing but the direction where the wind comes from, needs to be converted
// to match the assumption that a bearing is always the direction the wind flows to
SpeedWithBearing speedBearing = new KnotSpeedWithBearingImpl(windSpeed, bearing_from.reverse());
return new WindImpl(currentPosition, MillisecondsTimePoint.now(), speedBearing); return new WindImpl(currentPosition, MillisecondsTimePoint.now(), speedBearing);
} }
@@ -0,0 +1,52 @@
package com.sap.sailing.racecommittee.app.ui.fragments;
import android.app.Fragment;
import android.os.Bundle;
import com.sap.sailing.racecommittee.app.logging.LifecycleLogger;
public abstract class LoggableFragment extends Fragment {
private LifecycleLogger lifeLogger;
public LoggableFragment() {
this.lifeLogger = new LifecycleLogger();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
lifeLogger.onCreate(this);
}
@Override
public void onStart() {
super.onStart();
lifeLogger.onStart(this);
}
@Override
public void onResume() {
super.onResume();
lifeLogger.onResume(this);
}
@Override
public void onPause() {
super.onPause();
lifeLogger.onPause(this);
}
@Override
public void onStop() {
super.onStop();
lifeLogger.onStop(this);
}
@Override
public void onDestroy() {
super.onDestroy();
lifeLogger.onDestroy(this);
}
}
@@ -2,16 +2,17 @@ package com.sap.sailing.racecommittee.app.ui.fragments;
import java.io.Serializable; import java.io.Serializable;
import android.os.Bundle;
import com.sap.sailing.racecommittee.app.AppConstants; import com.sap.sailing.racecommittee.app.AppConstants;
import com.sap.sailing.racecommittee.app.data.OnlineDataManager; import com.sap.sailing.racecommittee.app.data.OnlineDataManager;
import com.sap.sailing.racecommittee.app.domain.ManagedRace; import com.sap.sailing.racecommittee.app.domain.ManagedRace;
import com.sap.sailing.racecommittee.app.utils.TickListener; import com.sap.sailing.racecommittee.app.utils.TickListener;
import com.sap.sailing.racecommittee.app.utils.TickSingleton; import com.sap.sailing.racecommittee.app.utils.TickSingleton;
import android.app.Fragment; public abstract class RaceFragment extends LoggableFragment implements TickListener {
import android.os.Bundle;
public abstract class RaceFragment extends Fragment implements TickListener { //private static final String TAG = RaceFragment.class.getName();
public static Bundle createArguments(ManagedRace race) { public static Bundle createArguments(ManagedRace race) {
Bundle arguments = new Bundle(); Bundle arguments = new Bundle();
@@ -63,7 +64,7 @@ public abstract class RaceFragment extends Fragment implements TickListener {
@Override @Override
public void notifyTick() { public void notifyTick() {
// may be overriden in subclasses. // see subclasses.
} }
} }
@@ -218,7 +218,7 @@ public class RaceInfoFragment extends RaceFragment implements RaceStateChangedLi
LastWindFixFinder windFinder = new LastWindFixFinder(getRace().getState().getRaceLog()); LastWindFixFinder windFinder = new LastWindFixFinder(getRace().getState().getRaceLog());
if (windFinder.analyze()!=null){ if (windFinder.analyze()!=null){
Wind wind = windFinder.analyze(); Wind wind = windFinder.analyze();
windInfoHeader.setText(String.format(getString(R.string.wind_info), wind.getKnots(), wind.getBearing().toString())); windInfoHeader.setText(String.format(getString(R.string.wind_info), wind.getKnots(), wind.getBearing().reverse().toString()));
} }
} }
@@ -29,9 +29,9 @@ public class RaceInfoFragmentChooser {
private RaceFragment createInfoFragment(Class<? extends RaceFragment> fragmentClass, ManagedRace managedRace) { private RaceFragment createInfoFragment(Class<? extends RaceFragment> fragmentClass, ManagedRace managedRace) {
try { try {
ExLog.i(TAG, String.format("Fragment %s is chosen.", fragmentClass.getName()));
RaceFragment fragment = fragmentClass.newInstance(); RaceFragment fragment = fragmentClass.newInstance();
fragment.setArguments(RaceFragment.createArguments(managedRace)); fragment.setArguments(RaceFragment.createArguments(managedRace));
ExLog.i(TAG, String.format("Fragment %s may be choosen", fragmentClass.getName()));
return fragment; return fragment;
} catch (Exception e) { } catch (Exception e) {
ExLog.e(TAG, String.format("Exception while instantiating race info fragment:\n%s", e.toString())); ExLog.e(TAG, String.format("Exception while instantiating race info fragment:\n%s", e.toString()));
@@ -1,7 +1,7 @@
package com.sap.sailing.racecommittee.app.ui.fragments.dialogs; package com.sap.sailing.racecommittee.app.ui.fragments.dialogs;
public abstract class ActivityAttachedDialogFragment extends BaseDialogFragment { public abstract class ActivityAttachedDialogFragment extends AttachedDialogFragment {
protected DialogListenerHost hostActivity; protected DialogListenerHost hostActivity;
@@ -1,17 +1,16 @@
package com.sap.sailing.racecommittee.app.ui.fragments.dialogs; package com.sap.sailing.racecommittee.app.ui.fragments.dialogs;
import com.sap.sailing.racecommittee.app.logging.ExLog;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.AlertDialog.Builder; import android.app.AlertDialog.Builder;
import android.app.Dialog; import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnClickListener;
import android.os.Bundle; import android.os.Bundle;
public abstract class BaseDialogFragment extends DialogFragment { import com.sap.sailing.racecommittee.app.logging.ExLog;
private final static String TAG = BaseDialogFragment.class.getName();
public abstract class AttachedDialogFragment extends LoggableDialogFragment {
private final static String TAG = AttachedDialogFragment.class.getName();
protected abstract CharSequence getNegativeButtonLabel(); protected abstract CharSequence getNegativeButtonLabel();
@@ -1,15 +1,14 @@
package com.sap.sailing.racecommittee.app.ui.fragments.dialogs; package com.sap.sailing.racecommittee.app.ui.fragments.dialogs;
import com.sap.sailing.racecommittee.app.R;
import android.app.DatePickerDialog; import android.app.DatePickerDialog;
import android.app.Dialog; import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
import android.widget.DatePicker; import android.widget.DatePicker;
public class DatePickerFragment extends DialogFragment implements DatePickerDialog.OnDateSetListener, import com.sap.sailing.racecommittee.app.R;
public class DatePickerFragment extends LoggableDialogFragment implements DatePickerDialog.OnDateSetListener,
DatePickerDialog.OnDismissListener { DatePickerDialog.OnDismissListener {
private DatePickerDialog.OnDateSetListener listener; private DatePickerDialog.OnDateSetListener listener;
@@ -2,8 +2,8 @@ package com.sap.sailing.racecommittee.app.ui.fragments.dialogs;
public interface DialogListenerHost { public interface DialogListenerHost {
public interface DialogResultListener { public interface DialogResultListener {
void onDialogNegativeButton(BaseDialogFragment dialog); void onDialogNegativeButton(AttachedDialogFragment dialog);
void onDialogPositiveButton(BaseDialogFragment dialog); void onDialogPositiveButton(AttachedDialogFragment dialog);
} }
DialogResultListener getListener(); DialogResultListener getListener();
@@ -7,6 +7,7 @@ import java.util.List;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Loader;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -47,7 +48,9 @@ import com.sap.sailing.racecommittee.app.ui.comparators.NaturalNamedComparator;
import com.sap.sailing.racecommittee.app.utils.ESSMarkImageHelper; import com.sap.sailing.racecommittee.app.utils.ESSMarkImageHelper;
public class ESSCourseDesignDialogFragment extends RaceDialogFragment { public class ESSCourseDesignDialogFragment extends RaceDialogFragment {
//private final static String TAG = ESSCourseDesignDialogFragment.class.getName(); // private final static String TAG = ESSCourseDesignDialogFragment.class.getName();
private static int MarksLoaderId = 0;
private List<Mark> aMarkList; private List<Mark> aMarkList;
private MarkGridAdapter gridAdapter; private MarkGridAdapter gridAdapter;
@@ -89,11 +92,13 @@ public class ESSCourseDesignDialogFragment extends RaceDialogFragment {
previousCourseElements = new ArrayList<CourseListDataElement>(); previousCourseElements = new ArrayList<CourseListDataElement>();
gridAdapter = new MarkGridAdapter(getActivity(), aMarkList, ESSMarkImageHelper.getInstance()); gridAdapter = new MarkGridAdapter(getActivity(), aMarkList, ESSMarkImageHelper.getInstance());
courseElementAdapter = new DraggableCourseElementListAdapter(getActivity(), courseElements, ESSMarkImageHelper.getInstance()); courseElementAdapter = new DraggableCourseElementListAdapter(getActivity(), courseElements,
previousCourseElementAdapter = new CourseElementListAdapter(getActivity(), previousCourseElements, ESSMarkImageHelper.getInstance()); ESSMarkImageHelper.getInstance());
previousCourseElementAdapter = new CourseElementListAdapter(getActivity(), previousCourseElements,
ESSMarkImageHelper.getInstance());
loadMarks(); loadMarks();
loadCourseOnServer(); fillPreviousCourseElementsWithLastPublishedCourseDesign();
GridView gridView = (GridView) getView().findViewById(R.id.gridViewAssets); GridView gridView = (GridView) getView().findViewById(R.id.gridViewAssets);
gridView.setAdapter(gridAdapter); gridView.setAdapter(gridAdapter);
@@ -163,14 +168,12 @@ public class ESSCourseDesignDialogFragment extends RaceDialogFragment {
} else { } else {
copyPreviousToNewCourseDesign(); copyPreviousToNewCourseDesign();
} }
} } else {
else {
Toast.makeText(getActivity(), "No course available to copy", Toast.LENGTH_LONG).show(); Toast.makeText(getActivity(), "No course available to copy", Toast.LENGTH_LONG).show();
} }
} }
}); });
publishButton = (Button) getView().findViewById(R.id.publishCourseDesignButton); publishButton = (Button) getView().findViewById(R.id.publishCourseDesignButton);
publishButton.setOnClickListener(new OnClickListener() { publishButton.setOnClickListener(new OnClickListener() {
@@ -180,13 +183,19 @@ public class ESSCourseDesignDialogFragment extends RaceDialogFragment {
sendCourseDataAndDismiss(courseData); sendCourseDataAndDismiss(courseData);
} catch (IllegalStateException ex) { } catch (IllegalStateException ex) {
if (ex.getMessage().equals("Gate has no right buoy")) { if (ex.getMessage().equals("Gate has no right buoy")) {
Toast.makeText(getActivity(), "A right buoy is missing for a gate. Please select the right buoy.", Toast.LENGTH_LONG).show(); Toast.makeText(getActivity(),
"A right buoy is missing for a gate. Please select the right buoy.", Toast.LENGTH_LONG)
.show();
} else if (ex.getMessage().equals("Each waypoints needs a passing side")) { } else if (ex.getMessage().equals("Each waypoints needs a passing side")) {
Toast.makeText(getActivity(), "A waypoint has no passing side. Please select the passing side by clicking long on the waypoint.", Toast.LENGTH_LONG).show(); Toast.makeText(
getActivity(),
"A waypoint has no passing side. Please select the passing side by clicking long on the waypoint.",
Toast.LENGTH_LONG).show();
} }
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
Toast.makeText(getActivity(), "The course design has to have at least one waypoint.", Toast.LENGTH_LONG).show(); Toast.makeText(getActivity(), "The course design has to have at least one waypoint.",
Toast.LENGTH_LONG).show();
} }
} }
@@ -194,19 +203,16 @@ public class ESSCourseDesignDialogFragment extends RaceDialogFragment {
unpublishButton = (Button) getView().findViewById(R.id.unpublishCourseDesignButton); unpublishButton = (Button) getView().findViewById(R.id.unpublishCourseDesignButton);
unpublishButton.setOnClickListener(new OnClickListener() { unpublishButton.setOnClickListener(new OnClickListener() {
@Override @Override
public void onClick(View arg0) { public void onClick(View arg0) {
CourseBase emptyCourse = new CourseDataImpl("Unpublished course design"); CourseBase emptyCourse = new CourseDataImpl("Unpublished course design");
sendCourseDataAndDismiss(emptyCourse); sendCourseDataAndDismiss(emptyCourse);
} }
}); });
} }
/** /**
* Creates a DragSortController and thereby defines the behaviour of the drag sort list * Creates a DragSortController and thereby defines the behavior of the drag sort list
*/ */
public DragSortController buildDragSortController(DragSortListView dragSortListView) { public DragSortController buildDragSortController(DragSortListView dragSortListView) {
DragSortController controller = new DragSortController(dragSortListView); DragSortController controller = new DragSortController(dragSortListView);
@@ -220,53 +226,29 @@ public class ESSCourseDesignDialogFragment extends RaceDialogFragment {
return controller; return controller;
} }
@Override
public void onResume() {
super.onResume();
onLoadMarksSucceeded(dataManager.getDataStore().getMarks());
}
private void loadCourseOnServer() {
dataManager.loadCourse(getRace(), new LoadClient<CourseBase>() {
@Override
public void onLoadFailed(Exception reason) {
fillPreviousCourseElementsWithLastPublishedCourseDesign();
}
@Override
public void onLoadSucceded(CourseBase data) {
/*if (Util.size(data.getWaypoints()) > 0) {
fillPreviousCourseElementsInList(data);
} else*/ {
fillPreviousCourseElementsWithLastPublishedCourseDesign();
}
}
});
}
private void fillPreviousCourseElementsWithLastPublishedCourseDesign() { private void fillPreviousCourseElementsWithLastPublishedCourseDesign() {
CourseBase lastPublishedCourseDesign = InMemoryDataStore.INSTANCE.getLastPublishedCourseDesign(); CourseBase lastPublishedCourseDesign = InMemoryDataStore.INSTANCE.getLastPublishedCourseDesign();
if(lastPublishedCourseDesign != null) { if (lastPublishedCourseDesign != null) {
fillPreviousCourseElementsInList(lastPublishedCourseDesign); fillPreviousCourseElementsInList(lastPublishedCourseDesign);
} }
} }
private void loadMarks() { private void loadMarks() {
dataManager.loadMarks(getRace(), new LoadClient<Collection<Mark>>() { Loader<?> marksLoader = getLoaderManager().restartLoader(MarksLoaderId, null,
dataManager.createMarksLoader(getRace(), new LoadClient<Collection<Mark>>() {
@Override @Override
public void onLoadFailed(Exception reason) { public void onLoadFailed(Exception reason) {
Toast.makeText(getActivity(), String.format("Marks: %s",reason.toString()), Toast.LENGTH_LONG).show();
} }
@Override @Override
public void onLoadSucceded(Collection<Mark> data) { public void onLoadSucceded(Collection<Mark> data, boolean isCached) {
onLoadMarksSucceeded(data); onLoadMarksSucceeded(data);
} }
}));
}); // ignore any cached results and force a remote load of the marks,
// because we always need the most current.
marksLoader.forceLoad();
} }
protected void onLoadMarksSucceeded(Collection<Mark> data) { protected void onLoadMarksSucceeded(Collection<Mark> data) {
@@ -298,7 +280,7 @@ public class ESSCourseDesignDialogFragment extends RaceDialogFragment {
if (controlPoint instanceof Mark) { if (controlPoint instanceof Mark) {
CourseListDataElement element = new CourseListDataElement(); CourseListDataElement element = new CourseListDataElement();
element.setLeftMark((Mark)controlPoint); element.setLeftMark((Mark) controlPoint);
element.setRoundingDirection(getRoundingDirection(waypoint.getPassingSide())); element.setRoundingDirection(getRoundingDirection(waypoint.getPassingSide()));
elementList.add(element); elementList.add(element);
} else if (controlPoint instanceof Gate) { } else if (controlPoint instanceof Gate) {
@@ -327,23 +309,27 @@ public class ESSCourseDesignDialogFragment extends RaceDialogFragment {
} }
protected CourseBase convertCourseElementsToACourseData() throws IllegalStateException, IllegalArgumentException { protected CourseBase convertCourseElementsToACourseData() throws IllegalStateException, IllegalArgumentException {
//TODO find a proper name for the highly flexible ESS courses to be shown on the regatta overview page // TODO find a proper name for the highly flexible ESS courses to be shown on the regatta overview page
CourseBase design = new CourseDataImpl("Course template"); CourseBase design = new CourseDataImpl("Course");
List<Waypoint> waypoints = new ArrayList<Waypoint>(); List<Waypoint> waypoints = new ArrayList<Waypoint>();
for (CourseListDataElement courseElement : courseElements) { for (CourseListDataElement courseElement : courseElements) {
if (courseElement.getRoundingDirection().equals(RoundingDirection.Gate) && courseElement.getRightMark() != null) { if (courseElement.getRoundingDirection().equals(RoundingDirection.Gate)
String gateName = "Gate " + courseElement.getLeftMark().getName() + " / " + courseElement.getRightMark().getName(); && courseElement.getRightMark() != null) {
String gateName = "Gate " + courseElement.getLeftMark().getName() + " / "
+ courseElement.getRightMark().getName();
Gate gate = new GateImpl(courseElement.getLeftMark(), courseElement.getRightMark(), gateName); Gate gate = new GateImpl(courseElement.getLeftMark(), courseElement.getRightMark(), gateName);
Waypoint waypoint = new WaypointImpl(gate); Waypoint waypoint = new WaypointImpl(gate);
waypoints.add(waypoint); waypoints.add(waypoint);
} else if (courseElement.getRoundingDirection().equals(RoundingDirection.Gate) && courseElement.getRightMark() == null) { } else if (courseElement.getRoundingDirection().equals(RoundingDirection.Gate)
&& courseElement.getRightMark() == null) {
throw new IllegalStateException("Gate has no right buoy"); throw new IllegalStateException("Gate has no right buoy");
} else if (courseElement.getRoundingDirection().equals(RoundingDirection.None)) { } else if (courseElement.getRoundingDirection().equals(RoundingDirection.None)) {
throw new IllegalStateException("Each waypoints needs a passing side"); throw new IllegalStateException("Each waypoints needs a passing side");
} else { } else {
Waypoint waypoint = new WaypointImpl(courseElement.getLeftMark(), getNauticalSide(courseElement.getRoundingDirection())); Waypoint waypoint = new WaypointImpl(courseElement.getLeftMark(),
getNauticalSide(courseElement.getRoundingDirection()));
waypoints.add(waypoint); waypoints.add(waypoint);
} }
@@ -365,7 +351,7 @@ public class ESSCourseDesignDialogFragment extends RaceDialogFragment {
NauticalSide side = null; NauticalSide side = null;
if (roundingDirection.name().equalsIgnoreCase(NauticalSide.PORT.name())) { if (roundingDirection.name().equalsIgnoreCase(NauticalSide.PORT.name())) {
side = NauticalSide.PORT; side = NauticalSide.PORT;
} else if (roundingDirection.name().equalsIgnoreCase(NauticalSide.STARBOARD.name())){ } else if (roundingDirection.name().equalsIgnoreCase(NauticalSide.STARBOARD.name())) {
side = NauticalSide.STARBOARD; side = NauticalSide.STARBOARD;
} }
return side; return side;
@@ -377,7 +363,7 @@ public class ESSCourseDesignDialogFragment extends RaceDialogFragment {
direction = RoundingDirection.None; direction = RoundingDirection.None;
} else if (passingSide.name().equalsIgnoreCase(RoundingDirection.Port.name())) { } else if (passingSide.name().equalsIgnoreCase(RoundingDirection.Port.name())) {
direction = RoundingDirection.Port; direction = RoundingDirection.Port;
} else if (passingSide.name().equalsIgnoreCase(RoundingDirection.Starboard.name())){ } else if (passingSide.name().equalsIgnoreCase(RoundingDirection.Starboard.name())) {
direction = RoundingDirection.Starboard; direction = RoundingDirection.Starboard;
} else { } else {
direction = RoundingDirection.Gate; direction = RoundingDirection.Gate;
@@ -400,7 +386,7 @@ public class ESSCourseDesignDialogFragment extends RaceDialogFragment {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
//do nothing // do nothing
} }
}); });
builder.create(); builder.create();
@@ -411,7 +397,7 @@ public class ESSCourseDesignDialogFragment extends RaceDialogFragment {
courseElements.clear(); courseElements.clear();
courseElements.addAll(previousCourseElements); courseElements.addAll(previousCourseElements);
courseElementAdapter.notifyDataSetChanged(); courseElementAdapter.notifyDataSetChanged();
} }
protected void onMarkClickedOnGrid(Mark mark) { protected void onMarkClickedOnGrid(Mark mark) {
if (courseElements.isEmpty()) { if (courseElements.isEmpty()) {
@@ -429,7 +415,8 @@ public class ESSCourseDesignDialogFragment extends RaceDialogFragment {
private CourseListDataElement getFirstGateCourseElementWithoutRightMark() { private CourseListDataElement getFirstGateCourseElementWithoutRightMark() {
for (CourseListDataElement courseElement : courseElements) { for (CourseListDataElement courseElement : courseElements) {
if (courseElement.getRoundingDirection().equals(RoundingDirection.Gate) && courseElement.getRightMark() == null) { if (courseElement.getRoundingDirection().equals(RoundingDirection.Gate)
&& courseElement.getRightMark() == null) {
return courseElement; return courseElement;
} }
} }
@@ -448,11 +435,9 @@ public class ESSCourseDesignDialogFragment extends RaceDialogFragment {
directions.add(direction.name()); directions.add(direction.name());
} }
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.pick_a_rounding_direction) builder.setTitle(R.string.pick_a_rounding_direction).setItems(R.array.rounding_directions,
.setItems(R.array.rounding_directions, new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int position) { public void onClick(DialogInterface dialog, int position) {
// for i18n we expect following order in array: 1. Port, 2. Starboard, 3. Gate
//String[] directions = getActivity().getResources().getStringArray(R.array.rounding_directions);
RoundingDirection pickedDirection = RoundingDirection.relevantValues()[position]; RoundingDirection pickedDirection = RoundingDirection.relevantValues()[position];
onRoundingDirectionPicked(courseElement, pickedDirection); onRoundingDirectionPicked(courseElement, pickedDirection);
} }
@@ -2,7 +2,7 @@ package com.sap.sailing.racecommittee.app.ui.fragments.dialogs;
import android.app.Fragment; import android.app.Fragment;
public abstract class FragmentAttachedDialogFragment extends BaseDialogFragment { public abstract class FragmentAttachedDialogFragment extends AttachedDialogFragment {
@Override @Override
protected DialogListenerHost getHost() { protected DialogListenerHost getHost() {
@@ -0,0 +1,55 @@
package com.sap.sailing.racecommittee.app.ui.fragments.dialogs;
import android.app.DialogFragment;
import android.os.Bundle;
import com.sap.sailing.racecommittee.app.logging.LifecycleLogger;
/**
* Simple base class for {@link DialogFragment}'s to add some logging.
*/
public abstract class LoggableDialogFragment extends DialogFragment {
private LifecycleLogger lifeLogger;
public LoggableDialogFragment() {
this.lifeLogger = new LifecycleLogger();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
lifeLogger.onCreate(this);
}
@Override
public void onStart() {
super.onStart();
lifeLogger.onStart(this);
}
@Override
public void onResume() {
super.onResume();
lifeLogger.onResume(this);
}
@Override
public void onPause() {
super.onPause();
lifeLogger.onPause(this);
}
@Override
public void onStop() {
super.onStop();
lifeLogger.onStop(this);
}
@Override
public void onDestroy() {
super.onDestroy();
lifeLogger.onDestroy(this);
}
}
@@ -9,6 +9,7 @@ import java.util.List;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Loader;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@@ -19,6 +20,7 @@ import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener; import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.Button; import android.widget.Button;
import android.widget.ListView; import android.widget.ListView;
import android.widget.Toast;
import com.mobeta.android.dslv.DragSortController; import com.mobeta.android.dslv.DragSortController;
import com.mobeta.android.dslv.DragSortListView; import com.mobeta.android.dslv.DragSortListView;
@@ -29,6 +31,7 @@ import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.common.impl.Util.Triple;
import com.sap.sailing.racecommittee.app.R; import com.sap.sailing.racecommittee.app.R;
import com.sap.sailing.racecommittee.app.data.OnlineDataManager; import com.sap.sailing.racecommittee.app.data.OnlineDataManager;
import com.sap.sailing.racecommittee.app.data.ReadonlyDataManager;
import com.sap.sailing.racecommittee.app.data.clients.LoadClient; import com.sap.sailing.racecommittee.app.data.clients.LoadClient;
import com.sap.sailing.racecommittee.app.domain.impl.DomainFactoryImpl; import com.sap.sailing.racecommittee.app.domain.impl.DomainFactoryImpl;
import com.sap.sailing.racecommittee.app.logging.ExLog; import com.sap.sailing.racecommittee.app.logging.ExLog;
@@ -93,7 +96,8 @@ public class PositioningFragment extends RaceDialogFragment {
positionedCompetitors = initializeFinishPositioningList(); positionedCompetitors = initializeFinishPositioningList();
deletePositionedCompetitorsFromUnpositionedList(); deletePositionedCompetitorsFromUnpositionedList();
positioningAdapter = new CompetitorPositioningListAdapter(getActivity(), R.layout.welter_positioning_item, positionedCompetitors); positioningAdapter = new CompetitorPositioningListAdapter(getActivity(), R.layout.welter_positioning_item,
positionedCompetitors);
positioningListView = (DragSortListView) getView().findViewById(R.id.dragSortListPositioning); positioningListView = (DragSortListView) getView().findViewById(R.id.dragSortListPositioning);
positioningListView.setAdapter(positioningAdapter); positioningListView.setAdapter(positioningAdapter);
@@ -139,18 +143,13 @@ public class PositioningFragment extends RaceDialogFragment {
public void onItemClick(AdapterView<?> arg0, View view, int position, long arg3) { public void onItemClick(AdapterView<?> arg0, View view, int position, long arg3) {
Competitor competitor = (Competitor) competitorsAdapter.getItem(position); Competitor competitor = (Competitor) competitorsAdapter.getItem(position);
ExLog.i("RaceFinishingFragment", "Grid is clicked at position " + position + " for competitor " + competitor.getName()); ExLog.i("RaceFinishingFragment", "Grid is clicked at position " + position + " for competitor "
+ competitor.getName());
onCompetitorClickedOnGrid(competitor); onCompetitorClickedOnGrid(competitor);
} }
}); });
} }
@Override
public void onResume() {
super.onResume();
onLoadCompetitorsSucceeded(getRace().getCompetitors());
}
/** /**
* Creates a DragSortController and thereby defines the behaviour of the drag sort list * Creates a DragSortController and thereby defines the behaviour of the drag sort list
*/ */
@@ -167,30 +166,50 @@ public class PositioningFragment extends RaceDialogFragment {
} }
private void loadCompetitors() { private void loadCompetitors() {
getActivity().setProgressBarIndeterminateVisibility(true);
OnlineDataManager.create(getActivity()).loadCompetitors(getRace(), new LoadClient<Collection<Competitor>>() { ReadonlyDataManager dataManager = OnlineDataManager.create(getActivity());
Loader<?> competitorLoaders = getLoaderManager().initLoader(0, null,
dataManager.createCompetitorsLoader(getRace(), new LoadClient<Collection<Competitor>>() {
@Override @Override
public void onLoadFailed(Exception reason) { public void onLoadFailed(Exception reason) {
onLoadCompetitorsSucceeded(getRace().getCompetitors()); getActivity().setProgressBarIndeterminateVisibility(false);
Toast.makeText(getActivity(), String.format("Competitors: %s", reason.toString()),
Toast.LENGTH_LONG).show();
} }
@Override @Override
public void onLoadSucceded(Collection<Competitor> data) { public void onLoadSucceded(Collection<Competitor> data, boolean isCached) {
if (!isCached) {
getActivity().setProgressBarIndeterminateVisibility(false);
}
onLoadCompetitorsSucceeded(data); onLoadCompetitorsSucceeded(data);
} }
}); }));
// Force load to get non-cached remote competitors...
competitorLoaders.forceLoad();
} }
protected void onLoadCompetitorsSucceeded(Collection<Competitor> data) { protected void onLoadCompetitorsSucceeded(Collection<Competitor> data) {
competitors.clear(); competitors.clear();
competitors.addAll(data); competitors.addAll(data);
Collections.sort(competitors, competitorComparator); Collections.sort(competitors, competitorComparator);
deleteObsoleteCompetitorsFromPositionedList(data);
deletePositionedCompetitorsFromUnpositionedList(); deletePositionedCompetitorsFromUnpositionedList();
competitorsAdapter.notifyDataSetChanged(); competitorsAdapter.notifyDataSetChanged();
} }
private void deleteObsoleteCompetitorsFromPositionedList(Collection<Competitor> validCompetitors) {
List<Triple<Serializable, String, MaxPointsReason>> toBeDeleted = new ArrayList<Util.Triple<Serializable, String, MaxPointsReason>>();
for (Triple<Serializable, String, MaxPointsReason> positionedItem : positionedCompetitors) {
if (!validCompetitors.contains(DomainFactoryImpl.INSTANCE.getExistingCompetitorById(positionedItem.getA()))) {
toBeDeleted.add(positionedItem);
}
}
positionedCompetitors.removeAll(toBeDeleted);
}
private void deletePositionedCompetitorsFromUnpositionedList() { private void deletePositionedCompetitorsFromUnpositionedList() {
for (Triple<Serializable, String, MaxPointsReason> positionedItem : positionedCompetitors) { for (Triple<Serializable, String, MaxPointsReason> positionedItem : positionedCompetitors) {
Competitor competitor = DomainFactoryImpl.INSTANCE.getExistingCompetitorById(positionedItem.getA()); Competitor competitor = DomainFactoryImpl.INSTANCE.getExistingCompetitorById(positionedItem.getA());
@@ -216,8 +235,8 @@ public class PositioningFragment extends RaceDialogFragment {
private void createMaxPointsReasonSelectionDialog(final Triple<Serializable, String, MaxPointsReason> item) { private void createMaxPointsReasonSelectionDialog(final Triple<Serializable, String, MaxPointsReason> item) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
final CharSequence[] maxPointsReasons = getAllMaxPointsReasons(); final CharSequence[] maxPointsReasons = getAllMaxPointsReasons();
builder.setTitle(R.string.select_penalty_reason) builder.setTitle(R.string.select_penalty_reason).setItems(maxPointsReasons,
.setItems(maxPointsReasons, new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int position) { public void onClick(DialogInterface dialog, int position) {
setMaxPointsReasonForItem(item, maxPointsReasons[position]); setMaxPointsReasonForItem(item, maxPointsReasons[position]);
} }
@@ -234,9 +253,11 @@ public class PositioningFragment extends RaceDialogFragment {
return result.toArray(new CharSequence[result.size()]); return result.toArray(new CharSequence[result.size()]);
} }
protected void setMaxPointsReasonForItem(Triple<Serializable, String, MaxPointsReason> item, CharSequence maxPointsReasonName) { protected void setMaxPointsReasonForItem(Triple<Serializable, String, MaxPointsReason> item,
CharSequence maxPointsReasonName) {
MaxPointsReason maxPointsReason = MaxPointsReason.valueOf(maxPointsReasonName.toString()); MaxPointsReason maxPointsReason = MaxPointsReason.valueOf(maxPointsReasonName.toString());
Triple<Serializable, String, MaxPointsReason> newItem = new Triple<Serializable, String, MaxPointsReason>(item.getA(), item.getB(), maxPointsReason); Triple<Serializable, String, MaxPointsReason> newItem = new Triple<Serializable, String, MaxPointsReason>(
item.getA(), item.getB(), maxPointsReason);
int currentIndexOfItem = positionedCompetitors.indexOf(item); int currentIndexOfItem = positionedCompetitors.indexOf(item);
replaceItemInPositioningList(currentIndexOfItem, item, newItem); replaceItemInPositioningList(currentIndexOfItem, item, newItem);
@@ -246,7 +267,8 @@ public class PositioningFragment extends RaceDialogFragment {
getRace().getState().setFinishPositioningListChanged(positionedCompetitors); getRace().getState().setFinishPositioningListChanged(positionedCompetitors);
} }
private void replaceItemInPositioningList(int currentIndexOfItem, Triple<Serializable, String, MaxPointsReason> item, Triple<Serializable, String, MaxPointsReason> newItem) { private void replaceItemInPositioningList(int currentIndexOfItem,
Triple<Serializable, String, MaxPointsReason> item, Triple<Serializable, String, MaxPointsReason> newItem) {
positioningAdapter.remove(item); positioningAdapter.remove(item);
positioningAdapter.insert(newItem, currentIndexOfItem); positioningAdapter.insert(newItem, currentIndexOfItem);
} }
@@ -271,7 +293,8 @@ public class PositioningFragment extends RaceDialogFragment {
private void addNewCompetitorToPositioningList(Competitor competitor) { private void addNewCompetitorToPositioningList(Competitor competitor) {
positionedCompetitors.add(new Triple<Serializable, String, MaxPointsReason>(competitor.getId(), competitor.getName(), MaxPointsReason.NONE)); positionedCompetitors.add(new Triple<Serializable, String, MaxPointsReason>(competitor.getId(), competitor
.getName(), MaxPointsReason.NONE));
positioningAdapter.notifyDataSetChanged(); positioningAdapter.notifyDataSetChanged();
} }
@@ -28,7 +28,7 @@ import com.sap.sailing.racecommittee.app.data.ReadonlyDataManager;
import com.sap.sailing.racecommittee.app.domain.ManagedRace; import com.sap.sailing.racecommittee.app.domain.ManagedRace;
import com.sap.sailing.racecommittee.app.domain.impl.BoatClassSeriesFleet; import com.sap.sailing.racecommittee.app.domain.impl.BoatClassSeriesFleet;
public class ProtestTimeDialogFragment extends BaseDialogFragment { public class ProtestTimeDialogFragment extends AttachedDialogFragment {
private static String ARGS_RACE_IDS = ProtestTimeDialogFragment.class.getSimpleName() + ".raceids"; private static String ARGS_RACE_IDS = ProtestTimeDialogFragment.class.getSimpleName() + ".raceids";
@@ -84,12 +84,12 @@ public class ProtestTimeDialogFragment extends BaseDialogFragment {
public DialogResultListener getListener() { public DialogResultListener getListener() {
return new DialogResultListener() { return new DialogResultListener() {
@Override @Override
public void onDialogNegativeButton(BaseDialogFragment dialog) { public void onDialogNegativeButton(AttachedDialogFragment dialog) {
// no operation // no operation
} }
@Override @Override
public void onDialogPositiveButton(BaseDialogFragment dialog) { public void onDialogPositiveButton(AttachedDialogFragment dialog) {
setAndAnnounceProtestTime(); setAndAnnounceProtestTime();
} }
}; };
@@ -40,7 +40,7 @@ public class RaceChooseCourseByLabelDialog extends RaceDialogFragment {
holder.addView(rowLayout); holder.addView(rowLayout);
for (int round = minNumberOfRounds; round <= maxNumberOfRounds; round++) { for (int round = minNumberOfRounds; round <= maxNumberOfRounds; round++) {
Button courseButton = new Button(getActivity()); Button courseButton = new Button(getActivity());
courseButton.setText(courseNumberAndName[0]+" "+round); courseButton.setText(courseNumberAndName[0] + " " + round);
courseButton.setOnClickListener( new OnClickListener() { courseButton.setOnClickListener( new OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@@ -56,8 +56,6 @@ public class RaceChooseCourseByLabelDialog extends RaceDialogFragment {
return holder; return holder;
} }
@Override @Override
public void onActivityCreated(Bundle savedInstanceState) { public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
@@ -75,9 +73,4 @@ public class RaceChooseCourseByLabelDialog extends RaceDialogFragment {
dismiss(); dismiss();
} }
@Override
public void notifyTick() {
}
} }
@@ -2,16 +2,15 @@ package com.sap.sailing.racecommittee.app.ui.fragments.dialogs;
import java.io.Serializable; import java.io.Serializable;
import android.os.Bundle;
import com.sap.sailing.racecommittee.app.AppConstants; import com.sap.sailing.racecommittee.app.AppConstants;
import com.sap.sailing.racecommittee.app.data.OnlineDataManager; import com.sap.sailing.racecommittee.app.data.OnlineDataManager;
import com.sap.sailing.racecommittee.app.domain.ManagedRace; import com.sap.sailing.racecommittee.app.domain.ManagedRace;
import com.sap.sailing.racecommittee.app.utils.TickListener; import com.sap.sailing.racecommittee.app.utils.TickListener;
import com.sap.sailing.racecommittee.app.utils.TickSingleton; import com.sap.sailing.racecommittee.app.utils.TickSingleton;
import android.app.DialogFragment; public abstract class RaceDialogFragment extends LoggableDialogFragment implements TickListener {
import android.os.Bundle;
public abstract class RaceDialogFragment extends DialogFragment implements TickListener {
public static Bundle createArguments(ManagedRace race) { public static Bundle createArguments(ManagedRace race) {
Bundle arguments = new Bundle(); Bundle arguments = new Bundle();
@@ -2,8 +2,10 @@ package com.sap.sailing.racecommittee.app.ui.fragments.lists;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import android.app.Activity; import android.app.Activity;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
@@ -11,6 +13,7 @@ import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.racecommittee.app.AppConstants; import com.sap.sailing.racecommittee.app.AppConstants;
import com.sap.sailing.racecommittee.app.R; import com.sap.sailing.racecommittee.app.R;
import com.sap.sailing.racecommittee.app.data.ReadonlyDataManager; import com.sap.sailing.racecommittee.app.data.ReadonlyDataManager;
import com.sap.sailing.racecommittee.app.data.loaders.DataLoaderResult;
import com.sap.sailing.racecommittee.app.ui.adapters.CourseAreaArrayAdapter; import com.sap.sailing.racecommittee.app.ui.adapters.CourseAreaArrayAdapter;
import com.sap.sailing.racecommittee.app.ui.adapters.NamedArrayAdapter; import com.sap.sailing.racecommittee.app.ui.adapters.NamedArrayAdapter;
import com.sap.sailing.racecommittee.app.ui.fragments.lists.selection.CourseAreaSelectedListenerHost; import com.sap.sailing.racecommittee.app.ui.fragments.lists.selection.CourseAreaSelectedListenerHost;
@@ -27,12 +30,6 @@ public class CourseAreaListFragment extends NamedListFragment<CourseArea> {
this.parentEventId = getArguments().getSerializable(AppConstants.EventIdTag); this.parentEventId = getArguments().getSerializable(AppConstants.EventIdTag);
} }
@Override
public void onResume() {
super.onResume();
loadItems();
}
@Override @Override
protected NamedArrayAdapter<CourseArea> createAdapter(Context context, ArrayList<CourseArea> items) { protected NamedArrayAdapter<CourseArea> createAdapter(Context context, ArrayList<CourseArea> items) {
return new CourseAreaArrayAdapter(context, items); return new CourseAreaArrayAdapter(context, items);
@@ -55,8 +52,8 @@ public class CourseAreaListFragment extends NamedListFragment<CourseArea> {
} }
@Override @Override
protected void loadItems(ReadonlyDataManager manager) { protected LoaderCallbacks<DataLoaderResult<Collection<CourseArea>>> createLoaderCallbacks(ReadonlyDataManager manager) {
manager.loadCourseAreas(parentEventId, this); return manager.createCourseAreasLoader(parentEventId, this);
} }
} }
Loaded 100 of 108 files, more files were not shown because too many files have changed in this diff. Show more