From 2a4d72c84884397e5ec0a9c702db74f0b8d18cce Mon Sep 17 00:00:00 2001 From: Joshua Sigona Date: Fri, 31 Jul 2020 02:44:43 +0900 Subject: [PATCH] Completely implement all basic requirements for application --- family-tracker/src/App.css | 45 +++++ family-tracker/src/App.js | 177 +++++++++++++++++- src/main/java/sig/family/Endpoints.java | 46 +++-- src/main/java/sig/family/FamilyApp.java | 166 ++++++++++++++++ src/main/java/sig/family/FamilyContainer.java | 5 +- .../sig/family/FamilyMemberContainer.java | 46 ++++- src/main/java/sig/family/GPSUser.java | 24 +++ src/main/java/sig/family/Location.java | 21 ++- .../java/sig/family/LocationRepository.java | 4 +- .../sig/family/NotificationRepository.java | 5 +- 10 files changed, 512 insertions(+), 27 deletions(-) create mode 100644 src/main/java/sig/family/GPSUser.java diff --git a/family-tracker/src/App.css b/family-tracker/src/App.css index 664b79b..ebb104a 100644 --- a/family-tracker/src/App.css +++ b/family-tracker/src/App.css @@ -22,6 +22,51 @@ } } +.fadein { + animation-name: fadein; + animation-duration: 4s; +} +.badfadein { + animation-name: badfadein; + animation-duration: 4s; + background-color:#faa; +} +.badchildfadein { + animation-name: badchildfadein; + animation-duration: 4s; + background-color:#fae; +} + +.tinytime{ + font-size:12px; + position:absolute; + bottom:4px; + right:7px; + /*z-index:-10000;*/ + color:#666; + font-style:italic; + /*background-color:rgba(240,240,255,0.8);*/ +} + +@keyframes fadein { + 0% {opacity:0; + background-color:#6ef;} + 100% {opacity:1; + background-color:#fff;} +} +@keyframes badfadein { + 0% {opacity:0; + background-color:#f00;} + 100% {opacity:1; + background-color:#faa;} +} +@keyframes badchildfadein { + 0% {opacity:0; + background-color:#f0a;} + 100% {opacity:1; + background-color:#fae;} +} + .App-header { background-color: #282c34; min-height: 100vh; diff --git a/family-tracker/src/App.js b/family-tracker/src/App.js index 055c105..b24787a 100644 --- a/family-tracker/src/App.js +++ b/family-tracker/src/App.js @@ -12,20 +12,183 @@ import { const axios = require('axios'); +var mapX=-110.267 +var mapY=31.544 +var mapXScale=0.00005 +var mapYScale=0.00005 +var ctx=undefined,locations=[],mousePos={x:0,y:0},members=[],requiresUpdate=false; +function getMousePos(canvas, evt) { + var rect = canvas.getBoundingClientRect(); + return { + x: evt.clientX - rect.left, + y: evt.clientY - rect.top + }; +} +setInterval(()=>{ + if (document.getElementById("map")!==null) { + var c = document.getElementById("map"); + //console.log("Update map.") + requiresUpdate=!requiresUpdate; + if (ctx===undefined||ctx===null) { + ctx = c.getContext("2d"); + //Draw all known locations. + axios.get("http://localhost:8080/knownlocation") + .then((data)=>{locations=data.data}) + } else { + ctx.clearRect(0, 0, c.width, c.height); + for (var loc of locations) { + ctx.beginPath(); + if (loc.safe) { + ctx.fillStyle="#6a6" + } else { + ctx.fillStyle="#a66" + } + ctx.arc((loc.x-mapX)/mapXScale,(loc.y-mapY)/mapYScale,10,0,2*Math.PI) + ctx.fill(); + } + for (var m of members) { + ctx.beginPath(); + ctx.fillStyle="#22f" + ctx.arc((m.x-mapX)/mapXScale,(m.y-mapY)/mapYScale,5,0,2*Math.PI); + ctx.fill(); + } + ctx.font = "22px Verdana"; + for (var loc of locations) { + if (mousePos.x>=(loc.x-mapX)/mapXScale-7 && mousePos.x<=(loc.x-mapX)/mapXScale+7 && + mousePos.y>=(loc.y-mapY)/mapYScale-7 && mousePos.y<=(loc.y-mapY)/mapYScale+7) { + var size = ctx.measureText(loc.name) + ctx.fillStyle="#eef" + ctx.fillRect(mousePos.x-size.width/2-2,mousePos.y-30,size.width+4,24) + ctx.fillStyle="#000" + ctx.fillText(loc.name,mousePos.x-size.width/2,mousePos.y-8) + } + } + var labelY = -30; + ctx.font = "14px Verdana"; + for (var m of members) { + if (mousePos.x>=(m.x-mapX)/mapXScale-7 && mousePos.x<=(m.x-mapX)/mapXScale+7 && + mousePos.y>=(m.y-mapY)/mapYScale-7 && mousePos.y<=(m.y-mapY)/mapYScale+7) { + var size = ctx.measureText(m.firstName+" "+m.lastName) + ctx.fillStyle="#eef" + ctx.fillRect(mousePos.x-size.width/2-2,mousePos.y-20+labelY,size.width+4,14) + ctx.fillStyle="#000" + ctx.fillText(m.firstName+" "+m.lastName,mousePos.x-size.width/2,mousePos.y-8+labelY) + labelY-=20; + } + } + } + } else { + ctx=null; + } +},50) + function Map(p) { - p.setActive("/map") + const [familyId,setFamilyId] = useState(0) + const [family,setFamily] = useState([]) + + useEffect(()=>{ + p.setActive("/map") + }) + useEffect(()=>{ + if (p.family[familyId]) { + members=p.family[familyId].members + //console.log(members) + } + },[p.family]) + useEffect(()=>{ + if (family[familyId]) { + members=family[familyId].members + //console.log(members) + } + },[family]) + useEffect(()=>{ + + const interval = setInterval(()=>{ + axios.get("http://localhost:8080/family") + .then((data)=>{setFamily(data.data)}) + },50) + return () => clearInterval(interval); + },[requiresUpdate]) return ( <>

Map View

+ + { + mousePos=getMousePos(e.target,e) + } + }/> ); } function Message(p) { - p.setActive("/messages") + const [familyId,setFamilyId] = useState(0) + const [memberId,setMemberId] = useState(null) + const [notifications,setNotifications] = useState([]) + const [timestamp,setTimestamp] = useState("") + + function displayTimestamp(date) { + var seconds = Math.floor((Date.now()-new Date(date))/1000); + seconds-=32400 + var minutes = Math.floor(seconds/60); + //console.log(Date.now()-new Date(date)) + //console.log(Date.now()) + + var hours = Math.floor(minutes/60) + if (seconds<60) {return <>{seconds} {"second"+((seconds!==1)?"s":"")} ago} else + if (minutes<60) {return <>{minutes} {"minute"+((minutes!==1)?"s":"")} ago} else { + return <>{hours} {"hour"+((hours!==1)?"s":"")} ago + } + } + + useEffect(()=>{ + p.setActive("/messages") + }) + + useEffect(()=>{ + if (memberId!==null) { + axios.get("http://localhost:8080/notification/"+memberId) + .then((data)=>{ + setNotifications(data.data) + }) + } else { + setNotifications([]) + } + const interval = setInterval(()=>{ + if (memberId!==null) { + axios.get("http://localhost:8080/notification/"+memberId) + .then((data)=>{ + setNotifications(data.data) + //console.log(data.data) + }) + } + },50) + return () => clearInterval(interval); + },[memberId,familyId]) + + useEffect(()=>{ + if (p.family[0]) { + setMemberId(p.family[0].members[0].id) + } + },[p.family]) + return ( <>

Message View

+ + +
+
+ {notifications.map((notification)=>{(notification.notificationType===0)?
{notification.message}{displayTimestamp(notification.date)}
:(notification.message.includes("You are"))?
{notification.message}{displayTimestamp(notification.date)}
:
{notification.message}{displayTimestamp(notification.date)}
}
)} ); } @@ -76,7 +239,8 @@ function Member(p) { setMobileDevice("") setRelationship("") setDisabled(false) - })}}>Add + }) + .catch(setDisabled(false))}}>Add @@ -113,6 +277,7 @@ function Editable(p) { {(editing)?<>{setValue(e.target.value);setStyle({})}} onKeyPress={(e)=>{ if (e.key==='Enter') { if (value.length===0) { + setStyle({border:"1px solid red",background:"#fdd"}) return new Error("Cannot accept 0 length string.") } setDisabled(true) @@ -126,7 +291,7 @@ function Editable(p) { }) .catch((err)=>{ setDisabled(false) - setStyle({border:"1px solid red"}) + setStyle({border:"1px solid red",background:"#fdd"}) }) } } @@ -215,10 +380,10 @@ function App() {
- + - + diff --git a/src/main/java/sig/family/Endpoints.java b/src/main/java/sig/family/Endpoints.java index e181495..1b2f121 100644 --- a/src/main/java/sig/family/Endpoints.java +++ b/src/main/java/sig/family/Endpoints.java @@ -1,5 +1,8 @@ package sig.family; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.DeleteMapping; @@ -39,12 +42,12 @@ import javax.websocket.server.PathParam; @RestController public class Endpoints { - FamilyRepository families; - FamilyMemberRepository members; - FamilyRelationshipRepository relationships; - LocationRepository locations; - KnownLocationRepository knownlocations; - NotificationRepository notifications; + public static FamilyRepository families; + public static FamilyMemberRepository members; + public static FamilyRelationshipRepository relationships; + public static LocationRepository locations; + public static KnownLocationRepository knownlocations; + public static NotificationRepository notifications; RestTemplate connection = new RestTemplate(); public Endpoints(FamilyRepository families, @@ -53,19 +56,19 @@ public class Endpoints { LocationRepository locations, KnownLocationRepository knownlocations, NotificationRepository notifications) { - this.families=families; - this.members=members; - this.relationships=relationships; - this.locations=locations; - this.knownlocations=knownlocations; - this.notifications=notifications; + Endpoints.families=families; + Endpoints.members=members; + Endpoints.relationships=relationships; + Endpoints.locations=locations; + Endpoints.knownlocations=knownlocations; + Endpoints.notifications=notifications; } @GetMapping("/family") public List _1() { List list = new ArrayList<>(); for (Family f : families.findAll()) { - list.add(new FamilyContainer(f.getName(),families,relationships,members)); + list.add(new FamilyContainer(f.getName(),families,relationships,members,locations)); } return list; } @@ -74,7 +77,7 @@ public class Endpoints { public FamilyContainer _2(@PathVariable Long id) { if (families.existsById(id)) { Family f = families.findById(id).get(); - return new FamilyContainer(f.getName(),families,relationships,members); + return new FamilyContainer(f.getName(),families,relationships,members,locations); } else { return null; } @@ -256,6 +259,16 @@ public class Endpoints { } } + @GetMapping("/knownlocation") + public Iterable _13() { + return knownlocations.findAll(); + } + + @GetMapping("/location/{memberid}") + public Location _13(@PathVariable Long memberid) { + return locations.findTopByMemberIdOrderByIdDesc(memberid).get(0); + } + @PostMapping("/knownlocation") /** * @RequestBody requires: @@ -287,7 +300,10 @@ public class Endpoints { @GetMapping("/notification/{id}") public List _10(@PathVariable Long id) { - return notifications.findByMemberId(id); + //return notifications.findByMemberIdOrderByDateDesc(id); + //Page findByValidIsTrue(Pageable pageable); + //List result = repository.findByValidIsTrue(new PageRequest(0, N)) + return notifications.findByMemberIdOrderByDateDesc(id,PageRequest.of(0,30,Sort.by(Direction.DESC,"date"))); } @PostMapping("/notification") diff --git a/src/main/java/sig/family/FamilyApp.java b/src/main/java/sig/family/FamilyApp.java index b9b4aee..e9acd12 100644 --- a/src/main/java/sig/family/FamilyApp.java +++ b/src/main/java/sig/family/FamilyApp.java @@ -1,16 +1,182 @@ package sig.family; +import java.net.URI; import java.util.Calendar; import java.util.Date; +import java.util.HashMap; +import java.util.Timer; +import java.util.TimerTask; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.http.RequestEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; @SpringBootApplication +@Service public class FamilyApp { + + static HashMap map = new HashMap<>(); + static RestTemplate connection = new RestTemplate(); + public final static int WAITMULT=3; + + public static Location postMessage(Message message) { + // Construct a URI from a template + URI uri = UriComponentsBuilder + .fromUriString("http://localhost:8080/location") + .buildAndExpand("") + .toUri(); + + // Create the request object + RequestEntity request = RequestEntity.post(uri) + .body(message); + + // Execute the request + ResponseEntity response = connection.exchange( + request, + Location.class // Declare the _type_ of the response + ); + + // Get the deserialized response body + return response.getBody(); + } + + @RequestMapping + static class Message{ + Long member; + double x; + double y; + public Long getMember() { + return member; + } + public void setMember(Long member) { + this.member = member; + } + public double getX() { + return x; + } + public void setX(double x) { + this.x = x; + } + public double getY() { + return y; + } + public void setY(double y) { + this.y = y; + } + } public static void main(String[] args) { SpringApplication.run(FamilyApp.class, args); + + Timer t2 = new Timer(); + t2.scheduleAtFixedRate(new TimerTask() { + + @Override + public void run() { + + for (FamilyMember m : Endpoints.members.findAll()) { + if (!map.containsKey(m.getId())) { + GPSUser g = new GPSUser(m.getId()); + if (m.getLastLocationId()!=null) { + g.x = Endpoints.knownlocations.findById(m.getLastLocationId()).get().getX(); + g.y = Endpoints.knownlocations.findById(m.getLastLocationId()).get().getY(); + } + else { + //Choose a random known location to start at. + Long count = (long)(Math.random()*Endpoints.knownlocations.count()); + for (KnownLocation l : Endpoints.knownlocations.findAll()) { + if (count>0) { + count--; + } else { + g.x=l.getX(); + g.y=l.getY(); + //System.out.println(connection.postForObject("http://localhost:8080", String.class, "6ba5969a",q)); + break; + } + } + } + g.waitTime=((int)Math.random()*28)+2; + //Choose a random known location to go to. + Long count = (long)(Math.random()*Endpoints.knownlocations.count()); + for (KnownLocation l : Endpoints.knownlocations.findAll()) { + if (count>0) { + count--; + } else { + g.targetX=l.getX(); + g.targetY=l.getY(); + //System.out.println(connection.postForObject("http://localhost:8080", String.class, "6ba5969a",q)); + break; + } + } + g.postLocation(); + map.put(m.getId(), g); + } + } + + for (Long id : map.keySet()) { + GPSUser u = map.get(id); + + if (u.waitTime>0) { + u.waitTime--; + } else { + if (u.targetX!=u.x || u.targetY!=u.y) { + switch ((int)(Math.random()*6)) { + case 0:{ + //Sometimes go a random direction. + switch ((int)(Math.random()*4)) { + case 0:{ + u.x+=0.001; + }break; + case 1:{ + u.x-=0.001; + }break; + case 2:{ + u.y+=0.001; + }break; + case 3:{ + u.y-=0.001; + }break; + } + }break; + default:{ + //Move towards. + if (Math.random()<0.5) { + u.x+=Math.signum(u.targetX-u.x)*0.001; + } + if (Math.random()<0.5) { + u.y+=Math.signum(u.targetY-u.y)*0.001; + } + } + } + u.waitTime=((int)Math.random()*6*WAITMULT)+2*WAITMULT; + } else { + //Select a new target. + //Choose a random known location to go to. + Long count = (long)(Math.random()*Endpoints.knownlocations.count()); + for (KnownLocation l : Endpoints.knownlocations.findAll()) { + if (count>0) { + count--; + } else { + u.targetX=l.getX(); + u.targetY=l.getY(); + //System.out.println(connection.postForObject("http://localhost:8080", String.class, "6ba5969a",q)); + break; + } + } + u.waitTime=((int)Math.random()*28*WAITMULT)+30*WAITMULT; + } + u.postLocation(); + } + } + + } + + }, 0l, 1000l); } } diff --git a/src/main/java/sig/family/FamilyContainer.java b/src/main/java/sig/family/FamilyContainer.java index 2696c52..8826a0e 100644 --- a/src/main/java/sig/family/FamilyContainer.java +++ b/src/main/java/sig/family/FamilyContainer.java @@ -26,13 +26,14 @@ public class FamilyContainer extends Family{ FamilyContainer(String name ,FamilyRepository families ,FamilyRelationshipRepository relationships - ,FamilyMemberRepository members) { + ,FamilyMemberRepository members + ,LocationRepository locations) { super(name); id = families.findByName(name).get(0).getId(); List relations = relationships.findByFamilyId(id); for (FamilyRelationship r : relations) { FamilyMember m = members.findById(r.getMemberId()).get(); - this.members.add(new FamilyMemberContainer(m,relationships)); + this.members.add(new FamilyMemberContainer(m,relationships,locations)); } } diff --git a/src/main/java/sig/family/FamilyMemberContainer.java b/src/main/java/sig/family/FamilyMemberContainer.java index 6b4697c..9cd5fb7 100644 --- a/src/main/java/sig/family/FamilyMemberContainer.java +++ b/src/main/java/sig/family/FamilyMemberContainer.java @@ -1,5 +1,7 @@ package sig.family; +import java.util.List; + import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; @@ -10,6 +12,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import com.fasterxml.jackson.annotation.JsonInclude; +import sig.family.FamilyApp.Message; + @RequestMapping public class FamilyMemberContainer extends FamilyMember{ Long id; @@ -22,15 +26,39 @@ public class FamilyMemberContainer extends FamilyMember{ String relationship; + double x,y; + FamilyMemberContainer(){} public FamilyMemberContainer(FamilyMember m, - FamilyRelationshipRepository relationships) { + FamilyRelationshipRepository relationships, + LocationRepository locations) { this.firstName = m.firstName; this.lastName = m.lastName; this.mobileDeviceId = m.mobileDeviceId; this.id=m.id; relationship = relationships.findByMemberId(m.getId()).get(0).getRelationship(); + List locs = locations.findTopByMemberIdOrderByIdDesc(m.getId()); + if (locs.size()>0) { + Location l = locs.get(0); + this.x=l.getX(); + this.y=l.getY(); + } else { + //Use their assigned location. + if (this.getLastLocationId()==null) { + this.x=-110.253; + this.y=31.554; + Message mm = new Message(); + mm.member=m.getId(); + mm.setX(this.x); + mm.setY(this.y); + FamilyApp.postMessage(mm); + } else { + Location l = locations.findById(this.getLastLocationId()).get(); + this.x=l.getX(); + this.y=l.getY(); + } + } } public Long getId() { @@ -73,4 +101,20 @@ public class FamilyMemberContainer extends FamilyMember{ public void setRelationship(String relationship) { this.relationship = relationship; } + + public double getX() { + return x; + } + + public void setX(double x) { + this.x = x; + } + + public double getY() { + return y; + } + + public void setY(double y) { + this.y = y; + } } diff --git a/src/main/java/sig/family/GPSUser.java b/src/main/java/sig/family/GPSUser.java new file mode 100644 index 0000000..3818557 --- /dev/null +++ b/src/main/java/sig/family/GPSUser.java @@ -0,0 +1,24 @@ +package sig.family; + +import sig.family.FamilyApp.Message; + +public class GPSUser { + Long id; + double x,y; + int waitTime=0; + double targetX,targetY; + + GPSUser(Long id) { + this.id=id; + } + + public Location postLocation() { + Message mm = new Message(); + mm.member=id; + mm.x=x; + mm.y=y; + Location res = FamilyApp.postMessage(mm); + System.out.println(res); + return res; + } +} diff --git a/src/main/java/sig/family/Location.java b/src/main/java/sig/family/Location.java index 90adb47..462c36f 100644 --- a/src/main/java/sig/family/Location.java +++ b/src/main/java/sig/family/Location.java @@ -1,5 +1,6 @@ package sig.family; +import java.lang.reflect.Field; import java.util.Date; import javax.persistence.Column; @@ -69,5 +70,23 @@ public class Location { public void setDate(Date date) { this.date = date; } - + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(this.getClass().getName()+"("); + boolean first=true; + for (Field f : this.getClass().getDeclaredFields()) { + if (!first) { + sb.append(","); + } + try { + sb.append(f.getName()+"="+f.get(this)); + first=false; + } catch (IllegalArgumentException|IllegalAccessException e) { + e.printStackTrace(); + } + } + sb.append(")"); + return sb.toString(); + } } diff --git a/src/main/java/sig/family/LocationRepository.java b/src/main/java/sig/family/LocationRepository.java index f284e46..682bd88 100644 --- a/src/main/java/sig/family/LocationRepository.java +++ b/src/main/java/sig/family/LocationRepository.java @@ -1,7 +1,9 @@ package sig.family; +import java.util.List; + import org.springframework.data.repository.CrudRepository; public interface LocationRepository extends CrudRepository{ - + List findTopByMemberIdOrderByIdDesc(Long memberId); } diff --git a/src/main/java/sig/family/NotificationRepository.java b/src/main/java/sig/family/NotificationRepository.java index 181e365..299138a 100644 --- a/src/main/java/sig/family/NotificationRepository.java +++ b/src/main/java/sig/family/NotificationRepository.java @@ -2,8 +2,11 @@ package sig.family; import java.util.List; +import org.springframework.data.domain.Pageable; import org.springframework.data.repository.CrudRepository; public interface NotificationRepository extends CrudRepository{ - List findByMemberId(Long memberid); + List findByMemberIdOrderByDateDesc(Long memberid); + List findByMemberIdOrderByDateDesc(Long memberid,Pageable p); + List OrderByDateDesc(); }