Compare commits
157 Commits
account
...
errorDebug
| Author | SHA1 | Date | |
|---|---|---|---|
| 216204fac5 | |||
| 01e01dcd36 | |||
| 6f77eb0881 | |||
| 15949b6405 | |||
| 83d304e215 | |||
| 240e23743b | |||
| 3a99049165 | |||
| 9373aecc86 | |||
| 50d79cfc40 | |||
| 4d2f363f8a | |||
| 2b0109468c | |||
| ed86c83d82 | |||
| b900123a09 | |||
| 580b8bdc2f | |||
| 81363beb47 | |||
| 3a214d2589 | |||
| 664d150ca4 | |||
| 095e332ccb | |||
| 33718976f9 | |||
| 7089e480ae | |||
| 2313b3cded | |||
| b0c5a2f458 | |||
| ba0caaade3 | |||
| fa69fc92a2 | |||
| 492320e4d6 | |||
| 00e55b44cb | |||
| 3ad22b4a9c | |||
| 3b735645e0 | |||
| ed1fdd34df | |||
| dba4c6c7d0 | |||
| 75c9ef4a85 | |||
| bd5b5704d1 | |||
| 885a3064eb | |||
| 9385eb908f | |||
| 656930cb7c | |||
| 2888765b39 | |||
| 23635612a1 | |||
| f82ac09fd3 | |||
| 5378764e37 | |||
| 91e58ab328 | |||
| a267d00d59 | |||
| 5b0a0a80f3 | |||
| 1e44b0548c | |||
| 64319e16d9 | |||
| e4d25a2dde | |||
| d36358fa7f | |||
| fcf280dfca | |||
| b98c99a2da | |||
| 0a50eb6dd8 | |||
| fcf3c6d312 | |||
| 1b8ba32c3f | |||
| 57e5eca0f9 | |||
| 65eeea22e8 | |||
| e55736d4d5 | |||
| 53a3616d5c | |||
| 7c626b4f82 | |||
| 3cd18e883d | |||
| da9fb487d2 | |||
| d797af3c57 | |||
| 4231bc6da1 | |||
| 803696e2a8 | |||
| ba88ea0a0a | |||
| 97f1fc6fae | |||
| 5529e5422c | |||
| 8daa15f24c | |||
| 3bb39b0027 | |||
| ddc47e8679 | |||
| 3c1c05d40e | |||
| 98025265a8 | |||
| dea555d5c2 | |||
| 8183907366 | |||
| 0de6aeef80 | |||
| b41c95320e | |||
| 2437c52faf | |||
| e5772a9705 | |||
| 40e2ba1f7b | |||
| 2b9e54f485 | |||
| 253c7641da | |||
| ab95dbfe46 | |||
| 6e82749142 | |||
| 31b224190b | |||
| b8aedfc28c | |||
| 4a601dae66 | |||
| b263824429 | |||
| f1b8b794d3 | |||
| dcd94fc25c | |||
| bf2be6442a | |||
| 550b0356fb | |||
| d67ba1671e | |||
| 782ebe8200 | |||
| 6ecf5f91d9 | |||
| 32a29bab38 | |||
| 6b0302a91d | |||
| 0b88dfe371 | |||
| 39578d4c61 | |||
| 8a90b216fd | |||
| 76e9daa384 | |||
| 7c893f0ae7 | |||
| 2bda42b36d | |||
| 6aed3cf6b4 | |||
| 9d4f4c212b | |||
| d84d65f28f | |||
| f0c32d408d | |||
| ddc8a459e0 | |||
| 75c3a6fe23 | |||
| e8da4650db | |||
| 7f2a34a99d | |||
| b564b1a836 | |||
| 4be170e203 | |||
| cb7845499d | |||
| e9387de879 | |||
| 9c21cb61e3 | |||
| ff9844ae61 | |||
| fdfd5b0985 | |||
| 58a7fe300c | |||
| 50b26b190e | |||
| b7fce3bf31 | |||
| e84fb87cd0 | |||
| 30f7a61b7e | |||
| 438bcf922e | |||
| a42093a0f0 | |||
| b4e2c4ebca | |||
| 1dc05b84cf | |||
| e0fb3ced68 | |||
| 9ce66652ab | |||
| c8484d4cc1 | |||
| cdbc7a5dd4 | |||
| 7a94b3fbf8 | |||
| 49576828b6 | |||
| 26aa07f4b1 | |||
| b05064e87b | |||
| 4f31b80707 | |||
| da4ea70b05 | |||
| 4c5f806dd2 | |||
| 6d7b19a478 | |||
| acfbf6e0da | |||
| 9f9d5751f6 | |||
| d362b5cd3e | |||
| 160c96047c | |||
| 90ba42d7f7 | |||
| fb2b92c381 | |||
| b758087a96 | |||
| 599fc8a125 | |||
| 54eabb62d2 | |||
| 7471f71212 | |||
| 137ee91845 | |||
| f8f01c0bcb | |||
| 4256eb45e8 | |||
| 8ae533d801 | |||
| 6df3790e51 | |||
| 52d5691b5b | |||
| 05cb6f9003 | |||
| 419726f94e | |||
| 606461d75a | |||
| e636e80a82 | |||
| dda2d48ea0 | |||
| 3129dc5b80 |
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "Backend"]
|
||||||
|
path = Backend
|
||||||
|
url = https://github.com/VergeDX/clock_api
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
HELP.md
|
|
||||||
.gradle
|
|
||||||
build/
|
|
||||||
!gradle/wrapper/gradle-wrapper.jar
|
|
||||||
!**/src/main/**/build/
|
|
||||||
!**/src/test/**/build/
|
|
||||||
|
|
||||||
### STS ###
|
|
||||||
.apt_generated
|
|
||||||
.classpath
|
|
||||||
.factorypath
|
|
||||||
.project
|
|
||||||
.settings
|
|
||||||
.springBeans
|
|
||||||
.sts4-cache
|
|
||||||
bin/
|
|
||||||
!**/src/main/**/bin/
|
|
||||||
!**/src/test/**/bin/
|
|
||||||
|
|
||||||
### IntelliJ IDEA ###
|
|
||||||
.idea
|
|
||||||
*.iws
|
|
||||||
*.iml
|
|
||||||
*.ipr
|
|
||||||
out/
|
|
||||||
!**/src/main/**/out/
|
|
||||||
!**/src/test/**/out/
|
|
||||||
|
|
||||||
### NetBeans ###
|
|
||||||
/nbproject/private/
|
|
||||||
/nbbuild/
|
|
||||||
/dist/
|
|
||||||
/nbdist/
|
|
||||||
/.nb-gradle/
|
|
||||||
|
|
||||||
### VS Code ###
|
|
||||||
.vscode/
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
id("org.springframework.boot") version "2.4.1"
|
|
||||||
id("io.spring.dependency-management") version "1.0.10.RELEASE"
|
|
||||||
kotlin("jvm") version "1.4.21"
|
|
||||||
kotlin("plugin.spring") version "1.4.21"
|
|
||||||
// kotlin("plugin.jpa") version "1.4.21"
|
|
||||||
}
|
|
||||||
|
|
||||||
group = "org.hydev.ios"
|
|
||||||
version = "0.0.1.1"
|
|
||||||
java.sourceCompatibility = JavaVersion.VERSION_11
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
|
||||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
|
||||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
|
||||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
|
||||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
|
||||||
implementation("org.springframework.boot", "spring-boot-starter-validation", "2.4.1")
|
|
||||||
|
|
||||||
// MongoDB
|
|
||||||
implementation(group = "org.springframework.data", name = "spring-data-mongodb", version = "3.1.2")
|
|
||||||
implementation(group = "org.springframework.boot", name = "spring-boot-starter-data-mongodb")
|
|
||||||
|
|
||||||
// MariaDB
|
|
||||||
// implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
|
||||||
// runtimeOnly("org.mariadb.jdbc:mariadb-java-client")
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.withType<KotlinCompile> {
|
|
||||||
kotlinOptions {
|
|
||||||
freeCompilerArgs = listOf("-Xjsr305=strict")
|
|
||||||
jvmTarget = "11"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.withType<Test> {
|
|
||||||
useJUnitPlatform()
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
|
||||||
distributionPath=wrapper/dists
|
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
|
||||||
zipStorePath=wrapper/dists
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
|
|
||||||
#
|
|
||||||
# Copyright 2015 the original author or authors.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
#
|
|
||||||
|
|
||||||
##############################################################################
|
|
||||||
##
|
|
||||||
## Gradle start up script for UN*X
|
|
||||||
##
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
|
||||||
# Resolve links: $0 may be a link
|
|
||||||
PRG="$0"
|
|
||||||
# Need this for relative symlinks.
|
|
||||||
while [ -h "$PRG" ] ; do
|
|
||||||
ls=`ls -ld "$PRG"`
|
|
||||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
|
||||||
if expr "$link" : '/.*' > /dev/null; then
|
|
||||||
PRG="$link"
|
|
||||||
else
|
|
||||||
PRG=`dirname "$PRG"`"/$link"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
SAVED="`pwd`"
|
|
||||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
|
||||||
APP_HOME="`pwd -P`"
|
|
||||||
cd "$SAVED" >/dev/null
|
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
|
||||||
APP_BASE_NAME=`basename "$0"`
|
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
|
||||||
MAX_FD="maximum"
|
|
||||||
|
|
||||||
warn () {
|
|
||||||
echo "$*"
|
|
||||||
}
|
|
||||||
|
|
||||||
die () {
|
|
||||||
echo
|
|
||||||
echo "$*"
|
|
||||||
echo
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false').
|
|
||||||
cygwin=false
|
|
||||||
msys=false
|
|
||||||
darwin=false
|
|
||||||
nonstop=false
|
|
||||||
case "`uname`" in
|
|
||||||
CYGWIN* )
|
|
||||||
cygwin=true
|
|
||||||
;;
|
|
||||||
Darwin* )
|
|
||||||
darwin=true
|
|
||||||
;;
|
|
||||||
MINGW* )
|
|
||||||
msys=true
|
|
||||||
;;
|
|
||||||
NONSTOP* )
|
|
||||||
nonstop=true
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
|
||||||
# IBM's JDK on AIX uses strange locations for the executables
|
|
||||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
|
||||||
else
|
|
||||||
JAVACMD="$JAVA_HOME/bin/java"
|
|
||||||
fi
|
|
||||||
if [ ! -x "$JAVACMD" ] ; then
|
|
||||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
location of your Java installation."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
JAVACMD="java"
|
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
location of your Java installation."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
|
||||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
|
||||||
MAX_FD_LIMIT=`ulimit -H -n`
|
|
||||||
if [ $? -eq 0 ] ; then
|
|
||||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
|
||||||
MAX_FD="$MAX_FD_LIMIT"
|
|
||||||
fi
|
|
||||||
ulimit -n $MAX_FD
|
|
||||||
if [ $? -ne 0 ] ; then
|
|
||||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# For Darwin, add options to specify how the application appears in the dock
|
|
||||||
if $darwin; then
|
|
||||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
|
||||||
fi
|
|
||||||
|
|
||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
|
||||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
|
||||||
|
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
|
||||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
|
||||||
SEP=""
|
|
||||||
for dir in $ROOTDIRSRAW ; do
|
|
||||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
|
||||||
SEP="|"
|
|
||||||
done
|
|
||||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
|
||||||
# Add a user-defined pattern to the cygpath arguments
|
|
||||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
|
||||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
|
||||||
fi
|
|
||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
|
||||||
i=0
|
|
||||||
for arg in "$@" ; do
|
|
||||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
|
||||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
|
||||||
|
|
||||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
|
||||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
|
||||||
else
|
|
||||||
eval `echo args$i`="\"$arg\""
|
|
||||||
fi
|
|
||||||
i=`expr $i + 1`
|
|
||||||
done
|
|
||||||
case $i in
|
|
||||||
0) set -- ;;
|
|
||||||
1) set -- "$args0" ;;
|
|
||||||
2) set -- "$args0" "$args1" ;;
|
|
||||||
3) set -- "$args0" "$args1" "$args2" ;;
|
|
||||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
|
||||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
|
||||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
|
||||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
|
||||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
|
||||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Escape application args
|
|
||||||
save () {
|
|
||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
|
||||||
echo " "
|
|
||||||
}
|
|
||||||
APP_ARGS=`save "$@"`
|
|
||||||
|
|
||||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
|
||||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
@rem
|
|
||||||
@rem Copyright 2015 the original author or authors.
|
|
||||||
@rem
|
|
||||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
@rem you may not use this file except in compliance with the License.
|
|
||||||
@rem You may obtain a copy of the License at
|
|
||||||
@rem
|
|
||||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
@rem
|
|
||||||
@rem Unless required by applicable law or agreed to in writing, software
|
|
||||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
@rem See the License for the specific language governing permissions and
|
|
||||||
@rem limitations under the License.
|
|
||||||
@rem
|
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
|
||||||
@rem ##########################################################################
|
|
||||||
@rem
|
|
||||||
@rem Gradle startup script for Windows
|
|
||||||
@rem
|
|
||||||
@rem ##########################################################################
|
|
||||||
|
|
||||||
@rem Set local scope for the variables with windows NT shell
|
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
|
||||||
set APP_BASE_NAME=%~n0
|
|
||||||
set APP_HOME=%DIRNAME%
|
|
||||||
|
|
||||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
|
||||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
|
||||||
|
|
||||||
@rem Find java.exe
|
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
|
||||||
|
|
||||||
set JAVA_EXE=java.exe
|
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
|
||||||
if "%ERRORLEVEL%" == "0" goto execute
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
|
||||||
echo.
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
echo location of your Java installation.
|
|
||||||
|
|
||||||
goto fail
|
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
|
||||||
echo.
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
echo location of your Java installation.
|
|
||||||
|
|
||||||
goto fail
|
|
||||||
|
|
||||||
:execute
|
|
||||||
@rem Setup the command line
|
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
|
||||||
|
|
||||||
:end
|
|
||||||
@rem End local scope for the variables with windows NT shell
|
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
|
||||||
|
|
||||||
:fail
|
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
|
||||||
rem the _cmd.exe /c_ return code!
|
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
|
||||||
exit /b 1
|
|
||||||
|
|
||||||
:mainEnd
|
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
|
||||||
|
|
||||||
:omega
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
rootProject.name = "alarm-clock"
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package org.hydev.ios.alarmclock
|
|
||||||
|
|
||||||
import org.hydev.ios.alarmclock.data.UserRepo
|
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
|
||||||
import org.springframework.boot.runApplication
|
|
||||||
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories
|
|
||||||
|
|
||||||
@SpringBootApplication
|
|
||||||
@EnableMongoRepositories(basePackageClasses = [UserRepo::class])
|
|
||||||
class AlarmClockApplication
|
|
||||||
|
|
||||||
fun main(args: Array<String>)
|
|
||||||
{
|
|
||||||
runApplication<AlarmClockApplication>(*args)
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
package org.hydev.ios.alarmclock
|
|
||||||
|
|
||||||
import org.springframework.http.ResponseEntity
|
|
||||||
import java.security.SecureRandom
|
|
||||||
import java.security.spec.KeySpec
|
|
||||||
import java.util.*
|
|
||||||
import javax.crypto.SecretKeyFactory
|
|
||||||
import javax.crypto.spec.PBEKeySpec
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate "Bad Request" response entity
|
|
||||||
*
|
|
||||||
* @param msg Message
|
|
||||||
* @return Response entity
|
|
||||||
*/
|
|
||||||
fun bad(msg: String): ResponseEntity<String> = ResponseEntity.badRequest().body(msg)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate random salt
|
|
||||||
*
|
|
||||||
* @param len Length of the salt in bytes
|
|
||||||
* @return Random byte array of size len
|
|
||||||
*/
|
|
||||||
fun randSalt(len: Int = 16): ByteArray
|
|
||||||
{
|
|
||||||
val random = SecureRandom()
|
|
||||||
val salt = ByteArray(len)
|
|
||||||
random.nextBytes(salt)
|
|
||||||
return salt
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hash a password
|
|
||||||
*
|
|
||||||
* @return <Hash, Salt>
|
|
||||||
*/
|
|
||||||
fun String.passwordHash(salt: String = randSalt().base64): Pair<String, String>
|
|
||||||
{
|
|
||||||
val spec: KeySpec = PBEKeySpec(toCharArray(), salt.base64, 32767, 512)
|
|
||||||
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
|
|
||||||
return Pair(factory.generateSecret(spec).encoded.base64, salt)
|
|
||||||
}
|
|
||||||
|
|
||||||
val ByteArray.base64: String
|
|
||||||
get() = Base64.getEncoder().encodeToString(this)
|
|
||||||
|
|
||||||
val String.base64: ByteArray
|
|
||||||
get() = Base64.getDecoder().decode(this)
|
|
||||||
|
|
||||||
|
|
||||||
fun main(args: Array<String>)
|
|
||||||
{
|
|
||||||
val (hash, salt) = "password".passwordHash()
|
|
||||||
println(hash)
|
|
||||||
println(salt)
|
|
||||||
val (hash2, salt2) = "password".passwordHash(salt)
|
|
||||||
assert(hash == hash2 && salt == salt2)
|
|
||||||
println("Hash matches")
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package org.hydev.ios.alarmclock.data
|
|
||||||
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping
|
|
||||||
import org.springframework.web.bind.annotation.RestController
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: Write a description for this class!
|
|
||||||
*
|
|
||||||
* @author HyDEV Team (https://github.com/HyDevelop)
|
|
||||||
* @author Hykilpikonna (https://github.com/hykilpikonna)
|
|
||||||
* @author Vanilla (https://github.com/VergeDX)
|
|
||||||
* @since 2021-01-12 09:28
|
|
||||||
*/
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api")
|
|
||||||
class Test
|
|
||||||
{
|
|
||||||
@GetMapping("/echo")
|
|
||||||
fun echo(message: String?) = message
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
package org.hydev.ios.alarmclock.data
|
|
||||||
|
|
||||||
import org.hydev.ios.alarmclock.bad
|
|
||||||
import org.hydev.ios.alarmclock.passwordHash
|
|
||||||
import org.springframework.data.annotation.Id
|
|
||||||
import org.springframework.data.domain.Example
|
|
||||||
import org.springframework.data.domain.ExampleMatcher
|
|
||||||
import org.springframework.data.domain.ExampleMatcher.GenericPropertyMatchers.ignoreCase
|
|
||||||
import org.springframework.data.mongodb.core.index.Indexed
|
|
||||||
import org.springframework.data.mongodb.core.mapping.Document
|
|
||||||
import org.springframework.data.mongodb.repository.MongoRepository
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam
|
|
||||||
import org.springframework.web.bind.annotation.RestController
|
|
||||||
import javax.validation.constraints.Email
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The database model for an user
|
|
||||||
*
|
|
||||||
* @author HyDEV Team (https://github.com/HyDevelop)
|
|
||||||
* @author Hykilpikonna (https://github.com/hykilpikonna)
|
|
||||||
* @author Vanilla (https://github.com/VergeDX)
|
|
||||||
* @since 2021-01-09 10:48
|
|
||||||
*/
|
|
||||||
@Document(collation = "user")
|
|
||||||
data class User(
|
|
||||||
@Id
|
|
||||||
var id: Long = 0,
|
|
||||||
|
|
||||||
var name: String,
|
|
||||||
|
|
||||||
@Indexed(unique = true)
|
|
||||||
var email: String,
|
|
||||||
|
|
||||||
var passHash: String = "",
|
|
||||||
|
|
||||||
var passSalt: String = ""
|
|
||||||
)
|
|
||||||
{
|
|
||||||
constructor(name: String, email: String, pass: String) : this(name = name, email = email)
|
|
||||||
{
|
|
||||||
val (h, s) = pass.passwordHash()
|
|
||||||
passHash = h
|
|
||||||
passSalt = s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UserRepo: MongoRepository<User, String>
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/user")
|
|
||||||
class UserApi(val repo: UserRepo)
|
|
||||||
{
|
|
||||||
val em = ExampleMatcher.matching().withIgnorePaths("id", "passHash", "passSalt", "name").withMatcher("email", ignoreCase())
|
|
||||||
|
|
||||||
@GetMapping("/register")
|
|
||||||
fun register(@RequestParam name: String, @RequestParam pass: String, @RequestParam @Email email: String): Any
|
|
||||||
{
|
|
||||||
// Check name length
|
|
||||||
if (name.length !in 1..32) return bad("Name length not in range 1 to 32")
|
|
||||||
|
|
||||||
// Check if email exists
|
|
||||||
val user = User(name, email, pass)
|
|
||||||
if (repo.exists(Example.of(user, em))) return bad("Email is already registered")
|
|
||||||
|
|
||||||
// Check password strength
|
|
||||||
if (pass.length < 8) return bad("Password must be longer than 8 chars")
|
|
||||||
|
|
||||||
// Register
|
|
||||||
repo.save(user)
|
|
||||||
return user
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/delete")
|
|
||||||
fun delete(@RequestParam email: String, @RequestParam pass: String): Any
|
|
||||||
{
|
|
||||||
// Check if username exists
|
|
||||||
val users = repo.findAll(Example.of(User("", email, pass), em))
|
|
||||||
if (users.isEmpty()) return bad("User doesn't exist")
|
|
||||||
|
|
||||||
// Delete
|
|
||||||
users.forEach { repo.delete(it) }
|
|
||||||
return "Deleted"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
|
|
||||||
#spring.datasource.url=jdbc:mariadb://192.168.0.22:3306/clock
|
|
||||||
#spring.datasource.username=root
|
|
||||||
#spring.datasource.password=<insert password here>
|
|
||||||
#spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
|
|
||||||
#spring.jpa.hibernate.ddl-auto=update
|
|
||||||
|
|
||||||
spring.data.mongodb.uri=mongodb://hykilp:<insert-password-here>@192.168.0.22:27017/?authSource=admin&retryWrites=true
|
|
||||||
spring.data.mongodb.database=clock
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package org.hydev.ios.alarmclock
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest
|
|
||||||
|
|
||||||
@SpringBootTest
|
|
||||||
class AlarmClockApplicationTests
|
|
||||||
{
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun contextLoads()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -15,13 +15,15 @@
|
|||||||
4FD642DB25B4B7F60069171E /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FD642DA25B4B7F60069171E /* Utils.swift */; };
|
4FD642DB25B4B7F60069171E /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FD642DA25B4B7F60069171E /* Utils.swift */; };
|
||||||
4FD642E025B4D5F30069171E /* AlarmActivationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FD642DF25B4D5F30069171E /* AlarmActivationViewController.swift */; };
|
4FD642E025B4D5F30069171E /* AlarmActivationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FD642DF25B4D5F30069171E /* AlarmActivationViewController.swift */; };
|
||||||
4FF0683F25A5F18700304E6B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FF0683E25A5F18700304E6B /* AppDelegate.swift */; };
|
4FF0683F25A5F18700304E6B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FF0683E25A5F18700304E6B /* AppDelegate.swift */; };
|
||||||
4FF0684325A5F18700304E6B /* AccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FF0684225A5F18700304E6B /* AccountViewController.swift */; };
|
4FF0684325A5F18700304E6B /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FF0684225A5F18700304E6B /* Account.swift */; };
|
||||||
4FF0684625A5F18700304E6B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4FF0684425A5F18700304E6B /* Main.storyboard */; };
|
4FF0684625A5F18700304E6B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4FF0684425A5F18700304E6B /* Main.storyboard */; };
|
||||||
4FF0684825A5F18800304E6B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4FF0684725A5F18800304E6B /* Assets.xcassets */; };
|
4FF0684825A5F18800304E6B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4FF0684725A5F18800304E6B /* Assets.xcassets */; };
|
||||||
4FF0684B25A5F18800304E6B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4FF0684925A5F18800304E6B /* LaunchScreen.storyboard */; };
|
4FF0684B25A5F18800304E6B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4FF0684925A5F18800304E6B /* LaunchScreen.storyboard */; };
|
||||||
|
7C12BC7825BE25C000E5659C /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C12BC7725BE25C000E5659C /* Notification.swift */; };
|
||||||
7C5DAE9C25AF812200E44C52 /* clock.png in Resources */ = {isa = PBXBuildFile; fileRef = 7C5DAE9B25AF812200E44C52 /* clock.png */; };
|
7C5DAE9C25AF812200E44C52 /* clock.png in Resources */ = {isa = PBXBuildFile; fileRef = 7C5DAE9B25AF812200E44C52 /* clock.png */; };
|
||||||
7C83963625AF375B0027A94C /* NotificationLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C83963525AF375B0027A94C /* NotificationLogic.swift */; };
|
7C60741E25C11DC000B0A154 /* AlarmLogo1.png in Resources */ = {isa = PBXBuildFile; fileRef = 7C60741D25C11DC000B0A154 /* AlarmLogo1.png */; };
|
||||||
7C83963925AF68980027A94C /* TestingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C83963825AF68980027A94C /* TestingViewController.swift */; };
|
7C83963925AF68980027A94C /* DebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C83963825AF68980027A94C /* DebugViewController.swift */; };
|
||||||
|
7CD385A625BE4649007E9890 /* notif.caf in Resources */ = {isa = PBXBuildFile; fileRef = 7CD385A525BE4649007E9890 /* notif.caf */; };
|
||||||
C7E638E825B88F8B00799512 /* MathExpressions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7E638E725B88F8B00799512 /* MathExpressions.swift */; };
|
C7E638E825B88F8B00799512 /* MathExpressions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7E638E725B88F8B00799512 /* MathExpressions.swift */; };
|
||||||
F0DF7C0725BCD9FC0064A26B /* StopwatchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0DF7C0625BCD9FC0064A26B /* StopwatchViewController.swift */; };
|
F0DF7C0725BCD9FC0064A26B /* StopwatchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0DF7C0625BCD9FC0064A26B /* StopwatchViewController.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
@@ -34,18 +36,20 @@
|
|||||||
4FD642D225B48C380069171E /* AlarmActivator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmActivator.swift; sourceTree = "<group>"; };
|
4FD642D225B48C380069171E /* AlarmActivator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmActivator.swift; sourceTree = "<group>"; };
|
||||||
4FD642DA25B4B7F60069171E /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = "<group>"; };
|
4FD642DA25B4B7F60069171E /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = "<group>"; };
|
||||||
4FD642DF25B4D5F30069171E /* AlarmActivationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmActivationViewController.swift; sourceTree = "<group>"; };
|
4FD642DF25B4D5F30069171E /* AlarmActivationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmActivationViewController.swift; sourceTree = "<group>"; };
|
||||||
4FF0683B25A5F18700304E6B /* ProjectClock.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ProjectClock.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
4FF0683B25A5F18700304E6B /* GetGoing.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GetGoing.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
4FF0683E25A5F18700304E6B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
4FF0683E25A5F18700304E6B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
4FF0684225A5F18700304E6B /* AccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountViewController.swift; sourceTree = "<group>"; };
|
4FF0684225A5F18700304E6B /* Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = "<group>"; };
|
||||||
4FF0684525A5F18700304E6B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
4FF0684525A5F18700304E6B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||||
4FF0684725A5F18800304E6B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
4FF0684725A5F18800304E6B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
4FF0684A25A5F18800304E6B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
4FF0684A25A5F18800304E6B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
4FF0684C25A5F18800304E6B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
4FF0684C25A5F18800304E6B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
7C12BC7725BE25C000E5659C /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = "<group>"; };
|
||||||
7C5DAE9B25AF812200E44C52 /* clock.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = clock.png; sourceTree = "<group>"; };
|
7C5DAE9B25AF812200E44C52 /* clock.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = clock.png; sourceTree = "<group>"; };
|
||||||
|
7C60741D25C11DC000B0A154 /* AlarmLogo1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = AlarmLogo1.png; sourceTree = "<group>"; };
|
||||||
7C83962D25AF34F00027A94C /* ProjectClock.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ProjectClock.entitlements; sourceTree = "<group>"; };
|
7C83962D25AF34F00027A94C /* ProjectClock.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ProjectClock.entitlements; sourceTree = "<group>"; };
|
||||||
7C83962F25AF34F10027A94C /* HealthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HealthKit.framework; path = System/Library/Frameworks/HealthKit.framework; sourceTree = SDKROOT; };
|
7C83962F25AF34F10027A94C /* HealthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HealthKit.framework; path = System/Library/Frameworks/HealthKit.framework; sourceTree = SDKROOT; };
|
||||||
7C83963525AF375B0027A94C /* NotificationLogic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationLogic.swift; sourceTree = "<group>"; };
|
7C83963825AF68980027A94C /* DebugViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugViewController.swift; sourceTree = "<group>"; };
|
||||||
7C83963825AF68980027A94C /* TestingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestingViewController.swift; sourceTree = "<group>"; };
|
7CD385A525BE4649007E9890 /* notif.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = notif.caf; sourceTree = "<group>"; };
|
||||||
C7E638E725B88F8B00799512 /* MathExpressions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MathExpressions.swift; sourceTree = "<group>"; };
|
C7E638E725B88F8B00799512 /* MathExpressions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MathExpressions.swift; sourceTree = "<group>"; };
|
||||||
F0DF7C0625BCD9FC0064A26B /* StopwatchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopwatchViewController.swift; sourceTree = "<group>"; };
|
F0DF7C0625BCD9FC0064A26B /* StopwatchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopwatchViewController.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
@@ -73,7 +77,7 @@
|
|||||||
4FF0683C25A5F18700304E6B /* Products */ = {
|
4FF0683C25A5F18700304E6B /* Products */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4FF0683B25A5F18700304E6B /* ProjectClock.app */,
|
4FF0683B25A5F18700304E6B /* GetGoing.app */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -81,24 +85,26 @@
|
|||||||
4FF0683D25A5F18700304E6B /* ProjectClock */ = {
|
4FF0683D25A5F18700304E6B /* ProjectClock */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
7C5DAE9B25AF812200E44C52 /* clock.png */,
|
7CD385A425BE4639007E9890 /* Sounds */,
|
||||||
4FF0684C25A5F18800304E6B /* Info.plist */,
|
4FF0684225A5F18700304E6B /* Account.swift */,
|
||||||
7C83962D25AF34F00027A94C /* ProjectClock.entitlements */,
|
|
||||||
4FF0684925A5F18800304E6B /* LaunchScreen.storyboard */,
|
|
||||||
4FF0684725A5F18800304E6B /* Assets.xcassets */,
|
|
||||||
4FF0683E25A5F18700304E6B /* AppDelegate.swift */,
|
|
||||||
4FF0684225A5F18700304E6B /* AccountViewController.swift */,
|
|
||||||
4FA419AE25AF93EC004CE0FC /* AlarmViewController.swift */,
|
|
||||||
4F8A607025A9160400D88DC3 /* AddAlarmViewController.swift */,
|
4F8A607025A9160400D88DC3 /* AddAlarmViewController.swift */,
|
||||||
7C83963825AF68980027A94C /* TestingViewController.swift */,
|
|
||||||
4FD642DF25B4D5F30069171E /* AlarmActivationViewController.swift */,
|
4FD642DF25B4D5F30069171E /* AlarmActivationViewController.swift */,
|
||||||
4FD642D225B48C380069171E /* AlarmActivator.swift */,
|
4FD642D225B48C380069171E /* AlarmActivator.swift */,
|
||||||
|
4FA419AE25AF93EC004CE0FC /* AlarmViewController.swift */,
|
||||||
|
4FF0683E25A5F18700304E6B /* AppDelegate.swift */,
|
||||||
|
4FF0684725A5F18800304E6B /* Assets.xcassets */,
|
||||||
|
7C5DAE9B25AF812200E44C52 /* clock.png */,
|
||||||
|
7C60741D25C11DC000B0A154 /* AlarmLogo1.png */,
|
||||||
|
7C83963825AF68980027A94C /* DebugViewController.swift */,
|
||||||
|
4FF0684C25A5F18800304E6B /* Info.plist */,
|
||||||
|
4FF0684925A5F18800304E6B /* LaunchScreen.storyboard */,
|
||||||
4FF0684425A5F18700304E6B /* Main.storyboard */,
|
4FF0684425A5F18700304E6B /* Main.storyboard */,
|
||||||
F0DF7C0625BCD9FC0064A26B /* StopwatchViewController.swift */,
|
|
||||||
7C83963525AF375B0027A94C /* NotificationLogic.swift */,
|
|
||||||
4F98955125A9260400F51321 /* Net.swift */,
|
|
||||||
4F509BD125AE22D100726227 /* Models.swift */,
|
|
||||||
C7E638E725B88F8B00799512 /* MathExpressions.swift */,
|
C7E638E725B88F8B00799512 /* MathExpressions.swift */,
|
||||||
|
4F509BD125AE22D100726227 /* Models.swift */,
|
||||||
|
4F98955125A9260400F51321 /* Net.swift */,
|
||||||
|
7C12BC7725BE25C000E5659C /* Notification.swift */,
|
||||||
|
7C83962D25AF34F00027A94C /* ProjectClock.entitlements */,
|
||||||
|
F0DF7C0625BCD9FC0064A26B /* StopwatchViewController.swift */,
|
||||||
4FD642DA25B4B7F60069171E /* Utils.swift */,
|
4FD642DA25B4B7F60069171E /* Utils.swift */,
|
||||||
);
|
);
|
||||||
path = ProjectClock;
|
path = ProjectClock;
|
||||||
@@ -112,12 +118,20 @@
|
|||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
7CD385A425BE4639007E9890 /* Sounds */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
7CD385A525BE4649007E9890 /* notif.caf */,
|
||||||
|
);
|
||||||
|
path = Sounds;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
4FF0683A25A5F18700304E6B /* ProjectClock */ = {
|
4FF0683A25A5F18700304E6B /* GetGoing */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 4FF0684F25A5F18800304E6B /* Build configuration list for PBXNativeTarget "ProjectClock" */;
|
buildConfigurationList = 4FF0684F25A5F18800304E6B /* Build configuration list for PBXNativeTarget "GetGoing" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
4FF0683725A5F18700304E6B /* Sources */,
|
4FF0683725A5F18700304E6B /* Sources */,
|
||||||
4FF0683825A5F18700304E6B /* Frameworks */,
|
4FF0683825A5F18700304E6B /* Frameworks */,
|
||||||
@@ -127,9 +141,9 @@
|
|||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
);
|
);
|
||||||
name = ProjectClock;
|
name = GetGoing;
|
||||||
productName = ProjectClock;
|
productName = ProjectClock;
|
||||||
productReference = 4FF0683B25A5F18700304E6B /* ProjectClock.app */;
|
productReference = 4FF0683B25A5F18700304E6B /* GetGoing.app */;
|
||||||
productType = "com.apple.product-type.application";
|
productType = "com.apple.product-type.application";
|
||||||
};
|
};
|
||||||
/* End PBXNativeTarget section */
|
/* End PBXNativeTarget section */
|
||||||
@@ -149,7 +163,7 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
buildConfigurationList = 4FF0683625A5F18700304E6B /* Build configuration list for PBXProject "ProjectClock" */;
|
buildConfigurationList = 4FF0683625A5F18700304E6B /* Build configuration list for PBXProject "GetGoing" */;
|
||||||
compatibilityVersion = "Xcode 9.3";
|
compatibilityVersion = "Xcode 9.3";
|
||||||
developmentRegion = en;
|
developmentRegion = en;
|
||||||
hasScannedForEncodings = 0;
|
hasScannedForEncodings = 0;
|
||||||
@@ -162,7 +176,7 @@
|
|||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
targets = (
|
targets = (
|
||||||
4FF0683A25A5F18700304E6B /* ProjectClock */,
|
4FF0683A25A5F18700304E6B /* GetGoing */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
@@ -172,10 +186,12 @@
|
|||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
7C60741E25C11DC000B0A154 /* AlarmLogo1.png in Resources */,
|
||||||
4FF0684B25A5F18800304E6B /* LaunchScreen.storyboard in Resources */,
|
4FF0684B25A5F18800304E6B /* LaunchScreen.storyboard in Resources */,
|
||||||
7C5DAE9C25AF812200E44C52 /* clock.png in Resources */,
|
7C5DAE9C25AF812200E44C52 /* clock.png in Resources */,
|
||||||
4FF0684825A5F18800304E6B /* Assets.xcassets in Resources */,
|
4FF0684825A5F18800304E6B /* Assets.xcassets in Resources */,
|
||||||
4FF0684625A5F18700304E6B /* Main.storyboard in Resources */,
|
4FF0684625A5F18700304E6B /* Main.storyboard in Resources */,
|
||||||
|
7CD385A625BE4649007E9890 /* notif.caf in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -188,16 +204,16 @@
|
|||||||
files = (
|
files = (
|
||||||
4F8A607125A9160400D88DC3 /* AddAlarmViewController.swift in Sources */,
|
4F8A607125A9160400D88DC3 /* AddAlarmViewController.swift in Sources */,
|
||||||
4F98955225A9260400F51321 /* Net.swift in Sources */,
|
4F98955225A9260400F51321 /* Net.swift in Sources */,
|
||||||
7C83963925AF68980027A94C /* TestingViewController.swift in Sources */,
|
7C83963925AF68980027A94C /* DebugViewController.swift in Sources */,
|
||||||
4FF0684325A5F18700304E6B /* AccountViewController.swift in Sources */,
|
4FF0684325A5F18700304E6B /* Account.swift in Sources */,
|
||||||
F0DF7C0725BCD9FC0064A26B /* StopwatchViewController.swift in Sources */,
|
F0DF7C0725BCD9FC0064A26B /* StopwatchViewController.swift in Sources */,
|
||||||
4FF0683F25A5F18700304E6B /* AppDelegate.swift in Sources */,
|
4FF0683F25A5F18700304E6B /* AppDelegate.swift in Sources */,
|
||||||
4FD642D325B48C380069171E /* AlarmActivator.swift in Sources */,
|
4FD642D325B48C380069171E /* AlarmActivator.swift in Sources */,
|
||||||
4FD642DB25B4B7F60069171E /* Utils.swift in Sources */,
|
4FD642DB25B4B7F60069171E /* Utils.swift in Sources */,
|
||||||
4FA419AF25AF93EC004CE0FC /* AlarmViewController.swift in Sources */,
|
4FA419AF25AF93EC004CE0FC /* AlarmViewController.swift in Sources */,
|
||||||
4F509BD225AE22D100726227 /* Models.swift in Sources */,
|
4F509BD225AE22D100726227 /* Models.swift in Sources */,
|
||||||
|
7C12BC7825BE25C000E5659C /* Notification.swift in Sources */,
|
||||||
C7E638E825B88F8B00799512 /* MathExpressions.swift in Sources */,
|
C7E638E825B88F8B00799512 /* MathExpressions.swift in Sources */,
|
||||||
7C83963625AF375B0027A94C /* NotificationLogic.swift in Sources */,
|
|
||||||
4FD642E025B4D5F30069171E /* AlarmActivationViewController.swift in Sources */,
|
4FD642E025B4D5F30069171E /* AlarmActivationViewController.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -353,7 +369,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.hydev.ProjectClock;
|
PRODUCT_BUNDLE_IDENTIFIER = org.hydev.GetGoing;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
@@ -373,7 +389,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.hydev.ProjectClock;
|
PRODUCT_BUNDLE_IDENTIFIER = org.hydev.GetGoing;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
@@ -383,7 +399,7 @@
|
|||||||
/* End XCBuildConfiguration section */
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
/* Begin XCConfigurationList section */
|
||||||
4FF0683625A5F18700304E6B /* Build configuration list for PBXProject "ProjectClock" */ = {
|
4FF0683625A5F18700304E6B /* Build configuration list for PBXProject "GetGoing" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
4FF0684D25A5F18800304E6B /* Debug */,
|
4FF0684D25A5F18800304E6B /* Debug */,
|
||||||
@@ -392,7 +408,7 @@
|
|||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
4FF0684F25A5F18800304E6B /* Build configuration list for PBXNativeTarget "ProjectClock" */ = {
|
4FF0684F25A5F18800304E6B /* Build configuration list for PBXNativeTarget "GetGoing" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
4FF0685025A5F18800304E6B /* Debug */,
|
4FF0685025A5F18800304E6B /* Debug */,
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1230"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "4FF0683A25A5F18700304E6B"
|
||||||
|
BuildableName = "GetGoing.app"
|
||||||
|
BlueprintName = "GetGoing"
|
||||||
|
ReferencedContainer = "container:GetGoing.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<Testables>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "4FF0683A25A5F18700304E6B"
|
||||||
|
BuildableName = "GetGoing.app"
|
||||||
|
BlueprintName = "GetGoing"
|
||||||
|
ReferencedContainer = "container:GetGoing.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "4FF0683A25A5F18700304E6B"
|
||||||
|
BuildableName = "GetGoing.app"
|
||||||
|
BlueprintName = "GetGoing"
|
||||||
|
ReferencedContainer = "container:GetGoing.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 Hykilpikonna
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>SchemeUserState</key>
|
|
||||||
<dict>
|
|
||||||
<key>ProjectClock.xcscheme_^#shared#^_</key>
|
|
||||||
<dict>
|
|
||||||
<key>orderHint</key>
|
|
||||||
<integer>0</integer>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -0,0 +1,542 @@
|
|||||||
|
//
|
||||||
|
// ViewController.swift
|
||||||
|
// ProjectClock
|
||||||
|
//
|
||||||
|
// Created by Hykilpikonna on 1/6/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
/**
|
||||||
|
Account view controller controlling the two separate view controllers
|
||||||
|
*/
|
||||||
|
class AccountViewController: UIViewController
|
||||||
|
{
|
||||||
|
@IBOutlet var vLogin: UIView!
|
||||||
|
@IBOutlet var vManage: UIView!
|
||||||
|
|
||||||
|
// For instance references
|
||||||
|
static var this: AccountViewController!
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when the user switch to this tab
|
||||||
|
*/
|
||||||
|
override func viewDidLoad()
|
||||||
|
{
|
||||||
|
// Static instance reference
|
||||||
|
AccountViewController.this = self
|
||||||
|
|
||||||
|
// Check if already registered/logged in
|
||||||
|
if localStorage.string(forKey: "id") != nil { login() }
|
||||||
|
|
||||||
|
super.viewDidLoad()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Login from the account page
|
||||||
|
*/
|
||||||
|
func login()
|
||||||
|
{
|
||||||
|
vLogin.hide()
|
||||||
|
vManage.show()
|
||||||
|
ManageVC.this.display()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Logout
|
||||||
|
*/
|
||||||
|
func logout()
|
||||||
|
{
|
||||||
|
// Remove login info
|
||||||
|
["id", "user", "pass", "family"].forEach { localStorage.removeObject(forKey: $0) }
|
||||||
|
|
||||||
|
// Switch UI
|
||||||
|
vLogin.show()
|
||||||
|
vManage.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
View controller for registration and login
|
||||||
|
*/
|
||||||
|
class LoginVC: EndEditingOnReturn
|
||||||
|
{
|
||||||
|
@IBOutlet weak var username: UITextField!
|
||||||
|
@IBOutlet weak var password: UITextField!
|
||||||
|
|
||||||
|
override func viewDidLoad()
|
||||||
|
{
|
||||||
|
username.delegate = self
|
||||||
|
password.delegate = self
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Send user login/registration request
|
||||||
|
|
||||||
|
- Parameter login: True: Login, False: Register
|
||||||
|
*/
|
||||||
|
func userRequest(login: Bool)
|
||||||
|
{
|
||||||
|
// Verify username and password
|
||||||
|
guard let name = username.text, name ~= "[A-Za-z0-9_-]{3,16}" else
|
||||||
|
{
|
||||||
|
msg("Username Invalid", "Username must be 3 to 16 characters long, and must only contain a-z, 0-9, underscore, and minus signs (-).")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let pass = password.text, pass.count >= 8 else
|
||||||
|
{
|
||||||
|
msg("Password Invalid", "Password must be more than or equal to 8 characters long")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error messages
|
||||||
|
let errors = ["409 - [\"A0111\"]": "Account already exists, please login instead.",
|
||||||
|
"401 -": "Incorrect username/password",
|
||||||
|
"404 -": "Username does not exist in the database",
|
||||||
|
"406 - [\"A0101\"]": "Username invalid."
|
||||||
|
]
|
||||||
|
|
||||||
|
// Send register request
|
||||||
|
sendReq(login ? APIs.login : APIs.register,
|
||||||
|
title: login ? "Logging in..." : "Registering...", errors: errors,
|
||||||
|
params: ["username": name, "password": pass.sha256])
|
||||||
|
{
|
||||||
|
// Store username and password
|
||||||
|
localStorage["name"] = name
|
||||||
|
localStorage["pass"] = pass.sha256
|
||||||
|
localStorage["id"] = $0
|
||||||
|
|
||||||
|
send(APIs.familyGet)
|
||||||
|
{
|
||||||
|
$0.localSave()
|
||||||
|
self.loginSuccess(login)
|
||||||
|
}
|
||||||
|
err: { it in print(it); self.loginSuccess(login) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loginSuccess(_ login: Bool)
|
||||||
|
{
|
||||||
|
ui
|
||||||
|
{
|
||||||
|
// Send feedback
|
||||||
|
if login { self.msg("Login success!", "Now you can use account features, yay!") }
|
||||||
|
else { self.msg("Registration success!", "Now you have an account, yay!") }
|
||||||
|
|
||||||
|
// Hide registration and show account detail view
|
||||||
|
AccountViewController.this.login()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when the user clicks register
|
||||||
|
*/
|
||||||
|
@IBAction func register(_ sender: Any)
|
||||||
|
{
|
||||||
|
self.view.endEditing(true)
|
||||||
|
userRequest(login: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when the user clicks login
|
||||||
|
*/
|
||||||
|
@IBAction func login(_ sender: Any)
|
||||||
|
{
|
||||||
|
self.view.endEditing(true)
|
||||||
|
userRequest(login: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Account manage view controller
|
||||||
|
*/
|
||||||
|
class ManageVC: UIViewController
|
||||||
|
{
|
||||||
|
static var this: ManageVC!
|
||||||
|
|
||||||
|
@IBOutlet weak var lUsername: UILabel!
|
||||||
|
@IBOutlet weak var lJoinDate: UILabel!
|
||||||
|
|
||||||
|
@IBOutlet weak var lCurrentFamily: UILabel!
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when the user switched to the account tab (whether the view container is hidden or not)
|
||||||
|
*/
|
||||||
|
override func viewDidLoad()
|
||||||
|
{
|
||||||
|
// Static reference
|
||||||
|
ManageVC.this = self
|
||||||
|
super.viewDidLoad()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Display account info
|
||||||
|
*/
|
||||||
|
func display()
|
||||||
|
{
|
||||||
|
lUsername.text = localStorage.string(forKey: "name")
|
||||||
|
// TODO: Implement join date (not important)
|
||||||
|
lJoinDate.text = localStorage.string(forKey: "id")
|
||||||
|
|
||||||
|
// Display family name
|
||||||
|
if let family = Family.fromLocal()
|
||||||
|
{
|
||||||
|
lCurrentFamily.text = family.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when the user clicks the upload backup button
|
||||||
|
*/
|
||||||
|
@IBAction func uploadBackup(_ sender: Any)
|
||||||
|
{
|
||||||
|
sendReq(APIs.uploadConfig, title: "Uploading...", params: ["config": localStorage.string(forKey: "alarms")!])
|
||||||
|
{ it in self.msg("Success!", "You're backed up.") }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when the user clicks the download backup button
|
||||||
|
*/
|
||||||
|
@IBAction func downloadBackup(_ sender: Any)
|
||||||
|
{
|
||||||
|
sendReq(APIs.downloadConfig, title: "Downloading...")
|
||||||
|
{
|
||||||
|
// Make sure backup exists and is parseable
|
||||||
|
if $0.isEmpty { self.msg("Nope", "No backups found"); return }
|
||||||
|
guard JSON.parse([Alarm].self, $0) != nil else { self.msg("Failed", "Backup restoration failed."); return }
|
||||||
|
|
||||||
|
// Save backup
|
||||||
|
localStorage.setValue($0, forKey: "alarms")
|
||||||
|
|
||||||
|
// Update UI
|
||||||
|
AlarmViewController.staticTable?.reloadData()
|
||||||
|
|
||||||
|
// Schedule any missing notifications
|
||||||
|
Notification.bulkScheduleNotifications(alarms: Alarms.fromLocal().list)
|
||||||
|
|
||||||
|
self.msg("Success!", "You're restored your backup.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when the user clicks the logout button
|
||||||
|
*/
|
||||||
|
@IBAction func logout(_ sender: Any)
|
||||||
|
{
|
||||||
|
AccountViewController.this.logout()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when the user clicks the delete account button
|
||||||
|
*/
|
||||||
|
@IBAction func deleteAccount(_ sender: Any)
|
||||||
|
{
|
||||||
|
enterPin("Are you sure?", "Enter 1234 to continue deleting your account, you can't undo this.")
|
||||||
|
{
|
||||||
|
guard $0 == "1234" else { return }
|
||||||
|
|
||||||
|
self.sendReq(APIs.delete, title: "Deleting...")
|
||||||
|
{
|
||||||
|
print("Deleted! \($0)")
|
||||||
|
self.msg("Deleted!", "You are erased from our database, you no longer exist.")
|
||||||
|
self.logout(sender)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Family view controller that displays family info or create/join family buttons
|
||||||
|
*/
|
||||||
|
class FamilyVC: UIViewController
|
||||||
|
{
|
||||||
|
static var this: FamilyVC!
|
||||||
|
|
||||||
|
// No family view - prompt to create/join a family
|
||||||
|
@IBOutlet weak var noFamilyView: UIView!
|
||||||
|
var createMode: Bool!
|
||||||
|
|
||||||
|
@IBAction func btnCreate(_ sender: Any)
|
||||||
|
{
|
||||||
|
createMode = true
|
||||||
|
performSegue(withIdentifier: "family-create-join", sender: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func btnJoin(_ sender: Any)
|
||||||
|
{
|
||||||
|
createMode = false
|
||||||
|
performSegue(withIdentifier: "family-create-join", sender: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBSegueAction func segueCreateJoin(_ coder: NSCoder) -> FamilyCreateJoinVC?
|
||||||
|
{
|
||||||
|
return FamilyCreateJoinVC(coder: coder, create: createMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Family view - Display family information and controls
|
||||||
|
@IBOutlet weak var familyView: UIView!
|
||||||
|
@IBOutlet weak var table: UITableView!
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when information is updated
|
||||||
|
*/
|
||||||
|
override func viewDidLoad()
|
||||||
|
{
|
||||||
|
FamilyVC.this = self
|
||||||
|
|
||||||
|
if let _ = Family.fromLocal()
|
||||||
|
{
|
||||||
|
// Family exists
|
||||||
|
noFamilyView.hide()
|
||||||
|
familyView.show()
|
||||||
|
|
||||||
|
table.dataSource = self
|
||||||
|
table.delegate = self
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Family doesn't exist
|
||||||
|
noFamilyView.show()
|
||||||
|
familyView.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when the user clicks the refresh button
|
||||||
|
*/
|
||||||
|
@IBAction func btnRefresh(_ sender: Any)
|
||||||
|
{
|
||||||
|
sendReq(APIs.familyGet, title: "Updating family...")
|
||||||
|
{
|
||||||
|
$0.localSave()
|
||||||
|
self.table.reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when the user clicks the change pin button
|
||||||
|
*/
|
||||||
|
@IBAction func btnChangePin(_ sender: Any)
|
||||||
|
{
|
||||||
|
self.enterPin("Change Pin", "Enter your OLD pin:") { oldPin in
|
||||||
|
|
||||||
|
self.enterPin("Change Pin", "Enter your NEW pin:") { newPin in
|
||||||
|
|
||||||
|
guard newPin.count >= 4 else { self.msg("Pin Too Weak", "Your family pin must be 4 numbers or more."); return }
|
||||||
|
|
||||||
|
self.sendReq(APIs.familyChangePin, title: "Updating Pin...", params: ["oldPin": oldPin, "newPin": newPin]) { it in
|
||||||
|
|
||||||
|
self.msg("Update Success!", "Your family pin is updated.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when the user clicks the leave or delete family button
|
||||||
|
*/
|
||||||
|
@IBAction func btnLeave(_ sender: UIButton)
|
||||||
|
{
|
||||||
|
let i = sender.tag
|
||||||
|
let action = ["Leave", "Delete"][i]
|
||||||
|
let title = ["Leaving...", "Deleting..."][i]
|
||||||
|
let msg = ["You left the family.", "You deleted the family."][i]
|
||||||
|
|
||||||
|
enterPin()
|
||||||
|
{
|
||||||
|
self.sendReq(APIs.familyAction, title: title, params: ["pin": $0, "action": action]) { it in
|
||||||
|
|
||||||
|
// Leave or delete, clear local storage's family section
|
||||||
|
if i == 0 || i == 1 { localStorage.removeObject(forKey: "family") }
|
||||||
|
|
||||||
|
self.msg("\(action) Success!", msg)
|
||||||
|
{
|
||||||
|
self.viewDidLoad()
|
||||||
|
AccountViewController.this.login()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Table data source
|
||||||
|
*/
|
||||||
|
extension FamilyVC: UITableViewDelegate, UITableViewDataSource
|
||||||
|
{
|
||||||
|
static var selectedUser: String = ""
|
||||||
|
|
||||||
|
/**
|
||||||
|
Define row count
|
||||||
|
*/
|
||||||
|
func tableView(_ _: UITableView, numberOfRowsInSection _: Int) -> Int
|
||||||
|
{
|
||||||
|
guard let family = Family.fromLocal() else { return 0 }
|
||||||
|
return family.membersList.count
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Set cell at i
|
||||||
|
*/
|
||||||
|
func tableView(_ view: UITableView, cellForRowAt i: IndexPath) -> UITableViewCell
|
||||||
|
{
|
||||||
|
let cell = view.dequeueReusableCell(withIdentifier: "family-member-cell", for: i)
|
||||||
|
cell.textLabel?.text = Family.fromLocal()!.membersList[i.row]
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when the user clicks on the cell at i
|
||||||
|
*/
|
||||||
|
func tableView(_ view: UITableView, didSelectRowAt i: IndexPath)
|
||||||
|
{
|
||||||
|
FamilyVC.selectedUser = Family.fromLocal()!.membersList[i.row]
|
||||||
|
view.deselectRow(at: i, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create or join a family
|
||||||
|
*/
|
||||||
|
class FamilyCreateJoinVC: EndEditingOnReturn
|
||||||
|
{
|
||||||
|
let createMode: Bool
|
||||||
|
@IBOutlet weak var lFamilyNameOrId: UILabel!
|
||||||
|
@IBOutlet weak var bCreateJoin: UIButton!
|
||||||
|
@IBOutlet weak var tNameOrId: UITextField!
|
||||||
|
@IBOutlet weak var tPin: UITextField!
|
||||||
|
|
||||||
|
/**
|
||||||
|
Pass in create mode from FamilyVC
|
||||||
|
*/
|
||||||
|
init?(coder: NSCoder, create: Bool)
|
||||||
|
{
|
||||||
|
createMode = create
|
||||||
|
super.init(coder: coder)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||||
|
|
||||||
|
/**
|
||||||
|
On load
|
||||||
|
*/
|
||||||
|
override func viewDidLoad()
|
||||||
|
{
|
||||||
|
// Set UI according to createMode
|
||||||
|
lFamilyNameOrId.text = createMode ? "Family Name" : "Family ID"
|
||||||
|
bCreateJoin.setTitle(createMode ? "Create" : "Join", for: .normal)
|
||||||
|
tNameOrId.keyboardType = createMode ? .default : .numberPad
|
||||||
|
|
||||||
|
// Default name
|
||||||
|
if createMode
|
||||||
|
{
|
||||||
|
tNameOrId.text = "\(localStorage.string(forKey: "name")!)'s Family"
|
||||||
|
}
|
||||||
|
|
||||||
|
// End editing on return
|
||||||
|
tNameOrId.delegate = self
|
||||||
|
tPin.delegate = self
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called after successfully joining or creating a family.
|
||||||
|
*/
|
||||||
|
func afterFamilyChange()
|
||||||
|
{
|
||||||
|
self.dismiss()
|
||||||
|
FamilyVC.this.viewDidLoad()
|
||||||
|
AccountViewController.this.login()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when the user clicks create or join button
|
||||||
|
*/
|
||||||
|
@IBAction func btnCreateOrJoin(_ sender: Any)
|
||||||
|
{
|
||||||
|
// Check pin
|
||||||
|
guard let pin = tPin.text, pin.count >= 4 else { msg("Pin Too Weak", "Your family pin must be 4 numbers or more."); return }
|
||||||
|
|
||||||
|
if createMode
|
||||||
|
{
|
||||||
|
guard let name = tNameOrId.text, !name.isEmpty else { msg("Name Empty", "You must enter a family name"); return }
|
||||||
|
|
||||||
|
// Create family
|
||||||
|
sendReq(APIs.familyCreate, title: "Creating...", params: ["name": name, "pin": pin])
|
||||||
|
{
|
||||||
|
// Save
|
||||||
|
$0.localSave()
|
||||||
|
|
||||||
|
// Send success message
|
||||||
|
self.msg("Created!", "Your family ID is \($0.fid)") { self.afterFamilyChange() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
guard let idString = tNameOrId.text, !idString.isEmpty, let id = Int(idString) else
|
||||||
|
{ msg("ID Incorrect", "Please make sure your ID is an positive integer."); return }
|
||||||
|
|
||||||
|
// Join family
|
||||||
|
sendReq(APIs.familyAction, title: "Joining...", params: ["fid": String(id), "pin": pin, "action": "join"])
|
||||||
|
{
|
||||||
|
// Save
|
||||||
|
$0.localSave()
|
||||||
|
|
||||||
|
// Send success message
|
||||||
|
self.msg("Joined!", "Your family ID is \($0.fid)") { self.afterFamilyChange() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
View controller for adding an alarm to a fmaily member
|
||||||
|
*/
|
||||||
|
class FamilyAddAlarmVC: UIViewController, UITableViewDelegate, UITableViewDataSource
|
||||||
|
{
|
||||||
|
@IBOutlet weak var table: UITableView!
|
||||||
|
|
||||||
|
override func viewDidLoad()
|
||||||
|
{
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
table.delegate = self
|
||||||
|
table.dataSource = self
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Define row count
|
||||||
|
*/
|
||||||
|
func tableView(_ _: UITableView, numberOfRowsInSection _: Int) -> Int
|
||||||
|
{
|
||||||
|
return Alarms.fromLocal().list.count
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Set cell at i
|
||||||
|
*/
|
||||||
|
func tableView(_ view: UITableView, cellForRowAt i: IndexPath) -> UITableViewCell
|
||||||
|
{
|
||||||
|
let cell = view.dequeueReusableCell(withIdentifier: "family-alarm-cell", for: i)
|
||||||
|
cell.textLabel?.text = Alarms.fromLocal().list[i.row].timeText
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when the user clicks on the cell at i
|
||||||
|
*/
|
||||||
|
func tableView(_ view: UITableView, didSelectRowAt i: IndexPath)
|
||||||
|
{
|
||||||
|
view.deselectRow(at: i, animated: true)
|
||||||
|
enterPin()
|
||||||
|
{
|
||||||
|
self.sendReq(APIs.familyAddAlarm, title: "Adding...", params: ["to": FamilyVC.selectedUser, "pin": $0, "alarm": JSON.stringify(Alarms.fromLocal().list[i.row])!])
|
||||||
|
{
|
||||||
|
print($0)
|
||||||
|
self.msg("Added!", "The member will receive the alarm after opening the app.")
|
||||||
|
{
|
||||||
|
self.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
//
|
|
||||||
// ViewController.swift
|
|
||||||
// ProjectClock
|
|
||||||
//
|
|
||||||
// Created by Hykilpikonna on 1/6/21.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
/**
|
|
||||||
Account view controller controlling the two separate view controllers
|
|
||||||
*/
|
|
||||||
class AccountViewController: UIViewController
|
|
||||||
{
|
|
||||||
@IBOutlet var vLogin: UIView!
|
|
||||||
@IBOutlet var vManage: UIView!
|
|
||||||
|
|
||||||
// For instance references
|
|
||||||
static var this: AccountViewController!
|
|
||||||
|
|
||||||
/**
|
|
||||||
Called when the user switch to this tab
|
|
||||||
*/
|
|
||||||
override func viewDidLoad()
|
|
||||||
{
|
|
||||||
// Static instance reference
|
|
||||||
AccountViewController.this = self
|
|
||||||
|
|
||||||
// Check if already registered/logged in
|
|
||||||
if localStorage.string(forKey: "id") != nil { login() }
|
|
||||||
|
|
||||||
super.viewDidLoad()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Login from the account page
|
|
||||||
*/
|
|
||||||
func login()
|
|
||||||
{
|
|
||||||
vLogin.isHidden = true
|
|
||||||
vManage.isHidden = false
|
|
||||||
ManageVC.this.display()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Logout
|
|
||||||
*/
|
|
||||||
func logout()
|
|
||||||
{
|
|
||||||
// Remove login info
|
|
||||||
["id", "user", "pass"].forEach { localStorage.removeObject(forKey: $0) }
|
|
||||||
|
|
||||||
// Switch UI
|
|
||||||
vLogin.isHidden = false
|
|
||||||
vManage.isHidden = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
View controller for registration and login
|
|
||||||
*/
|
|
||||||
class LoginVC: UIViewController
|
|
||||||
{
|
|
||||||
@IBOutlet weak var username: UITextField!
|
|
||||||
@IBOutlet weak var password: UITextField!
|
|
||||||
|
|
||||||
/**
|
|
||||||
Send user login/registration request
|
|
||||||
|
|
||||||
- Parameter login: True: Login, False: Register
|
|
||||||
*/
|
|
||||||
func userRequest(login: Bool)
|
|
||||||
{
|
|
||||||
// Verify username and password
|
|
||||||
guard let name = username.text, name ~= "[A-Za-z0-9_-]{3,16}" else
|
|
||||||
{
|
|
||||||
msg("Username Invalid", "Username must be 3 to 16 characters long, and must only contain a-z, 0-9, underscore, and minus signs (-).")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let pass = password.text, pass.count >= 8 else
|
|
||||||
{
|
|
||||||
msg("Password Invalid", "Password must be more than or equal to 8 characters long")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error messages
|
|
||||||
let errors = ["409 - [\"A0111\"]": "Account already exists, please login instead.",
|
|
||||||
"401 -": "Incorrect username/password",
|
|
||||||
"404 -": "Username does not exist in the database",
|
|
||||||
"406 - [\"A0101\"]": "Username invalid."
|
|
||||||
]
|
|
||||||
|
|
||||||
// Send register request
|
|
||||||
sendReq(login ? APIs.login : APIs.register,
|
|
||||||
title: login ? "Logging in..." : "Registering...", errors: errors,
|
|
||||||
params: ["username": name, "password": pass.sha256])
|
|
||||||
{
|
|
||||||
// Store username and password
|
|
||||||
localStorage["name"] = name
|
|
||||||
localStorage["pass"] = pass.sha256
|
|
||||||
localStorage["id"] = $0
|
|
||||||
|
|
||||||
// Send feedback
|
|
||||||
if login { self.msg("Login success!", "Now you can use account features, yay!") }
|
|
||||||
else { self.msg("Registration success!", "Now you have an account, yay!") }
|
|
||||||
|
|
||||||
// Hide registration and show account detail view
|
|
||||||
AccountViewController.this.login()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Called when the user clicks register
|
|
||||||
*/
|
|
||||||
@IBAction func register(_ sender: Any)
|
|
||||||
{
|
|
||||||
userRequest(login: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Called when the user clicks login
|
|
||||||
*/
|
|
||||||
@IBAction func login(_ sender: Any)
|
|
||||||
{
|
|
||||||
userRequest(login: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Account manage view controller
|
|
||||||
*/
|
|
||||||
class ManageVC: UIViewController
|
|
||||||
{
|
|
||||||
static var this: ManageVC!
|
|
||||||
|
|
||||||
@IBOutlet weak var lUsername: UILabel!
|
|
||||||
@IBOutlet weak var lJoinDate: UILabel!
|
|
||||||
|
|
||||||
/**
|
|
||||||
Called when the user switched to the account tab (whether the view container is hidden or not)
|
|
||||||
*/
|
|
||||||
override func viewDidLoad()
|
|
||||||
{
|
|
||||||
// Static reference
|
|
||||||
ManageVC.this = self
|
|
||||||
super.viewDidLoad()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Display account info
|
|
||||||
*/
|
|
||||||
func display()
|
|
||||||
{
|
|
||||||
lUsername.text = localStorage.string(forKey: "name")
|
|
||||||
// TODO: Correct join date
|
|
||||||
lJoinDate.text = localStorage.string(forKey: "id")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Called when the user clicks the logout button
|
|
||||||
*/
|
|
||||||
@IBAction func logout(_ sender: Any)
|
|
||||||
{
|
|
||||||
AccountViewController.this.logout()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Called when the user clicks the delete account button
|
|
||||||
*/
|
|
||||||
@IBAction func deleteAccount(_ sender: Any)
|
|
||||||
{
|
|
||||||
sendReq(APIs.delete, title: "Deleting...")
|
|
||||||
{
|
|
||||||
print("Deleted! \($0)")
|
|
||||||
self.msg("Deleted!", "You are erased from our database, you no longer exist.")
|
|
||||||
self.logout(sender)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,8 +7,42 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class AddAlarmViewController: UIViewController
|
class AddAlarmViewController: EndEditingOnReturn
|
||||||
{
|
{
|
||||||
|
// Editing variables
|
||||||
|
var alarmCell: AlarmTableCell? = nil
|
||||||
|
var editMode: Bool { alarmCell != nil }
|
||||||
|
|
||||||
|
override func viewDidLoad()
|
||||||
|
{
|
||||||
|
// End edit on return
|
||||||
|
alarmNameTextField.delegate = self
|
||||||
|
|
||||||
|
// Load alarm to edit if in edit mode
|
||||||
|
if let alarmCell = alarmCell, let alarm = alarmCell.alarm
|
||||||
|
{
|
||||||
|
// Toggle editing mode
|
||||||
|
viewTitle.text = "Edit Alarm"
|
||||||
|
|
||||||
|
// Set all the original values to be edited
|
||||||
|
let (y,m,d) = Date().getYMD()
|
||||||
|
timePicker.date = Date.create(y, m, d, alarm.hour, alarm.minute)
|
||||||
|
|
||||||
|
// Toggle proper repeats
|
||||||
|
repeatWeekdaysSwitch.isOn = alarm.repeats[1...5].allSatisfy { $0 }
|
||||||
|
repeatWeekendsSwitch.isOn = alarm.repeats[0] && alarm.repeats[6]
|
||||||
|
|
||||||
|
alarmNameTextField.text = alarm.text
|
||||||
|
updateETA()
|
||||||
|
|
||||||
|
// Sets the WVM
|
||||||
|
wvmPicker.selectRow(alarm.wakeMethod.index, inComponent: 0, animated: true)
|
||||||
|
|
||||||
|
// Sets alarm tone
|
||||||
|
ringtonePicker.selectRow(ringtones.firstIndex { $0.tone == alarm.alarmTone }!, inComponent: 0, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// UI: Make scroll view scrollable
|
// UI: Make scroll view scrollable
|
||||||
@IBOutlet weak var scrollView: UIScrollView!
|
@IBOutlet weak var scrollView: UIScrollView!
|
||||||
@IBOutlet weak var scrollViewInner: UIView!
|
@IBOutlet weak var scrollViewInner: UIView!
|
||||||
@@ -21,26 +55,46 @@ class AddAlarmViewController: UIViewController
|
|||||||
// Pickers
|
// Pickers
|
||||||
@IBOutlet weak var timePicker: UIDatePicker!
|
@IBOutlet weak var timePicker: UIDatePicker!
|
||||||
@IBOutlet weak var wvmPicker: UIPickerView!
|
@IBOutlet weak var wvmPicker: UIPickerView!
|
||||||
|
@IBOutlet weak var ringtonePicker: UIPickerView!
|
||||||
|
|
||||||
// UI Elements
|
// UI Elements
|
||||||
@IBOutlet weak var repeatWeekdaysSwitch: UISwitch!
|
@IBOutlet weak var repeatWeekdaysSwitch: UISwitch!
|
||||||
@IBOutlet weak var repeatWeekendsSwitch: UISwitch!
|
@IBOutlet weak var repeatWeekendsSwitch: UISwitch!
|
||||||
@IBOutlet weak var alarmNameTextField: UITextField!
|
@IBOutlet weak var alarmNameTextField: UITextField!
|
||||||
|
@IBOutlet weak var timeTillAlarmLabel: UILabel!
|
||||||
|
@IBOutlet weak var viewTitle: UILabel!
|
||||||
|
|
||||||
@IBAction func defaultRingtonesButton(_ sender: Any)
|
/**
|
||||||
|
Removes the currently selcted alarm.
|
||||||
|
Returns the removed Alarm object.
|
||||||
|
*/
|
||||||
|
@discardableResult
|
||||||
|
func removeCurrentAlarm() -> Alarm?
|
||||||
{
|
{
|
||||||
|
guard let alarm = alarmCell?.alarm else { return nil }
|
||||||
|
|
||||||
|
// Removes the alarm from stored alarms
|
||||||
|
let alarms = Alarms.fromLocal()
|
||||||
|
alarms.list = alarms.list.filter { $0 != alarmCell?.alarm }
|
||||||
|
alarms.localSave()
|
||||||
|
|
||||||
|
return alarm
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func soundLibraryButton(_ sender: Any)
|
/**
|
||||||
{
|
Called when the time for the alarm is changed.
|
||||||
|
Sets the time away at the top of the View.
|
||||||
}
|
*/
|
||||||
|
@IBAction func alarmTimeUpdated(_ sender: Any) { updateETA() }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Called when the user clicks the remove button and brings them back to the home page
|
Called when the user clicks the remove button and brings them back to the home page
|
||||||
*/
|
*/
|
||||||
@IBAction func cancelAlarmButton(_ sender: Any) {
|
@IBAction func cancelAlarmButton(_ sender: Any) {
|
||||||
|
if editMode {
|
||||||
|
removeCurrentAlarm()
|
||||||
|
}
|
||||||
|
|
||||||
self.dismiss(animated: true, completion: nil)
|
self.dismiss(animated: true, completion: nil)
|
||||||
//might need to reset all UI elements
|
//might need to reset all UI elements
|
||||||
}
|
}
|
||||||
@@ -48,24 +102,68 @@ class AddAlarmViewController: UIViewController
|
|||||||
/**
|
/**
|
||||||
Called when the user clicks Add Alarm
|
Called when the user clicks Add Alarm
|
||||||
*/
|
*/
|
||||||
@IBAction func addAlarmButton(_ sender: Any) {
|
@IBAction func addAlarmButton(_ sender: Any)
|
||||||
|
{
|
||||||
|
var oldAlarm: Alarm? = nil
|
||||||
|
let alarm = createAlarm()
|
||||||
|
let alarms = Alarms.fromLocal()
|
||||||
|
|
||||||
|
// Check if editing alarm
|
||||||
|
if (editMode)
|
||||||
|
{
|
||||||
|
oldAlarm = removeCurrentAlarm()
|
||||||
|
}
|
||||||
|
// Check for existing alarm
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (alarms.list.contains { $0 == alarm })
|
||||||
|
{
|
||||||
|
msg("Sorry", "An identical or similar alarm already exists, please try again")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the alarm to the list and save the list
|
||||||
|
Alarms.fromLocal().apply { $0.list.append(alarm) }.localSave();
|
||||||
|
|
||||||
|
//Schedules notification for the alarm
|
||||||
|
if editMode
|
||||||
|
{
|
||||||
|
Notification.removeNotification(alarm: oldAlarm!)
|
||||||
|
}
|
||||||
|
Notification.scheduleNotification(alarm: alarm)
|
||||||
|
|
||||||
|
// Dismiss this view
|
||||||
|
self.dismiss(animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create alarm, but it doesn't add the alarm to the list
|
||||||
|
*/
|
||||||
|
func createAlarm() -> Alarm
|
||||||
|
{
|
||||||
let (h, m, _) = timePicker.date.getHMS()
|
let (h, m, _) = timePicker.date.getHMS()
|
||||||
|
|
||||||
// Create the alarm
|
// Create the alarm
|
||||||
let alarm = Alarm(hour: h, minute: m,
|
let alarm = Alarm(hour: h, minute: m,
|
||||||
text: alarmNameTextField.text ?? "Alarm",
|
text: alarmNameTextField.text ?? "Alarm",
|
||||||
wakeMethod: wvms[wvmPicker.selectedRow(inComponent: 0)],
|
wakeMethod: wvms[wvmPicker.selectedRow(inComponent: 0)],
|
||||||
lastActivate: Date())
|
lastActivate: Date(), alarmTone: ringtones[ringtonePicker.selectedRow(inComponent: 0)].tone, toneName: ringtones[ringtonePicker.selectedRow(inComponent: 0)].name)
|
||||||
|
|
||||||
// TODO: Set alarm.repeats to correspond with what the user selects
|
// Set alarm.repeats to correspond with what the user selects
|
||||||
|
(0...6).forEach { alarm.repeats[$0] = false }
|
||||||
|
if repeatWeekdaysSwitch.isOn { (1...5).forEach { alarm.repeats[$0] = true } }
|
||||||
|
if repeatWeekendsSwitch.isOn { [0, 6].forEach { alarm.repeats[$0] = true } }
|
||||||
|
|
||||||
|
return alarm
|
||||||
|
}
|
||||||
|
|
||||||
// Add the alarm to the list and save the list
|
/**
|
||||||
Alarms.fromLocal().apply { $0.list.append(alarm) }.localSave();
|
Dynamically the ETA label for the alarm
|
||||||
|
*/
|
||||||
// Dismiss this view
|
func updateETA() {
|
||||||
self.dismiss(animated: true, completion: nil)
|
let timeTill = createAlarm().nextActivate!.timeIntervalSince(Date()).str()
|
||||||
|
timeTillAlarmLabel.text = "Going off in \(timeTill)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,3 +191,31 @@ class WVMDataSource: UIPickerView, UIPickerViewDelegate, UIPickerViewDataSource
|
|||||||
return wvms[r].name + " - " + wvms[r].desc
|
return wvms[r].name + " - " + wvms[r].desc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class RingtonesDataSource: UIPickerView, UIPickerViewDelegate, UIPickerViewDataSource
|
||||||
|
{
|
||||||
|
required init?(coder: NSCoder)
|
||||||
|
{
|
||||||
|
super.init(coder: coder)
|
||||||
|
delegate = self
|
||||||
|
dataSource = self
|
||||||
|
}
|
||||||
|
|
||||||
|
func numberOfComponents(in pickerView: UIPickerView) -> Int
|
||||||
|
{
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func pickerView(_ v: UIPickerView, numberOfRowsInComponent: Int) -> Int
|
||||||
|
{
|
||||||
|
return ringtones.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func pickerView(_ v: UIPickerView, titleForRow r: Int, forComponent: Int) -> String?
|
||||||
|
{
|
||||||
|
return ringtones[r].name
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,17 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
|
import CoreMotion
|
||||||
|
|
||||||
class AlarmActivationViewController: UIViewController
|
var motion = CMMotionManager()
|
||||||
|
var alarmStarted = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
View controlling alarm activation and dismissal
|
||||||
|
*/
|
||||||
|
class AlarmActivationViewController: EndEditingOnReturn
|
||||||
{
|
{
|
||||||
var timer: Timer?
|
var currentAlarm: Alarm
|
||||||
var currentAlarm: Alarm?
|
|
||||||
|
|
||||||
// Puzzle outlets
|
// Puzzle outlets
|
||||||
@IBOutlet weak var puzzleView: UIView!
|
@IBOutlet weak var puzzleView: UIView!
|
||||||
@@ -23,99 +29,163 @@ class AlarmActivationViewController: UIViewController
|
|||||||
@IBOutlet weak var rpsView: UIView!
|
@IBOutlet weak var rpsView: UIView!
|
||||||
@IBOutlet weak var rpsResult: UILabel!
|
@IBOutlet weak var rpsResult: UILabel!
|
||||||
|
|
||||||
|
// Shake Outlets
|
||||||
|
@IBOutlet weak var shakeView: UIView!
|
||||||
|
|
||||||
|
// Other Outlets
|
||||||
|
@IBOutlet weak var timeLabel: UILabel!
|
||||||
|
@IBOutlet weak var dateLabel: UILabel!
|
||||||
|
|
||||||
|
var solved = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
Constructor to receive alarm data from segue
|
||||||
|
*/
|
||||||
init?(coder: NSCoder, currentAlarm: Alarm)
|
init?(coder: NSCoder, currentAlarm: Alarm)
|
||||||
{
|
{
|
||||||
self.currentAlarm = currentAlarm
|
self.currentAlarm = currentAlarm
|
||||||
//print(currentAlarm.wakeMethod)
|
|
||||||
super.init(coder: coder)
|
super.init(coder: coder)
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
/**
|
||||||
fatalError("init(coder:) has not been implemented")
|
Unused init
|
||||||
}
|
*/
|
||||||
|
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when the alarm activates
|
||||||
|
*/
|
||||||
override func viewDidLoad()
|
override func viewDidLoad()
|
||||||
{
|
{
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
// Set the time and date
|
||||||
|
dateLabel.text = Date().str("MMM d, Y")
|
||||||
|
timeLabel.text = currentAlarm.timeText
|
||||||
|
|
||||||
// Hide all inactive wakemethods
|
// Hide all inactive wakemethods
|
||||||
puzzleView.isHidden = true
|
puzzleView.hide()
|
||||||
rpsView.isHidden = true
|
rpsView.hide()
|
||||||
|
shakeView.hide()
|
||||||
|
|
||||||
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(AlarmActivationViewController.playSound), userInfo: nil, repeats: true)
|
// Play sound
|
||||||
setAlarmType()
|
playSound()
|
||||||
//print(MathExpression.random())
|
vibrate()
|
||||||
|
|
||||||
|
// Run alarm
|
||||||
|
runAlarm()
|
||||||
|
|
||||||
|
// End edit on return
|
||||||
|
puzzleAnswerInput.delegate = self
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func playSound()
|
/**
|
||||||
|
Play alarm sound
|
||||||
|
*/
|
||||||
|
func playSound()
|
||||||
{
|
{
|
||||||
AudioServicesPlayAlertSound(SystemSoundID(1005))
|
AudioServicesPlayAlertSoundWithCompletion(currentAlarm.alarmTone) {
|
||||||
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate)
|
if alarmStarted { self.playSound() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setAlarmType()
|
func vibrate()
|
||||||
{
|
{
|
||||||
if let alarm = currentAlarm
|
AudioServicesPlayAlertSoundWithCompletion(kSystemSoundID_Vibrate) {
|
||||||
|
if alarmStarted { self.vibrate() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Run alarm dismissal logic
|
||||||
|
*/
|
||||||
|
func runAlarm()
|
||||||
{
|
{
|
||||||
switch alarm.wakeMethod.name {
|
// Check if the device has accelerometer
|
||||||
case "Walk":
|
var wvm = currentAlarm.wakeMethod.name
|
||||||
walkAction()
|
if wvm == "Shake" && !motion.isDeviceMotionAvailable
|
||||||
case "Jump":
|
{
|
||||||
jumpAction()
|
msg("Error", "Accelerometer is not available on your device, so shaking your phone wouldn't work.")
|
||||||
|
wvm = "Factor"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize alarm
|
||||||
|
switch wvm
|
||||||
|
{
|
||||||
|
case "Math 1", "Math 2", "Math 3":
|
||||||
|
let q = MathExpProblem(size: Int(wvm[5...5])!)
|
||||||
|
puzzleQuestionLabel.text = "Solve: \(q.prob.replacingOccurrences(of: "**", with: "^"))"
|
||||||
|
puzzleAnswers = [q.ans]
|
||||||
|
puzzleView.show()
|
||||||
case "Factor":
|
case "Factor":
|
||||||
self.puzzleAnswers = factorAction(puzzleQuestionLabel: puzzleQuestionLabel)
|
initFactorProblem()
|
||||||
puzzleView.isHidden = false
|
puzzleView.show()
|
||||||
case "Smash":
|
|
||||||
print("")
|
|
||||||
case "RPS":
|
case "RPS":
|
||||||
rpsView.isHidden = false
|
rpsView.show()
|
||||||
//Get Choice here
|
case "Shake":
|
||||||
//rpsAction(choice: choice)
|
shakeView.show()
|
||||||
|
|
||||||
|
// Start motion detection
|
||||||
|
let q = OperationQueue()
|
||||||
|
motion.deviceMotionUpdateInterval = 0.2
|
||||||
|
motion.startDeviceMotionUpdates(to: q) { data, error in
|
||||||
|
if let a = data?.userAcceleration, sqrt(pow(a.x, 2) + pow(a.y, 2) + pow(a.z, 2)) > 4
|
||||||
|
{
|
||||||
|
ui { self.endAlarm() }
|
||||||
|
motion.stopDeviceMotionUpdates()
|
||||||
|
q.cancelAllOperations()
|
||||||
|
}
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
print("Invalid alarm type")
|
print("Invalid alarm type")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
alarmStarted = true
|
||||||
}
|
}
|
||||||
|
|
||||||
//Verfies and ends factoring WVM
|
func initFactorProblem()
|
||||||
@IBAction func checkBinomialSolution(_ sender: Any) {
|
{
|
||||||
if let input = puzzleAnswerInput.text {
|
let problem = QuadraticProb()
|
||||||
if let numericalInput = Int(input) {
|
puzzleAnswers = problem.getAnswer()
|
||||||
if puzzleAnswers.contains(numericalInput) {
|
|
||||||
|
puzzleQuestionLabel.text = "Solve: \(problem.getProblem())"
|
||||||
|
print("Answer: \(puzzleAnswers)")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Verfies and ends factoring WVM
|
||||||
|
*/
|
||||||
|
@IBAction func checkBinomialSolution(_ sender: Any)
|
||||||
|
{
|
||||||
|
if let input = puzzleAnswerInput.text,
|
||||||
|
let numericalInput = Int(input),
|
||||||
|
puzzleAnswers.contains(numericalInput)
|
||||||
|
{
|
||||||
endAlarm()
|
endAlarm()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Gets RPS choice
|
/**
|
||||||
@IBAction func rockChoice(_ sender: Any) {
|
Gets RPS choice
|
||||||
if rpsAction(choice: .rock)! {
|
*/
|
||||||
|
@IBAction func rpsChoice(_ sender: UIButton)
|
||||||
|
{
|
||||||
|
if RPS.playRPS(you: [.rock, .paper, .scissors][sender.tag], computer: RPS.choices.randomElement()!)
|
||||||
|
{
|
||||||
endAlarm()
|
endAlarm()
|
||||||
} else {
|
|
||||||
rpsResult.text = "Paper: You lost, try again"
|
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
@IBAction func paperChoice(_ sender: Any) {
|
{
|
||||||
if rpsAction(choice: .paper)! {
|
rpsResult.text = "\(["Paper", "Scissors", "Rock"][sender.tag]): You lost, try again"
|
||||||
endAlarm()
|
|
||||||
} else {
|
|
||||||
rpsResult.text = "Scissors: You lost, try again"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@IBAction func scissorChoice(_ sender: Any) {
|
|
||||||
if rpsAction(choice: .scissors)! {
|
|
||||||
endAlarm()
|
|
||||||
} else {
|
|
||||||
rpsResult.text = "Rock: You lost, try again"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Standard way to turn off and close the alarm
|
||||||
//Standard way to turn off and close the alarm
|
*/
|
||||||
func endAlarm() {
|
func endAlarm()
|
||||||
timer?.invalidate()
|
{
|
||||||
|
alarmStarted = false
|
||||||
print("Alarm solved")
|
print("Alarm solved")
|
||||||
dismiss(animated: true, completion: nil)
|
dismiss(animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,9 +22,24 @@ class AlarmActivator: UITabBarController
|
|||||||
var timer: Timer?
|
var timer: Timer?
|
||||||
var alarm: Alarm?
|
var alarm: Alarm?
|
||||||
|
|
||||||
|
/// Timer for getting family alarm updates
|
||||||
|
var familyTimer: Timer?
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when the app started
|
||||||
|
*/
|
||||||
override func viewDidLoad()
|
override func viewDidLoad()
|
||||||
{
|
{
|
||||||
start()
|
start()
|
||||||
|
|
||||||
|
// Get notification permissions from user
|
||||||
|
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
|
||||||
|
if success {
|
||||||
|
print("All set!")
|
||||||
|
} else if let error = error {
|
||||||
|
print(error.localizedDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,6 +49,7 @@ class AlarmActivator: UITabBarController
|
|||||||
{
|
{
|
||||||
if timer != nil { return }
|
if timer != nil { return }
|
||||||
timer = Timer.scheduledTimer(timeInterval: AlarmActivator.interval, target: self, selector: #selector(AlarmActivator.check), userInfo: nil, repeats: true)
|
timer = Timer.scheduledTimer(timeInterval: AlarmActivator.interval, target: self, selector: #selector(AlarmActivator.check), userInfo: nil, repeats: true)
|
||||||
|
familyTimer = Timer.scheduledTimer(timeInterval: 20.0, target: self, selector: #selector(AlarmActivator.checkFamily), userInfo: nil, repeats: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,17 +76,57 @@ class AlarmActivator: UITabBarController
|
|||||||
alarm.apply {
|
alarm.apply {
|
||||||
$0.lastActivate = Date()
|
$0.lastActivate = Date()
|
||||||
if $0.oneTime { $0.enabled = false }
|
if $0.oneTime { $0.enabled = false }
|
||||||
|
else {
|
||||||
|
Notification.scheduleNotification(alarm: alarm)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
alarms.localSave()
|
alarms.localSave()
|
||||||
self.alarm = alarm
|
self.alarm = alarm
|
||||||
// Segue
|
|
||||||
//NSLog(JSON.stringify(alarm)!)
|
// Avoid starting duplicate alarms
|
||||||
performSegue(withIdentifier: "activate-alarm", sender: alarm)
|
if !alarmStarted { performSegue(withIdentifier: "activate-alarm", sender: alarm) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBSegueAction func sendAlarm(_ coder: NSCoder) -> AlarmActivationViewController? {
|
@IBSegueAction func sendAlarm(_ coder: NSCoder) -> AlarmActivationViewController?
|
||||||
|
{
|
||||||
return AlarmActivationViewController(coder: coder, currentAlarm: alarm!)
|
return AlarmActivationViewController(coder: coder, currentAlarm: alarm!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Check family alarm updates
|
||||||
|
*/
|
||||||
|
@objc func checkFamily()
|
||||||
|
{
|
||||||
|
guard localStorage.string(forKey: "family") != nil else { return }
|
||||||
|
|
||||||
|
send(APIs.familyAlarmUpdates)
|
||||||
|
{
|
||||||
|
guard $0 != "" else { return }
|
||||||
|
|
||||||
|
// Update alarms list
|
||||||
|
var changed = false
|
||||||
|
let alarms = Alarms.fromLocal()
|
||||||
|
$0.csv.forEach
|
||||||
|
{
|
||||||
|
guard let alarm = JSON.parse(Alarm.self, $0) else { return }
|
||||||
|
if (!alarms.list.contains { $0.timeText == alarm.timeText })
|
||||||
|
{
|
||||||
|
alarms.list.append(alarm)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
alarms.localSave()
|
||||||
|
|
||||||
|
// Update UI
|
||||||
|
guard changed else { return }
|
||||||
|
ui
|
||||||
|
{
|
||||||
|
self.msg("New alarm!", "A family member added an alarm for you!")
|
||||||
|
{
|
||||||
|
AlarmViewController.staticTable?.reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 3.0 MiB |
@@ -46,6 +46,14 @@ extension AlarmViewController: UITableViewDelegate, UITableViewDataSource
|
|||||||
|
|
||||||
/// IDK what this does (TODO: Find out what this does)
|
/// IDK what this does (TODO: Find out what this does)
|
||||||
func tableView(_ v: UITableView, didSelectRowAt i: IndexPath) { v.deselectRow(at: i, animated: true) }
|
func tableView(_ v: UITableView, didSelectRowAt i: IndexPath) { v.deselectRow(at: i, animated: true) }
|
||||||
|
|
||||||
|
/// Sends the selected alarm to be edited
|
||||||
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||||
|
if segue.identifier == "edit-alarm" {
|
||||||
|
let vc = segue.destination as! AddAlarmViewController
|
||||||
|
vc.alarmCell = (sender as! AlarmTableCell)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,35 +67,70 @@ class AlarmTableCell: UITableViewCell
|
|||||||
@IBOutlet weak var enable: UISwitch!
|
@IBOutlet weak var enable: UISwitch!
|
||||||
@IBOutlet weak var repeatText: UILabel!
|
@IBOutlet weak var repeatText: UILabel!
|
||||||
@IBOutlet weak var goingOffText: UILabel!
|
@IBOutlet weak var goingOffText: UILabel!
|
||||||
|
@IBOutlet weak var wvmText: UILabel!
|
||||||
|
@IBOutlet weak var toneLabel: UILabel!
|
||||||
|
|
||||||
/// Update information on the cell to information in the alarm object
|
var alarm: Alarm!
|
||||||
//WARNING:Terrible code lies ahead! You WILL be dissapointed! But it works, and that is all that matters.
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Update information on the cell to information in the alarm object
|
||||||
|
*/
|
||||||
func setData(_ alarm: Alarm)
|
func setData(_ alarm: Alarm)
|
||||||
{
|
{
|
||||||
|
self.alarm = alarm
|
||||||
descriptionText.text = "- " + alarm.text
|
descriptionText.text = "- " + alarm.text
|
||||||
|
enable.isOn = alarm.enabled
|
||||||
|
wvmText.text = alarm.wakeMethod.name
|
||||||
|
toneLabel.text = ringtones.first { $0.tone.description == alarm.alarmTone.description }?.name
|
||||||
|
|
||||||
// Display Hour, Minute, and AM or PM
|
// Display Hour, Minute, and AM or PM
|
||||||
ampm.text = alarm.hour < 12 || alarm.hour == 24 ? "AM" : "PM"
|
ampm.text = alarm.hour < 12 || alarm.hour == 24 ? "AM" : "PM"
|
||||||
let hour = alarm.hour <= 12 ? alarm.hour : alarm.hour - 12
|
var hour = alarm.hour <= 12 ? alarm.hour : alarm.hour - 12
|
||||||
time.text = alarm.minute < 10 ? "\(hour):0\(alarm.minute)" : "\(hour):\(alarm.minute)"
|
hour = alarm.hour == 0 ? 12 : hour
|
||||||
|
time.text = String(format: "%i:%02i", hour, alarm.minute)
|
||||||
|
|
||||||
// displays the specific days alarm is activated
|
// displays the specific days alarm is activated
|
||||||
let daysDict = ["Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat"]
|
let daysDict = ["Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat"]
|
||||||
var daysActive : [String] = []
|
var daysActive : [String] = []
|
||||||
if alarm.oneTime {repeatText.text = "No Repeat"}
|
if alarm.oneTime {repeatText.text = "One-time Alarm"}
|
||||||
else {
|
else {
|
||||||
for (index, element) in alarm.repeats.enumerated() {
|
for (index, element) in alarm.repeats.enumerated() {
|
||||||
if element {
|
if element {
|
||||||
daysActive.append(daysDict[index])
|
daysActive.append(daysDict[index])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if daysDict == daysActive {
|
||||||
|
repeatText.text = "Repeats: Daily"
|
||||||
|
} else if daysActive == ["Sun", "Sat"] {
|
||||||
|
repeatText.text = "Repeats: Weekends"
|
||||||
|
} else if daysActive == ["Mon", "Tues", "Wed", "Thurs", "Fri"] {
|
||||||
|
repeatText.text = "Repeats: Weekdays"
|
||||||
|
} else {
|
||||||
repeatText.text = daysActive.joined(separator: ", ")
|
repeatText.text = daysActive.joined(separator: ", ")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateActivationTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateActivationTime()
|
||||||
|
{
|
||||||
// Show next activation date
|
// Show next activation date
|
||||||
if alarm.enabled, let n = alarm.nextActivate {
|
if alarm.enabled, let n = alarm.nextActivate {
|
||||||
goingOffText.text = "(Going off in \(n.timeIntervalSince(Date()).str()))"
|
goingOffText.text = "(Going off in \(n.timeIntervalSince(Date()).str()))"
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
goingOffText.text = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when the user switches the switch
|
||||||
|
*/
|
||||||
|
@IBAction func switchChange(_ sender: Any)
|
||||||
|
{
|
||||||
|
Alarms.fromLocal().apply {
|
||||||
|
$0.list.first { $0.hour == self.alarm.hour && $0.minute == self.alarm.minute }?.enabled = enable.isOn
|
||||||
|
}.localSave()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 11 KiB |
@@ -1,91 +1,109 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
|
"filename" : "40.png",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"scale" : "2x",
|
"scale" : "2x",
|
||||||
"size" : "20x20"
|
"size" : "20x20"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"filename" : "60.png",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"scale" : "3x",
|
"scale" : "3x",
|
||||||
"size" : "20x20"
|
"size" : "20x20"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"filename" : "58.png",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"scale" : "2x",
|
"scale" : "2x",
|
||||||
"size" : "29x29"
|
"size" : "29x29"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"filename" : "87.png",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"scale" : "3x",
|
"scale" : "3x",
|
||||||
"size" : "29x29"
|
"size" : "29x29"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"filename" : "80.png",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"scale" : "2x",
|
"scale" : "2x",
|
||||||
"size" : "40x40"
|
"size" : "40x40"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"filename" : "120.png",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"scale" : "3x",
|
"scale" : "3x",
|
||||||
"size" : "40x40"
|
"size" : "40x40"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"filename" : "120-1.png",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"scale" : "2x",
|
"scale" : "2x",
|
||||||
"size" : "60x60"
|
"size" : "60x60"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"filename" : "180.png",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"scale" : "3x",
|
"scale" : "3x",
|
||||||
"size" : "60x60"
|
"size" : "60x60"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"filename" : "20.png",
|
||||||
"idiom" : "ipad",
|
"idiom" : "ipad",
|
||||||
"scale" : "1x",
|
"scale" : "1x",
|
||||||
"size" : "20x20"
|
"size" : "20x20"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"filename" : "40-1.png",
|
||||||
"idiom" : "ipad",
|
"idiom" : "ipad",
|
||||||
"scale" : "2x",
|
"scale" : "2x",
|
||||||
"size" : "20x20"
|
"size" : "20x20"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"filename" : "29.png",
|
||||||
"idiom" : "ipad",
|
"idiom" : "ipad",
|
||||||
"scale" : "1x",
|
"scale" : "1x",
|
||||||
"size" : "29x29"
|
"size" : "29x29"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"filename" : "58-1.png",
|
||||||
"idiom" : "ipad",
|
"idiom" : "ipad",
|
||||||
"scale" : "2x",
|
"scale" : "2x",
|
||||||
"size" : "29x29"
|
"size" : "29x29"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"filename" : "40-2.png",
|
||||||
"idiom" : "ipad",
|
"idiom" : "ipad",
|
||||||
"scale" : "1x",
|
"scale" : "1x",
|
||||||
"size" : "40x40"
|
"size" : "40x40"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"filename" : "80-1.png",
|
||||||
"idiom" : "ipad",
|
"idiom" : "ipad",
|
||||||
"scale" : "2x",
|
"scale" : "2x",
|
||||||
"size" : "40x40"
|
"size" : "40x40"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"filename" : "76.png",
|
||||||
"idiom" : "ipad",
|
"idiom" : "ipad",
|
||||||
"scale" : "1x",
|
"scale" : "1x",
|
||||||
"size" : "76x76"
|
"size" : "76x76"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"filename" : "152.png",
|
||||||
"idiom" : "ipad",
|
"idiom" : "ipad",
|
||||||
"scale" : "2x",
|
"scale" : "2x",
|
||||||
"size" : "76x76"
|
"size" : "76x76"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"filename" : "167.png",
|
||||||
"idiom" : "ipad",
|
"idiom" : "ipad",
|
||||||
"scale" : "2x",
|
"scale" : "2x",
|
||||||
"size" : "83.5x83.5"
|
"size" : "83.5x83.5"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"filename" : "appstore.png",
|
||||||
"idiom" : "ios-marketing",
|
"idiom" : "ios-marketing",
|
||||||
"scale" : "1x",
|
"scale" : "1x",
|
||||||
"size" : "1024x1024"
|
"size" : "1024x1024"
|
||||||
|
|||||||
|
After Width: | Height: | Size: 1.3 MiB |
@@ -0,0 +1,64 @@
|
|||||||
|
//
|
||||||
|
// TestingViewController.swift
|
||||||
|
// ProjectClock
|
||||||
|
//
|
||||||
|
// Created by Aaron Saporito on 1/13/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import UserNotifications
|
||||||
|
|
||||||
|
class DebugViewController: EndEditingOnReturn
|
||||||
|
{
|
||||||
|
@IBOutlet weak var userModeButton: UIButton!
|
||||||
|
var darkMode = false
|
||||||
|
|
||||||
|
@IBOutlet weak var wvmInput: UITextField!
|
||||||
|
@IBOutlet weak var wvmStepper: UIStepper!
|
||||||
|
|
||||||
|
override func viewDidLoad()
|
||||||
|
{
|
||||||
|
super.viewDidLoad()
|
||||||
|
wvmStepper.maximumValue = Double(wvms.count - 1)
|
||||||
|
|
||||||
|
// End editing on return
|
||||||
|
wvmInput.delegate = self
|
||||||
|
}
|
||||||
|
|
||||||
|
//Sends a test notification
|
||||||
|
@IBAction func sendNotification(_ sender: Any)
|
||||||
|
{
|
||||||
|
Notification.scheduleNotification(alarm: Alarms.fromLocal().listEnabled[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func addAlarm(_ sender: Any)
|
||||||
|
{
|
||||||
|
let (h, m, _) = Date().getHMS()
|
||||||
|
let alarm = Alarm(hour: h, minute: m, text: "Test alarm - \(h * m)", wakeMethod: wvms[Int(wvmStepper.value)], repeats: [true, true, true, true, true, true, true], lastActivate: Date().added(.minute, -1))
|
||||||
|
|
||||||
|
Alarms.fromLocal().apply { $0.list.append(alarm) }.localSave()
|
||||||
|
Notification.scheduleNotification(alarm: alarm)
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func deleteAlarm(_ sender: Any)
|
||||||
|
{
|
||||||
|
Alarms.fromLocal().apply { $0.list.removeAll() }.localSave()
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func wvmStepperChange(_ sender: Any)
|
||||||
|
{
|
||||||
|
wvmInput.text = String(Int(wvmStepper.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func switchViewingMode(_ sender: Any) {
|
||||||
|
if !darkMode {
|
||||||
|
view.window?.overrideUserInterfaceStyle = .dark
|
||||||
|
userModeButton.setTitle("Switch to Light Mode", for: .normal)
|
||||||
|
darkMode = true
|
||||||
|
} else {
|
||||||
|
view.window?.overrideUserInterfaceStyle = .light
|
||||||
|
userModeButton.setTitle("Switch to Dark Mode", for: .normal)
|
||||||
|
darkMode = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import CoreMotion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Math element for problem generation (Credit: https://stackoverflow.com/a/43132311/7346633)
|
Math element for problem generation (Credit: https://stackoverflow.com/a/43132311/7346633)
|
||||||
@@ -39,11 +40,10 @@ enum MathOperator : String {
|
|||||||
case plus = "+"
|
case plus = "+"
|
||||||
case minus = "-"
|
case minus = "-"
|
||||||
case multiply = "*"
|
case multiply = "*"
|
||||||
case divide = "/"
|
|
||||||
case power = "**"
|
case power = "**"
|
||||||
|
|
||||||
static func random() -> MathOperator {
|
static func random() -> MathOperator {
|
||||||
let allMathOperators: [MathOperator] = [.plus, .minus, .multiply, .divide, .power]
|
let allMathOperators: [MathOperator] = [.plus, .minus, .multiply, .power]
|
||||||
let index = Int(arc4random_uniform(UInt32(allMathOperators.count)))
|
let index = Int(arc4random_uniform(UInt32(allMathOperators.count)))
|
||||||
|
|
||||||
return allMathOperators[index]
|
return allMathOperators[index]
|
||||||
@@ -82,50 +82,42 @@ class MathExpression : CustomStringConvertible {
|
|||||||
return "\(leftString) \(self.op.rawValue) \(rightString)"
|
return "\(leftString) \(self.op.rawValue) \(rightString)"
|
||||||
}
|
}
|
||||||
|
|
||||||
var result : Int? {
|
var result: Int {
|
||||||
let format = "\(lhs.nsExpressionFormatString) \(op.rawValue) \(rhs.nsExpressionFormatString)"
|
let format = "\(lhs.nsExpressionFormatString) \(op.rawValue) \(rhs.nsExpressionFormatString)"
|
||||||
let expr = NSExpression(format: format)
|
let expr = NSExpression(format: format)
|
||||||
return expr.expressionValue(with: nil, context: nil) as? Int
|
let result = expr.expressionValue(with: nil, context: nil)
|
||||||
|
return Int(round(result as! Double))
|
||||||
}
|
}
|
||||||
|
|
||||||
static func random() -> MathExpression {
|
static func random() -> MathExpression {
|
||||||
|
let op: MathOperator = .random()
|
||||||
let lhs = MathElement.Integer(value: Int(arc4random_uniform(10)))
|
let lhs = MathElement.Integer(value: Int(arc4random_uniform(10)))
|
||||||
let rhs = MathElement.Integer(value: Int(arc4random_uniform(10)))
|
let rhs = MathElement.Integer(value: Int(arc4random_uniform(op == .power ? 3 : 10)))
|
||||||
|
|
||||||
return MathExpression(lhs: lhs, rhs: rhs, op: .random())
|
return MathExpression(lhs: lhs, rhs: rhs, op: op)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Generate simple problem - 2 expressions
|
Generate simple problem - 2 expressions
|
||||||
*/
|
*/
|
||||||
class AlgProb2 : MathExpression {
|
class MathExpProblem
|
||||||
let a = MathExpression.random()
|
{
|
||||||
let b = MathExpression.random()
|
let prob: String
|
||||||
|
let ans: Int
|
||||||
|
|
||||||
func getProblem() -> String {
|
init(size: Int)
|
||||||
return "\(a) + \(b)"
|
{
|
||||||
|
var expressions: [String] = []
|
||||||
|
var answer = 0
|
||||||
|
for _ in 1...size
|
||||||
|
{
|
||||||
|
let exp = MathExpression.random()
|
||||||
|
expressions.append(exp.description)
|
||||||
|
answer += exp.result
|
||||||
}
|
}
|
||||||
|
prob = expressions.joined(separator: " + ")
|
||||||
func getAnswer() -> String {
|
ans = answer
|
||||||
return "\(a.result! + b.result!)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Generate simple problem - 3 expressions
|
|
||||||
*/
|
|
||||||
class AlgProb3 : MathExpression {
|
|
||||||
let a = MathExpression.random()
|
|
||||||
let b = MathExpression.random()
|
|
||||||
let c = MathExpression.random()
|
|
||||||
|
|
||||||
func getProblem() -> String {
|
|
||||||
return "\(a) + \(b) + \(c)"
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAnswer() -> String {
|
|
||||||
return "\(a.result! + b.result! + c.result!)"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,43 +148,25 @@ class QuadraticProb {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RPS {
|
/**
|
||||||
//@IBOutlet weak var resultsLabel: UILabel!
|
Rock paper scissors
|
||||||
|
|
||||||
enum Choice: String {
|
|
||||||
case rock = "ROCK"
|
|
||||||
case paper = "PAPER"
|
|
||||||
case scissors = "SCISSORS"
|
|
||||||
}
|
|
||||||
|
|
||||||
static func randomComputerChoice() -> Choice {
|
|
||||||
let choices: [Choice] = [.rock, .paper, .scissors]
|
|
||||||
return choices[Int.random(in: 0...2)]
|
|
||||||
}
|
|
||||||
|
|
||||||
func playRPS(you: Choice, computer: Choice) -> Bool? {
|
|
||||||
if you == .rock && computer == .scissors { return true }
|
|
||||||
else if you == .paper && computer == .rock { return true}
|
|
||||||
else if you == .scissors && computer == .paper { return true }
|
|
||||||
else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
@IBAction func rock(_ sender: UIButton) {
|
|
||||||
let computerChoice = Choice.randomComputerChoice()
|
|
||||||
resultsLabel.text = playRPS(you: .rock, computer: computerChoice)
|
|
||||||
}
|
|
||||||
|
|
||||||
@IBAction func paper(_ sender: UIButton) {
|
|
||||||
let computerChoice = Choice.randomComputerChoice()
|
|
||||||
resultsLabel.text = playRPS(you: .paper, computer: computerChoice)
|
|
||||||
}
|
|
||||||
|
|
||||||
@IBAction func scissors(_ sender: UIButton) {
|
|
||||||
let computerChoice = Choice.randomComputerChoice()
|
|
||||||
resultsLabel.text = playRPS(you: .scissors, computer: computerChoice)
|
|
||||||
}
|
|
||||||
*/
|
*/
|
||||||
|
class RPS
|
||||||
|
{
|
||||||
|
static let choices: [Choice] = [.rock, .paper, .scissors]
|
||||||
|
|
||||||
|
enum Choice: String
|
||||||
|
{
|
||||||
|
case rock = "Rock"
|
||||||
|
case paper = "Paper"
|
||||||
|
case scissors = "Scissors"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func playRPS(you: Choice, computer: Choice) -> Bool
|
||||||
|
{
|
||||||
|
return you == .rock && computer == .scissors ||
|
||||||
|
you == .paper && computer == .rock ||
|
||||||
|
you == .scissors && computer == .paper
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,51 +6,89 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import AVFoundation
|
||||||
struct User: Codable
|
|
||||||
{
|
|
||||||
var id: Int
|
|
||||||
var name: String
|
|
||||||
var email: String
|
|
||||||
var pass: String
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Family: Codable
|
struct Family: Codable
|
||||||
{
|
{
|
||||||
var fid: Int
|
var fid: Int
|
||||||
var fname: String
|
var name: String
|
||||||
var members: [String]
|
var members: String
|
||||||
// And a hidden field: admin pin
|
// And a hidden field: admin pin
|
||||||
|
|
||||||
|
var membersList: [String] { members.csv }
|
||||||
|
|
||||||
|
/// Save family to local storage
|
||||||
|
func localSave()
|
||||||
|
{
|
||||||
|
localStorage.setValue(JSON.stringify(self)!, forKey: "family")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read family object from local storage
|
||||||
|
static func fromLocal() -> Family?
|
||||||
|
{
|
||||||
|
guard let f = localStorage.string(forKey: "family") else { return nil }
|
||||||
|
return JSON.parse(Family.self, f)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct WVM: Codable
|
struct WVM: Codable
|
||||||
{
|
{
|
||||||
|
let index: Int
|
||||||
let name: String
|
let name: String
|
||||||
let desc: String
|
let desc: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let wvms = [
|
let wvms = [
|
||||||
WVM(name: "Factor", desc: "Factor a binomial"),
|
WVM(index: 0, name: "Shake", desc: "Shake your phone... aggresively!"),
|
||||||
WVM(name: "RPS", desc: "Win a game of rock paper scissors"),
|
WVM(index: 1, name: "Math 1", desc: "Easy math expression"),
|
||||||
WVM(name: "Smash", desc: "It'll never turn off"),
|
WVM(index: 2, name: "Math 2", desc: "Medium math expression"),
|
||||||
WVM(name: "Walk", desc: "Walk a few steps"),
|
WVM(index: 3, name: "Math 3", desc: "Hard math expression"),
|
||||||
WVM(name: "Jump", desc: "Make a few jumps")
|
WVM(index: 4, name: "Factor", desc: "Factor a binomial"),
|
||||||
|
WVM(index: 5, name: "RPS", desc: "Win a game of rock paper scissors"),
|
||||||
|
//WVM(name: "Smash", desc: "It'll never turn off"),
|
||||||
|
//WVM(name: "Walk", desc: "Walk a few steps"),
|
||||||
|
//WVM(name: "Jump", desc: "Make a few jumps")
|
||||||
]
|
]
|
||||||
|
|
||||||
class Alarm: Codable
|
|
||||||
|
struct Tone: Codable{
|
||||||
|
|
||||||
|
let name: String
|
||||||
|
let tone: SystemSoundID
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
let ringtones = [
|
||||||
|
Tone(name: "News Flash", tone: SystemSoundID(1028)),
|
||||||
|
Tone(name: "Sherwood Forest", tone: SystemSoundID(1030)),
|
||||||
|
Tone(name: "Ladder", tone: SystemSoundID(1326)),
|
||||||
|
Tone(name: "Minuet", tone: SystemSoundID(1327)),
|
||||||
|
Tone(name: "Tock", tone: SystemSoundID(1306)),
|
||||||
|
Tone(name: "Bloom", tone: SystemSoundID(1321)),
|
||||||
|
Tone(name: "Calypso", tone: SystemSoundID(1322)),
|
||||||
|
Tone(name: "Train", tone: SystemSoundID(1323)),
|
||||||
|
Tone(name: "Fanfare", tone: SystemSoundID(1325))
|
||||||
|
]
|
||||||
|
|
||||||
|
class Alarm: Codable, Equatable
|
||||||
{
|
{
|
||||||
|
static func == (lhs: Alarm, rhs: Alarm) -> Bool {
|
||||||
|
return lhs.hour == rhs.hour && lhs.minute == rhs.minute && lhs.text == rhs.text &&
|
||||||
|
lhs.alarmTone == rhs.alarmTone && lhs.repeats == rhs.repeats
|
||||||
|
}
|
||||||
|
|
||||||
var enabled: Bool
|
var enabled: Bool
|
||||||
var hour: Int // Hour (24)
|
var hour: Int // Hour (24)
|
||||||
var minute: Int
|
var minute: Int
|
||||||
var text: String
|
var text: String
|
||||||
var wakeMethod: WVM
|
var wakeMethod: WVM
|
||||||
|
var alarmTone: SystemSoundID
|
||||||
|
var notificationID: String
|
||||||
|
|
||||||
/// What days does it repeat (Sun, Mon, Tue, Wed, Thu, Fri, Sat)
|
/// What days does it repeat (Sun, Mon, Tue, Wed, Thu, Fri, Sat)
|
||||||
var repeats: [Bool]
|
var repeats: [Bool]
|
||||||
|
|
||||||
/// Does it automatically disable after activating once
|
|
||||||
var oneTime: Bool
|
|
||||||
|
|
||||||
/// When is the last time that the alarm went off
|
/// When is the last time that the alarm went off
|
||||||
var lastActivate: Date
|
var lastActivate: Date
|
||||||
|
|
||||||
@@ -58,7 +96,10 @@ class Alarm: Codable
|
|||||||
init(enabled: Bool = true,
|
init(enabled: Bool = true,
|
||||||
hour: Int, minute: Int, text: String, wakeMethod: WVM,
|
hour: Int, minute: Int, text: String, wakeMethod: WVM,
|
||||||
repeats: [Bool] = [false, true, true, true, true, true, false],
|
repeats: [Bool] = [false, true, true, true, true, true, false],
|
||||||
oneTime: Bool = false, lastActivate: Date = Date()
|
lastActivate: Date = Date(),
|
||||||
|
alarmTone: SystemSoundID = ringtones[0].tone,
|
||||||
|
toneName: String = ""
|
||||||
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
self.enabled = enabled
|
self.enabled = enabled
|
||||||
@@ -67,10 +108,17 @@ class Alarm: Codable
|
|||||||
self.text = text
|
self.text = text
|
||||||
self.wakeMethod = wakeMethod
|
self.wakeMethod = wakeMethod
|
||||||
self.repeats = repeats
|
self.repeats = repeats
|
||||||
self.oneTime = oneTime
|
|
||||||
self.lastActivate = lastActivate
|
self.lastActivate = lastActivate
|
||||||
|
self.alarmTone = alarmTone
|
||||||
|
self.notificationID = "notification.id.\(Int.random(in: 1...Int.max))"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Does it automatically disable after activating once
|
||||||
|
var oneTime: Bool { repeats.allSatisfy { !$0 } }
|
||||||
|
|
||||||
|
/// Get time in h:mm format
|
||||||
|
var timeText: String { String(format: "%i:%02i", hour, minute) }
|
||||||
|
|
||||||
/// When should the alarm activate next since lastActivate?
|
/// When should the alarm activate next since lastActivate?
|
||||||
var nextActivate: Date?
|
var nextActivate: Date?
|
||||||
{
|
{
|
||||||
@@ -103,6 +151,7 @@ class Alarms: Codable
|
|||||||
/// Save alarms to local storage
|
/// Save alarms to local storage
|
||||||
func localSave()
|
func localSave()
|
||||||
{
|
{
|
||||||
|
list.sort { ($0.hour * 60 + $0.minute) < ($1.hour * 60 + $1.minute) }
|
||||||
localStorage.setValue(JSON.stringify(list)!, forKey: "alarms")
|
localStorage.setValue(JSON.stringify(list)!, forKey: "alarms")
|
||||||
|
|
||||||
// Reload table view
|
// Reload table view
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class APIs
|
|||||||
## Returns
|
## Returns
|
||||||
Success or error
|
Success or error
|
||||||
*/
|
*/
|
||||||
static let uploadConfig = API<String>(loc: "/backup/upload")
|
static let uploadConfig = API<String>(loc: "/user/backup/upload")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Download the config from the cloud.
|
Download the config from the cloud.
|
||||||
@@ -95,13 +95,24 @@ class APIs
|
|||||||
## Returns
|
## Returns
|
||||||
Config Json
|
Config Json
|
||||||
*/
|
*/
|
||||||
static let downloadConfig = API<String>(loc: "/backup/download")
|
static let downloadConfig = API<String>(loc: "/user/backup/download")
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get family info for this account
|
||||||
|
|
||||||
|
## Parameters (Besides from username and password)
|
||||||
|
None
|
||||||
|
|
||||||
|
## Returns
|
||||||
|
Family object
|
||||||
|
*/
|
||||||
|
static let familyGet = API<Family>(loc: "/family/get")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a family
|
Create a family
|
||||||
|
|
||||||
## Parameters (Besides from username and password)
|
## Parameters (Besides from username and password)
|
||||||
- fname: Family name
|
- name: Family name
|
||||||
- pin: Admin pin
|
- pin: Admin pin
|
||||||
|
|
||||||
## Returns
|
## Returns
|
||||||
@@ -109,54 +120,56 @@ class APIs
|
|||||||
*/
|
*/
|
||||||
static let familyCreate = API<Family>(loc: "/family/create")
|
static let familyCreate = API<Family>(loc: "/family/create")
|
||||||
|
|
||||||
/**
|
|
||||||
Delete a family
|
|
||||||
|
|
||||||
## Parameters (Besides from username and password)
|
|
||||||
- fid: Family ID
|
|
||||||
- pin: Admin pin
|
|
||||||
|
|
||||||
## Returns
|
|
||||||
Success or not
|
|
||||||
*/
|
|
||||||
static let familyDelete = API<String>(loc: "/family/delete")
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Change a family's admin pin
|
Change a family's admin pin
|
||||||
|
|
||||||
## Parameters (Besides from username and password)
|
## Parameters (Besides from username and password)
|
||||||
- fid: Family ID
|
- fid: Family ID
|
||||||
- orig_pin: Original admin pin
|
- oldPin: Original admin pin
|
||||||
- new_pin: New admin pin
|
- newPin: New admin pin
|
||||||
|
|
||||||
## Returns
|
## Returns
|
||||||
Success or not
|
Updated family object
|
||||||
*/
|
*/
|
||||||
static let familyChangePin = API<String>(loc: "/family/update_pin")
|
static let familyChangePin = API<Family>(loc: "/family/update_pin")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Join family
|
Family-related action
|
||||||
|
|
||||||
## Parameters (Besides from username and password)
|
## Parameters (Besides from username and password)
|
||||||
- fid: Family ID
|
- fid: Family ID
|
||||||
- pin: Admin pin
|
- pin: Admin pin
|
||||||
|
- action: Join / Leave / Delete
|
||||||
|
|
||||||
## Returns
|
## Returns
|
||||||
Family object
|
Family object
|
||||||
*/
|
*/
|
||||||
static let familyJoin = API<Family>(loc: "/family/join")
|
static let familyAction = API<Family>(loc: "/family/action")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Leave family
|
Get updates about alarms that other family members added
|
||||||
|
|
||||||
|
## Parameters (Besides from username and password)
|
||||||
|
None
|
||||||
|
|
||||||
|
## Returns
|
||||||
|
Alarm updates
|
||||||
|
*/
|
||||||
|
static let familyAlarmUpdates = API<String>(loc: "/family/get_alarm_updates")
|
||||||
|
|
||||||
|
/**
|
||||||
|
Add alarm to a family member
|
||||||
|
|
||||||
## Parameters (Besides from username and password)
|
## Parameters (Besides from username and password)
|
||||||
- fid: Family ID
|
- fid: Family ID
|
||||||
- pin: Admin pin
|
- pin: Admin pin
|
||||||
|
- to: Family member's username
|
||||||
|
- alarm: Alarm json
|
||||||
|
|
||||||
## Returns
|
## Returns
|
||||||
Success or not
|
Success message
|
||||||
*/
|
*/
|
||||||
static let familyLeave = API<String>(loc: "/family/leave")
|
static let familyAddAlarm = API<String>(loc: "/family/add_alarm")
|
||||||
|
|
||||||
private init() {}
|
private init() {}
|
||||||
}
|
}
|
||||||
@@ -197,6 +210,7 @@ func send<T: Decodable>(_ api: API<T>, _ params: [String: String]? = [:], _ succ
|
|||||||
{
|
{
|
||||||
if params!["username"] == nil { params!["username"] = localStorage.string(forKey: "name") }
|
if params!["username"] == nil { params!["username"] = localStorage.string(forKey: "name") }
|
||||||
if params!["password"] == nil { params!["password"] = localStorage.string(forKey: "pass") }
|
if params!["password"] == nil { params!["password"] = localStorage.string(forKey: "pass") }
|
||||||
|
if params!["fid"] == nil, let f = Family.fromLocal() { params!["fid"] = String(f.fid) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create task
|
// Create task
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
//
|
||||||
|
// Notification.swift
|
||||||
|
// ProjectClock
|
||||||
|
//
|
||||||
|
// Created by Aaron Saporito on 1/24/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import UserNotifications
|
||||||
|
|
||||||
|
class Notification {
|
||||||
|
|
||||||
|
//Removes the notifiation
|
||||||
|
static func removeNotification(alarm: Alarm) {
|
||||||
|
UNUserNotificationCenter.current().getPendingNotificationRequests { (notificationRequests) in
|
||||||
|
var identifiers: [String] = []
|
||||||
|
for notification:UNNotificationRequest in notificationRequests {
|
||||||
|
if notification.identifier == alarm.notificationID {
|
||||||
|
identifiers.append(notification.identifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: identifiers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for duplicates and adds any missing notifications
|
||||||
|
static func bulkScheduleNotifications(alarms: [Alarm]) {
|
||||||
|
var ids: [String] = []
|
||||||
|
|
||||||
|
UNUserNotificationCenter.current().getPendingNotificationRequests { (notificationRequests) in
|
||||||
|
for notification:UNNotificationRequest in notificationRequests {
|
||||||
|
ids.append(notification.identifier)
|
||||||
|
}
|
||||||
|
for alarm in alarms {
|
||||||
|
if !ids.contains(alarm.notificationID) {
|
||||||
|
scheduleNotification(alarm: alarm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func scheduleNotification(alarm: Alarm) {
|
||||||
|
let content = UNMutableNotificationContent()
|
||||||
|
|
||||||
|
//Date formatting to string
|
||||||
|
let today = Date()
|
||||||
|
let formatter1 = DateFormatter()
|
||||||
|
formatter1.dateFormat = "MMM d, h:mm"
|
||||||
|
|
||||||
|
//Notification content
|
||||||
|
content.title = alarm.text
|
||||||
|
content.subtitle = formatter1.string(from: today)
|
||||||
|
content.body = "Wake method: \(alarm.wakeMethod.name)"
|
||||||
|
content.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "notif.caf"))
|
||||||
|
|
||||||
|
// Notification image content
|
||||||
|
let imageName = "AlarmLogo1"
|
||||||
|
guard let imageURL = Bundle.main.url(forResource: imageName, withExtension: "png") else { return }
|
||||||
|
let attachment = try! UNNotificationAttachment(identifier: imageName, url: imageURL, options: .none)
|
||||||
|
content.attachments = [attachment]
|
||||||
|
|
||||||
|
// Scheduels alarm notification for proper time
|
||||||
|
let interval = alarm.nextActivate!.timeIntervalSince(Date())
|
||||||
|
guard interval > 0 else { return }
|
||||||
|
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: alarm.nextActivate!.timeIntervalSince(Date()), repeats: false)
|
||||||
|
let request = UNNotificationRequest(identifier: alarm.notificationID, content: content, trigger: trigger)
|
||||||
|
|
||||||
|
// Sends notification
|
||||||
|
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
//
|
|
||||||
// NotificationLogic.swift
|
|
||||||
// ProjectClock
|
|
||||||
//
|
|
||||||
// Created by Aaron Saporito on 1/13/21.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CoreMotion
|
|
||||||
import UserNotifications
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
func walkAction() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func jumpAction() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func rpsAction(choice: RPS.Choice) -> Bool? {
|
|
||||||
let rps = RPS()
|
|
||||||
return rps.playRPS(you: choice, computer: RPS.randomComputerChoice())
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handles the core logic behind the factoring alarm
|
|
||||||
func factorAction(puzzleQuestionLabel: UILabel) -> [Int] {
|
|
||||||
let problem = QuadraticProb()
|
|
||||||
|
|
||||||
let answer = problem.getAnswer()
|
|
||||||
let problemString = problem.getProblem()
|
|
||||||
|
|
||||||
puzzleQuestionLabel.text = "Solve: \(problemString)"
|
|
||||||
print("Answer: \(answer)")
|
|
||||||
return answer
|
|
||||||
}
|
|
||||||
|
|
||||||
func smashAction() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -7,123 +7,149 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class StopwatchViewController: UIViewController {
|
/**
|
||||||
|
Stopwatch feature
|
||||||
@IBOutlet weak var hourLabel: UILabel!
|
*/
|
||||||
@IBOutlet weak var minuteLabel: UILabel!
|
class StopwatchViewController: UIViewController
|
||||||
@IBOutlet weak var secondLabel: UILabel!
|
{
|
||||||
|
// UI Components
|
||||||
|
@IBOutlet weak var timeLabel: UILabel!
|
||||||
@IBOutlet weak var startButton: UIButton!
|
@IBOutlet weak var startButton: UIButton!
|
||||||
@IBOutlet weak var stopButton: UIButton!
|
|
||||||
@IBOutlet weak var resetButton: UIButton!
|
@IBOutlet weak var resetButton: UIButton!
|
||||||
@IBOutlet weak var lapButton: UIButton!
|
|
||||||
|
|
||||||
@IBOutlet weak var tableView: UITableView!
|
@IBOutlet weak var tableView: UITableView!
|
||||||
|
|
||||||
|
// Time Components
|
||||||
var hours = 0
|
var hours = 0
|
||||||
var minutes = 0
|
var minutes = 0
|
||||||
var seconds = 0
|
var seconds = 0
|
||||||
|
var started = false
|
||||||
|
|
||||||
var lappedTimes: [String] = []
|
var lappedTimes: [String] = []
|
||||||
var timer = Timer()
|
var timer = Timer()
|
||||||
|
|
||||||
override func viewDidLoad() {
|
/**
|
||||||
super.viewDidLoad()
|
Start/stop stopwatch
|
||||||
|
*/
|
||||||
//lapButton.isHidden = true
|
@IBAction func start(_ sender: UIButton)
|
||||||
}
|
{
|
||||||
|
if !started
|
||||||
@IBAction func start(_ sender: UIButton) {
|
{
|
||||||
|
// Start timer
|
||||||
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(count), userInfo: nil, repeats: true)
|
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(count), userInfo: nil, repeats: true)
|
||||||
//startButton.isHidden = true
|
started = true
|
||||||
//lapButton.isHidden = false
|
startButton.setTitle("Stop", for: .normal)
|
||||||
|
resetButton.setTitle("Lap", for: .normal)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Stop timer
|
||||||
|
timer.invalidate()
|
||||||
|
started = false
|
||||||
|
startButton.setTitle("Start", for: .normal)
|
||||||
|
resetButton.setTitle("Reset", for: .normal)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc fileprivate func count() {
|
@objc fileprivate func count()
|
||||||
|
{
|
||||||
|
// Add time (If it goes longer than 24 hours, the hour count should go to 25)
|
||||||
seconds += 1
|
seconds += 1
|
||||||
|
|
||||||
if seconds == 60 {
|
if seconds == 60
|
||||||
|
{
|
||||||
minutes += 1
|
minutes += 1
|
||||||
seconds = 0
|
seconds = 0
|
||||||
}
|
}
|
||||||
if minutes == 60 {
|
if minutes == 60
|
||||||
|
{
|
||||||
hours += 1
|
hours += 1
|
||||||
minutes = 0
|
minutes = 0
|
||||||
}
|
}
|
||||||
if hours == 24 {
|
|
||||||
resetTimes()
|
// Set label text
|
||||||
|
timeLabel.text = String(format: "%02i:%02i:%02i", hours, minutes, seconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
if seconds >= 10 { secondLabel.text = "\(seconds)" }
|
/**
|
||||||
else { secondLabel.text = "0\(seconds)" }
|
Lap/reset button
|
||||||
if minutes >= 10 { minuteLabel.text = "\(minutes)" }
|
*/
|
||||||
else { minuteLabel.text = "0\(minutes)" }
|
@IBAction func lapOrReset(_ sender: UIButton)
|
||||||
if hours >= 10 { hourLabel.text = "\(hours)" }
|
{
|
||||||
else { hourLabel.text = "0\(hours)" }
|
if started
|
||||||
|
{
|
||||||
|
// Insert lap
|
||||||
|
lappedTimes.insert(timeLabel.text!, at: 0)
|
||||||
|
tableView.reloadData()
|
||||||
}
|
}
|
||||||
|
else
|
||||||
@IBAction func stop(_ sender: UIButton) {
|
{
|
||||||
timer.invalidate()
|
// Reset
|
||||||
//startButton.isHidden = false
|
|
||||||
}
|
|
||||||
|
|
||||||
@IBAction func reset(_ sender: UIButton) {
|
|
||||||
resetTimes()
|
|
||||||
}
|
|
||||||
|
|
||||||
func resetTimes() {
|
|
||||||
seconds = 0
|
seconds = 0
|
||||||
minutes = 0
|
minutes = 0
|
||||||
seconds = 0
|
seconds = 0
|
||||||
lappedTimes = []
|
lappedTimes = []
|
||||||
timer.invalidate()
|
timeLabel.text = "00:00:00"
|
||||||
secondLabel.text = "00"
|
|
||||||
minuteLabel.text = "00"
|
|
||||||
hourLabel.text = "00"
|
|
||||||
tableView.reloadData()
|
tableView.reloadData()
|
||||||
//startButton.isHidden = false
|
|
||||||
//lapButton.isHidden = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func lap(_ sender: UIButton) {
|
|
||||||
var currentSec = ""
|
|
||||||
if seconds >= 10 { currentSec = "\(seconds)" }
|
|
||||||
else { currentSec = "0\(seconds)" }
|
|
||||||
|
|
||||||
var currentMin = ""
|
|
||||||
if minutes >= 10 { currentMin = "\(minutes)" }
|
|
||||||
else { currentMin = "0\(minutes)" }
|
|
||||||
|
|
||||||
var currentHour = ""
|
|
||||||
if hours >= 10 { currentHour = "\(hours)" }
|
|
||||||
else { currentHour = "0\(hours)" }
|
|
||||||
|
|
||||||
let currentTime = "\(currentHour):\(currentMin):\(currentSec)" //CHECK THIS
|
|
||||||
lappedTimes.append(currentTime)
|
|
||||||
|
|
||||||
let indexPath = IndexPath(row: lappedTimes.count - 1, section: 0)
|
|
||||||
tableView.insertRows(at: [indexPath], with: .automatic)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension StopwatchViewController: UITableViewDelegate, UITableViewDataSource {
|
/**
|
||||||
|
Table data source
|
||||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
*/
|
||||||
|
extension StopwatchViewController: UITableViewDelegate, UITableViewDataSource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
Define row count
|
||||||
|
*/
|
||||||
|
func tableView(_ _: UITableView, numberOfRowsInSection _: Int) -> Int
|
||||||
|
{
|
||||||
return lappedTimes.count
|
return lappedTimes.count
|
||||||
}
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
/**
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: "lapCell", for: indexPath)
|
Set cell at i
|
||||||
cell.textLabel?.text = lappedTimes[indexPath.row]
|
*/
|
||||||
|
func tableView(_ view: UITableView, cellForRowAt i: IndexPath) -> UITableViewCell
|
||||||
|
{
|
||||||
|
let cell = view.dequeueReusableCell(withIdentifier: "lapCell", for: i)
|
||||||
|
cell.textLabel?.text = lappedTimes[i.row]
|
||||||
cell.selectionStyle = .none
|
cell.selectionStyle = .none
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
|
/**
|
||||||
if editingStyle == .delete {
|
Swipe left to delete cells at i
|
||||||
lappedTimes.remove(at: indexPath.row)
|
*/
|
||||||
|
func tableView(_ view: UITableView, commit: UITableViewCell.EditingStyle, forRowAt i: IndexPath)
|
||||||
|
{
|
||||||
|
if commit == .delete
|
||||||
|
{
|
||||||
|
lappedTimes.remove(at: i.row)
|
||||||
|
view.deleteRows(at: [i], with: .automatic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tableView.deleteRows(at: [indexPath], with: .automatic)
|
/**
|
||||||
|
Class to set relative font size for the stopwatch
|
||||||
|
*/
|
||||||
|
class StopwatchText: UILabel
|
||||||
|
{
|
||||||
|
@IBInspectable var iPhoneFontSize: CGFloat = 0
|
||||||
|
{
|
||||||
|
didSet
|
||||||
|
{
|
||||||
|
overrideFontSize(iPhoneFontSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func overrideFontSize(_ fontSize: CGFloat)
|
||||||
|
{
|
||||||
|
let size = UIScreen.main.bounds.size
|
||||||
|
let width = UIDevice.current.orientation.isPortrait ? size.width : size.height
|
||||||
|
|
||||||
|
// ViewWidth-based font size
|
||||||
|
font = font.withSize(0.22 * width)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import UserNotifications
|
import UserNotifications
|
||||||
|
|
||||||
class TestingViewController: UIViewController
|
class DebugViewController: UIViewController
|
||||||
{
|
{
|
||||||
override func viewDidLoad()
|
override func viewDidLoad()
|
||||||
{
|
{
|
||||||
@@ -27,38 +27,13 @@ class TestingViewController: UIViewController
|
|||||||
//Sends a test notification
|
//Sends a test notification
|
||||||
@IBAction func sendNotification(_ sender: Any)
|
@IBAction func sendNotification(_ sender: Any)
|
||||||
{
|
{
|
||||||
let alarm = Alarm(hour: 7, minute: 20, text: "Good morning!", wakeMethod: WVM(name: "walking", desc: "Walk"))
|
Notification(alarm: Alarms.fromLocal().listEnabled[0]).scheduleNotification()
|
||||||
|
|
||||||
let content = UNMutableNotificationContent()
|
|
||||||
|
|
||||||
//Date formatting to string
|
|
||||||
let today = Date()
|
|
||||||
let formatter1 = DateFormatter()
|
|
||||||
formatter1.dateStyle = .long
|
|
||||||
|
|
||||||
//Notification content
|
|
||||||
content.title = alarm.text
|
|
||||||
content.subtitle = formatter1.string(from: today)
|
|
||||||
content.body = "Wake method: \(alarm.wakeMethod.name)"
|
|
||||||
|
|
||||||
// Notification image content
|
|
||||||
let imageName = "clock"
|
|
||||||
guard let imageURL = Bundle.main.url(forResource: imageName, withExtension: "png") else { return }
|
|
||||||
let attachment = try! UNNotificationAttachment(identifier: imageName, url: imageURL, options: .none)
|
|
||||||
content.attachments = [attachment]
|
|
||||||
|
|
||||||
// Readies notification to be sent
|
|
||||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false)
|
|
||||||
let request = UNNotificationRequest(identifier: "notification.id.01", content: content, trigger: trigger)
|
|
||||||
|
|
||||||
// Sends notification
|
|
||||||
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func addAlarm(_ sender: Any)
|
@IBAction func addAlarm(_ sender: Any)
|
||||||
{
|
{
|
||||||
let (h, m, _) = Date().getHMS()
|
let (h, m, _) = Date().getHMS()
|
||||||
Alarms.fromLocal().apply { $0.list.append(Alarm(hour: h, minute: m, text: "Test alarm - \(h * m)", wakeMethod: wvms[1], repeats: [true, true, true, true, true, true, true], oneTime: true, lastActivate: Date().added(.minute, -1))) }.localSave()
|
Alarms.fromLocal().apply { $0.list.append(Alarm(hour: h, minute: m, text: "Test alarm - \(h * m)", wakeMethod: wvms[1], repeats: [true, true, true, true, true, true, true], lastActivate: Date().added(.minute, -1))) }.localSave()
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func deleteAlarm(_ sender: Any)
|
@IBAction func deleteAlarm(_ sender: Any)
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ import UIKit
|
|||||||
extension Date
|
extension Date
|
||||||
{
|
{
|
||||||
/// Add toString to Date
|
/// Add toString to Date
|
||||||
func str() -> String
|
func str(_ format: String = "yyyy-MM-dd hh:mm:ss") -> String
|
||||||
{
|
{
|
||||||
let f = DateFormatter()
|
let f = DateFormatter()
|
||||||
f.dateFormat = "yyyy-MM-dd hh:mm:ss"
|
f.dateFormat = format
|
||||||
return f.string(from: self)
|
return f.string(from: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +76,7 @@ extension TimeInterval
|
|||||||
{
|
{
|
||||||
if days != 0 { return "\(days)d \(hours)h \(minutes)m \(seconds)s" }
|
if days != 0 { return "\(days)d \(hours)h \(minutes)m \(seconds)s" }
|
||||||
else if hours != 0 { return "\(hours)h \(minutes)m \(seconds)s" }
|
else if hours != 0 { return "\(hours)h \(minutes)m \(seconds)s" }
|
||||||
|
else if days != 0 && hours == 0 { return "\(days)d \(minutes)m \(seconds)s"}
|
||||||
else if minutes != 0 { return "\(minutes)m \(seconds)s" }
|
else if minutes != 0 { return "\(minutes)m \(seconds)s" }
|
||||||
else { return "\(seconds)s" }
|
else { return "\(seconds)s" }
|
||||||
}
|
}
|
||||||
@@ -107,6 +108,7 @@ extension Digest
|
|||||||
extension String
|
extension String
|
||||||
{
|
{
|
||||||
var sha256: String { SHA256.hash(data: self.data(using: .utf8)!).b64 }
|
var sha256: String { SHA256.hash(data: self.data(using: .utf8)!).b64 }
|
||||||
|
var csv: [String] { components(separatedBy: ";") }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -121,13 +123,13 @@ extension UIViewController
|
|||||||
- Parameter okayable: Whether the alert can be okayed
|
- Parameter okayable: Whether the alert can be okayed
|
||||||
*/
|
*/
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func alert(_ title: String, _ message: String, okayable: Bool = false) -> UIAlertController
|
func alert(_ title: String, _ message: String, okayable: Bool = false, _ completion: (() -> Void)? = nil) -> UIAlertController
|
||||||
{
|
{
|
||||||
// Create alert
|
// Create alert
|
||||||
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||||
|
|
||||||
// Add okay button if it's okayable
|
// Add okay button if it's okayable
|
||||||
if okayable { alert.addAction(UIAlertAction(title: "OK", style: .default)) }
|
if okayable { alert.addAction(UIAlertAction(title: "OK", style: .default) { it in if let c = completion { c() } }) }
|
||||||
|
|
||||||
// Display alert
|
// Display alert
|
||||||
self.present(alert, animated: true, completion: nil)
|
self.present(alert, animated: true, completion: nil)
|
||||||
@@ -136,7 +138,10 @@ extension UIViewController
|
|||||||
|
|
||||||
/// A message is an okayable alert
|
/// A message is an okayable alert
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func msg(_ title: String, _ message: String) -> UIAlertController { alert(title, message, okayable: true) }
|
func msg(_ title: String, _ message: String, _ completion: (() -> Void)? = nil) -> UIAlertController
|
||||||
|
{
|
||||||
|
alert(title, message, okayable: true, completion)
|
||||||
|
}
|
||||||
|
|
||||||
/// More convenient dismiss function
|
/// More convenient dismiss function
|
||||||
func dismiss(_ completion: (() -> Void)? = nil) { ui { self.dismiss(animated: false, completion: completion) } }
|
func dismiss(_ completion: (() -> Void)? = nil) { ui { self.dismiss(animated: false, completion: completion) } }
|
||||||
@@ -144,24 +149,59 @@ extension UIViewController
|
|||||||
/**
|
/**
|
||||||
Send a http request even more conveniently
|
Send a http request even more conveniently
|
||||||
*/
|
*/
|
||||||
func sendReq<T: Decodable>(_ api: API<T>, title: String, errors: [String: String] = [:], params: [String: String]? = [:], _ success: @escaping (T) -> Void, err: @escaping (String) -> Void = {it in})
|
func sendReq<T: Decodable>(_ api: API<T>, title: String, errors: [String: String] = [:], params: [String: String]? = [:], _ success: @escaping (T) -> Void, err: ((String) -> Void)? = nil)
|
||||||
{
|
{
|
||||||
// Send request
|
// Send request
|
||||||
let a = alert(title, "Please Wait")
|
let a = alert(title, "Please Wait")
|
||||||
send(api, params) { it in a.dismiss { success(it) } }
|
send(api, params) { it in a.dismiss { success(it) } }
|
||||||
err:
|
err:
|
||||||
{
|
{
|
||||||
|
// Call callback error function
|
||||||
|
if let err = err { err($0); return }
|
||||||
|
|
||||||
// Display error message
|
// Display error message
|
||||||
print("===== Error: \($0) =====")
|
print("===== Error: \($0) =====")
|
||||||
let message = errors[$0.trimmingCharacters(in: .whitespaces)]
|
let message = errors[$0.trimmingCharacters(in: .whitespaces)]
|
||||||
?? "Maybe the server is on fire, just wait a few hours."
|
?? "Maybe the server is on fire, just wait a few hours. (Error: \($0))"
|
||||||
a.dismiss { self.msg("An error occurred", message) }
|
a.dismiss { self.msg("An error occurred", message) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Asks the user to enter a pin
|
||||||
|
*/
|
||||||
|
func enterPin(_ title: String = "Enter Pin", _ message: String = "Please enter your family pin.", _ then: @escaping (String) -> Void)
|
||||||
|
{
|
||||||
|
// Create alert
|
||||||
|
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertController.Style.alert)
|
||||||
|
|
||||||
|
// Add next button
|
||||||
|
alert.addAction(UIAlertAction(title: "Next", style: UIAlertAction.Style.default) { it in
|
||||||
|
let t = alert.textFields![0] as UITextField
|
||||||
|
then(t.text!)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add pin text field
|
||||||
|
alert.addTextField(configurationHandler: { (t: UITextField!) in
|
||||||
|
t.placeholder = "Enter Pin"
|
||||||
|
t.isSecureTextEntry = true
|
||||||
|
})
|
||||||
|
|
||||||
|
// Present alert
|
||||||
|
self.present(alert, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UIView
|
||||||
|
{
|
||||||
|
func hide(_ hidden: Bool = true) { isHidden = hidden }
|
||||||
|
func show(_ shown: Bool = true) { hide(!shown) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Regex Matching (Credit: https://www.hackingwithswift.com/articles/108/how-to-use-regular-expressions-in-swift)
|
/**
|
||||||
|
Regex Matching (Credit: https://www.hackingwithswift.com/articles/108/how-to-use-regular-expressions-in-swift)
|
||||||
|
*/
|
||||||
extension NSRegularExpression
|
extension NSRegularExpression
|
||||||
{
|
{
|
||||||
convenience init(_ pattern: String)
|
convenience init(_ pattern: String)
|
||||||
@@ -177,6 +217,9 @@ extension NSRegularExpression
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
String convenience functions
|
||||||
|
*/
|
||||||
extension String
|
extension String
|
||||||
{
|
{
|
||||||
static func ~= (lhs: String, rhs: String) -> Bool
|
static func ~= (lhs: String, rhs: String) -> Bool
|
||||||
@@ -185,12 +228,29 @@ extension String
|
|||||||
let range = NSRange(location: 0, length: lhs.utf16.count)
|
let range = NSRange(location: 0, length: lhs.utf16.count)
|
||||||
return regex.firstMatch(in: lhs, options: [], range: range) != nil
|
return regex.firstMatch(in: lhs, options: [], range: range) != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Better subscripting from: https://stackoverflow.com/a/46627527
|
||||||
|
subscript (bounds: CountableClosedRange<Int>) -> String
|
||||||
|
{
|
||||||
|
let start = index(startIndex, offsetBy: bounds.lowerBound)
|
||||||
|
let end = index(startIndex, offsetBy: bounds.upperBound)
|
||||||
|
return String(self[start...end])
|
||||||
|
}
|
||||||
|
|
||||||
|
subscript (bounds: CountableRange<Int>) -> String
|
||||||
|
{
|
||||||
|
let start = index(startIndex, offsetBy: bounds.lowerBound)
|
||||||
|
let end = index(startIndex, offsetBy: bounds.upperBound)
|
||||||
|
return String(self[start..<end])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// More convenient ui update closure
|
/// More convenient ui update closure
|
||||||
func ui(closure: @escaping () -> Void) { DispatchQueue.main.async { closure() } }
|
func ui(closure: @escaping () -> Void) { DispatchQueue.main.async { closure() } }
|
||||||
|
|
||||||
/// More convenient UserDefaults access (Credit: https://gist.github.com/Otbivnoe/04b8bd7984fba0cb58ca7f136fd95582)
|
/**
|
||||||
|
More convenient UserDefaults access (Credit: https://gist.github.com/Otbivnoe/04b8bd7984fba0cb58ca7f136fd95582)
|
||||||
|
*/
|
||||||
extension UserDefaults
|
extension UserDefaults
|
||||||
{
|
{
|
||||||
subscript<T>(key: String) -> T?
|
subscript<T>(key: String) -> T?
|
||||||
@@ -209,3 +269,15 @@ extension UserDefaults
|
|||||||
set { self[key] = newValue?.rawValue }
|
set { self[key] = newValue?.rawValue }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EndEditingOnReturn: UIViewController, UITextFieldDelegate
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
End editing on return
|
||||||
|
*/
|
||||||
|
func textFieldShouldReturn(_ scoreText: UITextField) -> Bool
|
||||||
|
{
|
||||||
|
self.view.endEditing(true)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||