Skip to content
Snippets Groups Projects
Commit 583d6e2e authored by Connor Brock's avatar Connor Brock
Browse files

Merge branch 'gamification' into 'main'

Resolve "As a user, I would like a place to see all my earned badges, so that I can track my progress."

Closes #51

See merge request !25
parents cc2d0669 efc03fa4
No related branches found
No related tags found
1 merge request!25Resolve "As a user, I would like a place to see all my earned badges, so that I can track my progress."
Showing
with 509 additions and 16 deletions
/*AUTHOR: Gabriel Copat*/
package Team5.SmartTowns.rewards;
import lombok.Data;
import java.io.File;
import java.util.Objects;
@Data
public class Badge {
/* Badges can be earned by completing certain goals.
* They are displayed in the user profile page
*
* For example, one might earn a badge after visiting 20 locations */
int id;
String name;
String description;
String imgPath;
int difficulty; //1-5
public Badge(int id, String name, String description, int difficulty) {
this.id = id;
this.name = name;
this.description = description;
this.difficulty = difficulty;
imgPath = findImagePath();
}
private String findImagePath(){
/* Finds the image in the Path folder, if image is not found assigns default image */
String imgPath = "images/rewards/badges/" + id + ".jpg";
String notFoundPath = "/images/rewards/badges/0.png";
File imgFile = new File("src/main/resources/static/" + imgPath);
return imgFile.exists() ? imgPath : notFoundPath;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Badge badge = (Badge) o;
return id == badge.id && Objects.equals(name, badge.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
package Team5.SmartTowns.rewards;
import org.springframework.stereotype.Controller;
@Controller
public class RewardsController {
}
/*AUTHOR: Gabriel Copat*/
package Team5.SmartTowns.rewards;
import lombok.Data;
import java.io.File;
import java.util.Objects;
@Data
public class Sticker {
/* Stickers are trade-able rewards, they vary in rarity and are earned at random */
int id;
String name;
String description;
String imgPath;
int rarity; //1-5
public Sticker(int id, String name, String description, int rarity) {
this.id = id;
this.name = name;
this.description = description;
this.rarity = rarity;
imgPath = findImagePath();
}
private String findImagePath(){
/* Finds the image in the Path folder, if image is not found assigns default image */
String imgPath = "images/rewards/stickers/" + id + ".jpg";
String notFoundPath = "images/rewards/stickers/0.png";
File imgFile = new File("src/main/resources/static/" + imgPath);
return imgFile.exists() ? imgPath : notFoundPath;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Sticker sticker = (Sticker) o;
return id == sticker.id && Objects.equals(name, sticker.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
package Team5.SmartTowns.users;
import Team5.SmartTowns.rewards.Badge;
import Team5.SmartTowns.rewards.Sticker;
import lombok.Data;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
@Data
public class User {
int id;
String email; //Validation would be done by email, since they will have that
String name;
String imgPath;
Map<Badge, Integer> badgeProgress = new HashMap<>(); // Demonstrates the progress towards a specific badge (0-100)
Map<Sticker, Boolean> hasStickers = new HashMap<>(); // True if User has sticker (key)
public User(int id, String email, String name) {
this.id = id;
this.email = email;
this.name = name;
imgPath = findImagePath();
}
private String findImagePath(){
/* Finds the image in the Path folder, if image is not found assigns default image */
String imgPath = "images/users/" + id + ".jpg";
String notFoundPath = "../images/users/0.png";
File imgFile = new File("src/main/resources/static/" + imgPath);
return imgFile.exists() ? "../" + imgPath : notFoundPath;
}
}
package Team5.SmartTowns.users;
import Team5.SmartTowns.rewards.Badge;
import Team5.SmartTowns.rewards.Sticker;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
@Controller
public class UserController {
/* TEMPORARY USER LIST --- TODO REPLACE IT WITH DATABASE LIST*/
static List<User> users = List.of(
new User(1, "johndoe@gmail.com", "Claire Redfield"),
new User(2, "johndoe@gmail.com", "Albert Wesker"),
new User(3, "johndoe@gmail.com", "Leon Kennedy"),
new User(4, "johndoe@gmail.com", "Jill Valentine")
);
static List<Badge> badges = List.of(
new Badge(1, "Badge1", "Bage One is This", 1),
new Badge(2, "Badge1", "Bage One is This", 4),
new Badge(3, "Badge1", "Bage One is This", 4),
new Badge(4, "Badge1", "Bage One is This", 5),
new Badge(5, "Badge1", "Bage One is This", 5),
new Badge(46, "Badge1", "Bage One is This", 5),
new Badge(7, "Badge1", "Bage One is This", 2)
);
static List<Sticker> stickers = List.of(
new Sticker(1, "Sticker", "Sticker", 1),
new Sticker(2, "Sticker", "Sticker", 4),
new Sticker(3, "Sticker", "Sticker One is This", 4),
new Sticker(4, "Sticker", "Sticker One is This", 5),
new Sticker(5, "Sticker", "Sticker One is This", 5),
new Sticker(46, "Sticker", "Sticker One is This", 5),
new Sticker(7, "Sticker", "Sticker One is This", 2)
);
@GetMapping("/user/{id}")
public ModelAndView getUserPage(@PathVariable int id) {
ModelAndView mav = new ModelAndView("rewards/userProfile");
users.stream()
.filter(user -> user.getId() == id)
.findFirst() //Convoluted way of finding the matching user to the id, probably easier to do a hashmap
.ifPresent(result -> mav.addObject("user", result));
mav.addObject("badges", badges);
mav.addObject("stickers", stickers);
return mav;
}
// @GetMapping("/userProfile")
// public ModelAndView getUserPage(ModelAndView mav) {
// return mav;
// }
}
...@@ -183,6 +183,7 @@ main .badgesBlock{ ...@@ -183,6 +183,7 @@ main .badgesBlock{
} }
footer { footer {
z-index: 99;
bottom: 0%; bottom: 0%;
left: 0%; left: 0%;
position: fixed; position: fixed;
......
/* AUTHOR: Gabriel Copat*/
/*FONTS, TYPOGRAPHY & BACKGROUNDS*/
* {
margin: 0;
padding: 0;
& h1, & h2 {
letter-spacing: 0.25vw;
line-height: 1.3;
text-align: center;
color: white;
text-justify: inter-word;
}
}
@media only screen and (max-device-width: 500px) {
/*ADJUSTING FOR SMALLER SCREENS*/
* {
& h1, & h2 { text-shadow: rgba(0, 0, 0, 0.7) 0 0.5svh 1svh;}
& p { line-height: 1.1; color: white;}
}
}
body {
background: linear-gradient(135deg, #9f74be, #3e126b);
height: 100svh;
}
main {
background: linear-gradient(to bottom, #1e1e1e 10%, darkgoldenrod 50%, #1e1e1e 90%);
border-radius: 1vw;
margin-inline: 5%;
/*margin-block: 5%;*/
width: auto;
padding-block: 2svh;
margin-top: 6em;
padding-inline: 1vw;
box-shadow: rgba(0, 0, 0, 0.7) 0 0.5svh max(1vw, 1em);
}
.userInfo {
display: flex;
flex-direction: column;
/*padding: min(2vw, 4em);*/
text-align: center;
& #userPicture {
width: min(30vw, 30em);
margin-inline: auto;
border-radius: 100%;
border: solid #a2a2a2 4px;
box-shadow: rgba(0, 0, 0, 0.7) 0 0.5svh max(1vw, 1em);
}
& h1 {
font-size: max(5vw, 2em);
margin: 1svh 25%;
color:white;
border-bottom: #36454F solid 2px;
border-radius: 5vw;
box-shadow: rgba(0, 0, 0, 0.7) 0 0.5svh 1vw -1vw;
}
}
#badgesBar::-webkit-scrollbar {
display: none;
-ms-scrollbar-darkshadow-color: transparent;
}
#badgesBar {
display: grid;
grid-template-areas:
"header"
"badges";
overflow-x: scroll;
overflow-y: hidden;
color: white;
padding-bottom: 2%;
@media only screen and (min-device-width: 501px) {
height: 24vw;
}
& h2 {
position: absolute;
grid-area: header;
margin-inline: 5vw;
padding-inline: 2vw;
margin-block: -1svh;
box-shadow: rgba(0, 0, 0, 0.7) 0 0.5svh 1vw -1vw;
border-bottom: #36454F solid 2px;
font-size: 4vw;
width: 7em;
height: 1.2em;
}
& #allBadgesContainer {
margin-top: 3svh;
grid-area: badges;
height: 10svh;
align-content: center;
display: flex;
@media only screen and (min-device-width: 501px) {
height: 20vw;
margin-top: 6vw;
}
}
& .badgeImg {
margin-inline: 3vw;
height: 8svh;
z-index: 50;
@media only screen and (min-device-width: 501px) {
height: 15vw;
}
transition: 0.3s ease-out 100ms;
}
& .badgeImg:hover {
/*box-shadow: 0 0 20px 10px #bbbb00;*/
transform: scale(1.5,1.5);
}
}
#stickersBox {
padding-top: 5%;
display: flex;
flex-direction: column;
/* border-bottom-left-radius: 2vw; */
/* border-bottom-right-radius: 2vw; */
/*background: linear-gradient(to bottom, darkgoldenrod, transparent 90%);*/
margin-top: -1%;
& h2 {
font-size: 4em;
text-align: center;
box-shadow: rgba(0, 0, 0, 0.7) 0 2vw 2vw -2vw;
border-bottom: #36454F solid 2px;
margin-block: 1svh;
margin-inline: 25%;
}
& .stickersContainer {
margin-block: 1svh;
display: flex;
flex-wrap: wrap;
justify-content: space-around;
width: 100%;
& .stickerImg {
width: 20vw;
margin-block: 1em;
}
}
}
.locked {
filter: grayscale(100%);
}
.dragonProgression {
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
height: 16svh;
box-sizing: content-box;
/*background: linear-gradient(to bottom, transparent -50%, darkgoldenrod 50%);*/
width: 100%;
/*padding-top: 1svh;*/
@media only screen and (min-device-width: 501px) {
height: 28vw;
margin-bottom: 0;
padding-bottom: 5svh;
}
& h1 {
font-size: 3em;
box-shadow: rgba(0, 0, 0, 0.7) 0 2vw 2vw -2vw;
border-bottom: #36454F solid 2px;
border-top: #36454F solid 2px;
margin-inline: 25%;
margin-bottom: 1%;
}
& .dragonContainer {
position: relative;
margin: auto;
}
& .dragonImg {
height: 10svh;
width: 16svh;
@media only screen and (min-device-width: 501px) {
height: 22vw;
width: 30vw;
}
}
& .dragonFill {
position: absolute;
overflow: hidden;
width: 40%;
}
& .dragonOut {
/*position: absolute;*/
overflow: hidden;
}
}
header {
z-index: 99;
top: 0.5svh;
left: 0;
position: fixed;
width: 100vw;
justify-content: center;
display: flex;
}
header .footerBar {
display: flex;
list-style: none;
border-radius: 1vw;
overflow: hidden;
justify-content: space-evenly;
background-color: rgba(0, 0, 0, 0.7);
}
header .footerButton {
padding: 1vw;
text-align: center;
/*flex: 1 1;*/
color:crimson;
background-color: rgba(31, 31, 31, 0.7);
font-size: 2.5em;
width: 15vw;
}
header .footerButton:hover {
background-color: #36454F;
}
\ No newline at end of file
src/main/resources/static/images/rewards/badges/0.png

639 KiB

src/main/resources/static/images/rewards/dragonFilled.png

49.7 KiB

src/main/resources/static/images/rewards/dragonOutline.png

46.8 KiB

src/main/resources/static/images/rewards/stickers/0.png

1.65 MiB

src/main/resources/static/images/users/0.png

21.7 KiB

function selectTrail(string, element) {
console.log('Clicked')
let listEl = document.getElementsByClassName('liHeader')
for (let i = 0; i < listEl.length; i++) {
listEl[i].classList.remove('selected')
}
document.getElementById(string).classList.add("selected")
}
function updateOutput() {
$.post("test_ajax_frag").done(function (fragment) {
console.log(fragment);
$("#trailInfoBox").replaceWith(fragment);
});
}
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title th:text="'VZLA Profile Page of ' + ${user.getName()}"></title>
<link rel="stylesheet" th:href="@{/css/userProfile.css}">
<!-- <link rel="stylesheet" th:href="@{/css/templatingstyle.css}">-->
</head>
<body>
<header>
<ul class="footerBar">
<li class="footerButton"><b>Home</b></li>
<li class="footerButton"><b>About</b></li>
<li class="footerButton"><b>Map</b></li>
<li class="footerButton"><b>Facilities</b></li>
<li class="footerButton"><b>Search</b></li>
</ul>
</header>
<main>
<!--PICTURE - DATA - BADGES -->
<div class="userInfo">
<img th:src="@{${user.getImgPath()}}"
th:alt="${user.getName()}"
id="userPicture"
>
<h1 th:text="${user.getName()}"></h1>
<!--TODO add some progression info here?-->
</div>
<section class="rewards"> <!--Reward lists, badges on top, stickers (larger) on the bottom-->
<article id="badgesBar">
<h2>Your Badges: </h2> <!--Shows first earned badges, followed by greyed out badges-->
<div id="allBadgesContainer" class="centerFlex">
<img class="badgeImg" th:each="badge : ${badges}" th:src="@{'..' + ${badge.getImgPath()}}"
th:id="'img' + ${badge.getId()}" th:alt="${badge.getName()}" >
</div>
</article>
<article class="dragonProgression">
<h1>The Dragon Trail</h1>
<div class="dragonContainer">
<div class="dragonFill">
<img th:src="@{/images/rewards/dragonFilled.png}"
alt="Filled Dragon" id="FilledDragon" class="dragonImg">
</div>
<div class="dragonOut">
<img th:src="@{/images/rewards/dragonOutline.png}"
alt="Outline Dragon" id="OutlineDragon" class="dragonImg">
</div>
</div>
<h2>40%</h2>
</article>
<article id="stickersBox"> <!--Need a controller to show earned stickers -->
<h2> STICKERS! </h2>
<div class="stickersContainer">
<img class="stickerImg" th:each="sticker : ${stickers}" th:src="@{'../' + ${sticker.getImgPath()}}"
th:id="'img' + ${sticker.getId()}" th:alt="${sticker.getName()}" >
</div>
</article>
</section>
</main>
<footer>
</footer>
</body>
</html>
<!--TODO finished doing the tooltips, need to add some more changes to them for sure
TODO afterwards probably need to implement thymeleaf so it shows badges based on the list
TODO implement some placeholder pictures as well for the badges and for the stickers -->
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment