157 Commits

Author SHA1 Message Date
Hykilpikonna 216204fac5 [O] Optimize Alarm edit 2021-01-28 02:26:25 -05:00
Hykilpikonna 01e01dcd36 [F] Get tone name through list rather than adding another field to Alarm 2021-01-28 02:05:54 -05:00
Hykilpikonna 6f77eb0881 [O] Put notification permission request in the correct location 2021-01-28 02:05:08 -05:00
Aaron 15949b6405 Fixed editing alarm tones. 2021-01-27 22:52:50 -05:00
Aaron 83d304e215 Made alarmTone editable, but localRead() nil error. 2021-01-27 22:44:16 -05:00
Hykilpikonna 240e23743b [+] Add MIT License 2021-01-27 21:48:55 -05:00
Hykilpikonna 3a99049165 [+] Link backend repo 2021-01-27 21:41:08 -05:00
Hykilpikonna 9373aecc86 [-] Remove unused backend code 2021-01-27 21:35:20 -05:00
Dallon Archibald 50d79cfc40 Moved Core Motion Stuff 2021-01-27 21:16:01 -05:00
Hykilpikonna 4d2f363f8a [F] Fix longer alarm sounds cutting off problem 2021-01-27 20:28:31 -05:00
Hykilpikonna 2b0109468c [+] Implement regular math expressions 2021-01-27 20:20:41 -05:00
Hykilpikonna ed86c83d82 [F] Fix typo 2021-01-27 19:49:52 -05:00
Hykilpikonna b900123a09 [F] End editing on return 2021-01-27 19:37:13 -05:00
Hykilpikonna 580b8bdc2f [F] Make sure backup exists and is parseable 2021-01-27 19:23:29 -05:00
Hykilpikonna 81363beb47 [-] Reduce repeated code in add alarm view controller 2021-01-27 19:17:51 -05:00
Hykilpikonna 3a214d2589 [F] Avoid starting multiple alarms at once 2021-01-27 19:04:39 -05:00
Hykilpikonna 664d150ca4 [F] Use linear acceleration rather than x-axis acceleration for shaking 2021-01-27 18:59:26 -05:00
Hykilpikonna 095e332ccb [F] Fix shake layout 2021-01-27 18:34:07 -05:00
Hykilpikonna 33718976f9 [O] Stop motion detection after alarm stopped 2021-01-27 18:32:25 -05:00
Hykilpikonna 7089e480ae [+] Check if accelerometer is available 2021-01-27 18:30:08 -05:00
Hykilpikonna 2313b3cded [F] Implement shake correctly 2021-01-27 18:27:31 -05:00
Hykilpikonna b0c5a2f458 [O] CurrentAlarm should not be optional 2021-01-27 18:27:15 -05:00
Hykilpikonna ba0caaade3 [O] Set maximum value to avoid overflows 2021-01-27 18:19:28 -05:00
Hykilpikonna fa69fc92a2 [F] Fix number displaying as decimal 2021-01-27 18:17:24 -05:00
Hykilpikonna 492320e4d6 [+] Add wvm selector for adding debug alarms 2021-01-27 18:16:35 -05:00
Hykilpikonna 00e55b44cb [O] Optimize factoring problem initialization 2021-01-27 18:07:52 -05:00
Hykilpikonna 3ad22b4a9c [O] Replace isHidden = false with .show() and replace isHidden = true with .hide() 2021-01-27 18:00:14 -05:00
Hykilpikonna 3b735645e0 [+] Add comments to Utils and encapsulate UIView.hide() and .show() 2021-01-27 17:56:43 -05:00
Hykilpikonna ed1fdd34df [O] Remove disabled code and remove smash 2021-01-27 17:52:44 -05:00
Hykilpikonna dba4c6c7d0 [-] Remove unused walk and jump actions 2021-01-27 17:46:05 -05:00
Hykilpikonna 75c9ef4a85 [O] Combine RPS choice into one function 2021-01-27 17:45:13 -05:00
Hykilpikonna bd5b5704d1 [+] Add comments to AlarmActivationViewController 2021-01-27 17:41:17 -05:00
Hykilpikonna 885a3064eb [M] Rename NotificationLogic to AlarmActivationLogic 2021-01-27 17:38:16 -05:00
Hykilpikonna 9385eb908f Merge branch 'main' of github.com:hykilpikonna/ProjectClock into main 2021-01-27 17:37:32 -05:00
Hykilpikonna 656930cb7c [O] Optimize alarm activation code 2021-01-27 17:37:21 -05:00
Andrew 2888765b39 Extra Tones 2021-01-27 16:06:56 -05:00
Aaron 23635612a1 Alarms with the same time, but different repeats/title can be added. 2021-01-27 11:50:31 -05:00
Aaron f82ac09fd3 Added notifications for recurrent alarms. 2021-01-27 11:32:21 -05:00
Aaron 5378764e37 Removed obsolete code. 2021-01-27 11:08:34 -05:00
Aaron 91e58ab328 Fixed ringtone selection. 2021-01-27 11:07:20 -05:00
Hykilpikonna a267d00d59 [M] Rename readme heading 2021-01-27 09:46:27 -05:00
Hykilpikonna 5b0a0a80f3 [O] Fix AddAlarmViewController indents 2021-01-27 09:44:30 -05:00
Hykilpikonna 1e44b0548c [M] Rename modules in storyboard 2021-01-27 09:44:10 -05:00
Hykilpikonna 64319e16d9 [M] Add contents.xcworkspacedata 2021-01-27 09:43:50 -05:00
Hykilpikonna e4d25a2dde [M] Change project name to GetGoing 2021-01-27 09:42:59 -05:00
Hykilpikonna d36358fa7f [F] Switch to production server 2021-01-27 08:54:07 -05:00
Hykilpikonna fcf280dfca [+] Sort alarms on save 2021-01-27 08:41:54 -05:00
Hykilpikonna b98c99a2da [F] Fix PM still displaying 24h hour 2021-01-27 08:41:40 -05:00
Andrew 0a50eb6dd8 Tagged both PickerView...Still no luck :( 2021-01-27 00:44:08 -05:00
Andrew fcf3c6d312 Need help adding two UIPickers to one view 2021-01-27 00:09:48 -05:00
Aaron 1b8ba32c3f Added app icon to notification 2021-01-26 23:06:23 -05:00
Aaron 57e5eca0f9 Implemented bulk notification schedueling after restore. 2021-01-26 22:47:16 -05:00
Aaron 65eeea22e8 Restructured Notification. Added BulkSchedueling 2021-01-26 22:46:39 -05:00
Aaron e55736d4d5 Editing an alarm now reschedules the notification 2021-01-26 22:18:18 -05:00
Aaron 53a3616d5c Edit Alarm delete now works. 2021-01-26 21:49:39 -05:00
Aaron 7c626b4f82 Editing alarm now displays correct title. 2021-01-26 21:41:04 -05:00
Hykilpikonna 3cd18e883d [F] Update UI only on UI thread 2021-01-26 21:13:10 -05:00
Hykilpikonna da9fb487d2 Revert "[F] Fix alarm addition scroll view constraints (!)"
This reverts commit ab95dbfe46.
2021-01-26 20:47:42 -05:00
Hykilpikonna d797af3c57 [O] Warn user that they can't undo deleting account 2021-01-26 20:40:28 -05:00
Aaron 4231bc6da1 Fixed theme switching removing all alarms. 2021-01-26 19:46:09 -05:00
Aaron 803696e2a8 Merge branch 'main' of github.com:hykilpikonna/ProjectClock into main 2021-01-26 19:29:34 -05:00
Aaron ba88ea0a0a Readded Equatable to Alarm. 2021-01-26 19:29:28 -05:00
Hykilpikonna 97f1fc6fae Merge branch 'main' of github.com:hykilpikonna/ProjectClock into main 2021-01-26 19:27:48 -05:00
Hykilpikonna 5529e5422c [F] Add null cases for family updates checking 2021-01-26 19:27:42 -05:00
Andrew 8daa15f24c RingTone work... 2021-01-26 19:14:32 -05:00
Hykilpikonna 3bb39b0027 [+] Implement adding alarms sent by family members 2021-01-26 18:06:40 -05:00
Hykilpikonna ddc47e8679 [+] Add get family alarm updates API 2021-01-26 17:41:42 -05:00
Hykilpikonna 3c1c05d40e [+] Implement add alarm to a family member 2021-01-26 17:39:49 -05:00
Hykilpikonna 98025265a8 [+] Add /family/add_alarm API 2021-01-26 17:38:42 -05:00
Hykilpikonna dea555d5c2 [O] Encapsulate Alarm.textLabel 2021-01-26 17:38:02 -05:00
Aaron 8183907366 Merge branch 'main' of github.com:hykilpikonna/ProjectClock into main 2021-01-26 16:23:45 -05:00
Aaron 0de6aeef80 Alarms are now editable. 2021-01-26 16:23:23 -05:00
Dallon Archibald b41c95320e Completed Dark/Light Mode 2021-01-26 16:02:25 -05:00
Aaron 2437c52faf Made Alarm Equatable (==)
Ignores WVM
2021-01-26 15:55:49 -05:00
Dallon Archibald e5772a9705 Dark Mode Button (I'll finish when I get home) 2021-01-26 15:49:10 -05:00
Dallon Archibald 40e2ba1f7b Working on shaking 2021-01-26 15:28:01 -05:00
Hykilpikonna 2b9e54f485 [+] Add family add alarm table 2021-01-26 15:18:56 -05:00
Aaron 253c7641da Added better substrings to Utils 2021-01-26 15:13:55 -05:00
Hykilpikonna ab95dbfe46 [F] Fix alarm addition scroll view constraints (!) 2021-01-26 13:43:47 -05:00
Hykilpikonna 6e82749142 Merge branch 'main' of github.com:hykilpikonna/ProjectClock into main 2021-01-26 13:39:49 -05:00
Hykilpikonna 31b224190b [+] Add alarm selection view 2021-01-26 13:39:42 -05:00
Dallon Archibald b8aedfc28c Starting to Implement Shake Action 2021-01-26 13:23:38 -05:00
Dallon Archibald 4a601dae66 App Icons! :D 2021-01-26 12:59:29 -05:00
Hykilpikonna b263824429 [+] Implement upload and download backup 2021-01-25 23:00:53 -05:00
Hykilpikonna f1b8b794d3 [+] Add refresh family button 2021-01-25 16:38:01 -05:00
Hykilpikonna dcd94fc25c [+] Create family member table 2021-01-25 16:34:23 -05:00
Hykilpikonna bf2be6442a [+] Encapsulate family.memberList 2021-01-25 16:21:27 -05:00
Hykilpikonna 550b0356fb [+] Reload view after leaving/deleting a family 2021-01-25 16:13:54 -05:00
Hykilpikonna d67ba1671e [+] Update info after family change 2021-01-25 16:12:27 -05:00
Hykilpikonna 782ebe8200 [+] Automatically change the family id input to number pad 2021-01-25 16:03:43 -05:00
Hykilpikonna 6ecf5f91d9 [F] Change the stopwatch icon for iOS 13 compatibility 2021-01-25 15:59:34 -05:00
Hykilpikonna 32a29bab38 [+] Add segue to alarm-edit from alarm table cell 2021-01-25 10:27:12 -05:00
Hykilpikonna 6b0302a91d [F] Fix notification interval >0 issue 2021-01-25 10:13:49 -05:00
Hykilpikonna 0b88dfe371 [F] Fix RPS and solve view 2021-01-25 09:50:12 -05:00
Aaron 39578d4c61 Added NotificationID property to Alarm 2021-01-25 09:38:00 -05:00
Hykilpikonna 8a90b216fd [F] Fix switchChange action wasn't connected 2021-01-24 21:18:30 -05:00
Hykilpikonna 76e9daa384 [F] Fix forgot to add fid parameter 2021-01-24 21:17:34 -05:00
Hykilpikonna 7c893f0ae7 [+] Implement join family 2021-01-24 21:15:27 -05:00
Hykilpikonna 2bda42b36d [+] Implement delete family 2021-01-24 21:12:24 -05:00
Hykilpikonna 6aed3cf6b4 [+] Implement leave family 2021-01-24 20:59:54 -05:00
Hykilpikonna 9d4f4c212b [O] Combine join, leave, delete into one action api 2021-01-24 20:54:09 -05:00
Hykilpikonna d84d65f28f [O] Check pin count when updating pins as well 2021-01-24 20:49:11 -05:00
Hykilpikonna f0c32d408d [+] Implement change pin 2021-01-24 20:45:39 -05:00
Hykilpikonna ddc8a459e0 [+] Include fid by default 2021-01-24 20:45:30 -05:00
Hykilpikonna 75c3a6fe23 [+] Create changePin function 2021-01-24 20:32:45 -05:00
Hykilpikonna e8da4650db [+] Create function to prompt the user to enter the pin 2021-01-24 20:32:34 -05:00
Hykilpikonna 7f2a34a99d [+] Switch views if family exists 2021-01-24 20:32:14 -05:00
Hykilpikonna b564b1a836 [F] Fix display not detecting family 2021-01-24 20:16:29 -05:00
Hykilpikonna 4be170e203 [F] Fix "Attempt to insert non-property list object" 2021-01-24 20:14:49 -05:00
Hykilpikonna cb7845499d [+] Display family name in account vc 2021-01-24 20:07:55 -05:00
Hykilpikonna e9387de879 [+] Implement family checking after login 2021-01-24 20:05:35 -05:00
Hykilpikonna 9c21cb61e3 [+] Add familyGet API 2021-01-24 19:50:26 -05:00
Hykilpikonna ff9844ae61 [+] Save family after creation 2021-01-24 19:44:20 -05:00
Hykilpikonna fdfd5b0985 Merge branch 'main' of github.com:hykilpikonna/ProjectClock into main 2021-01-24 19:31:50 -05:00
Hykilpikonna 58a7fe300c [F] Fix alarm completion order 2021-01-24 19:31:44 -05:00
Aaron 50b26b190e Merge branch 'main' of github.com:hykilpikonna/ProjectClock into main 2021-01-24 19:27:26 -05:00
Aaron b7fce3bf31 Added longer, more abnoxious notification sound. 2021-01-24 19:27:18 -05:00
Hykilpikonna e84fb87cd0 [+] Implement create family 2021-01-24 19:14:51 -05:00
Hykilpikonna 30f7a61b7e [+] Generate default name 2021-01-24 19:08:48 -05:00
Hykilpikonna 438bcf922e [+] Check family pin length 2021-01-24 19:05:31 -05:00
Hykilpikonna a42093a0f0 [-] Remove unused User model 2021-01-24 18:59:57 -05:00
Hykilpikonna b4e2c4ebca [F] Fix family segoe 2021-01-24 18:46:39 -05:00
Aaron 1dc05b84cf Merge branch 'main' of github.com:hykilpikonna/ProjectClock into main 2021-01-24 18:38:05 -05:00
Aaron e0fb3ced68 Moved notification permission request to initial VC 2021-01-24 18:37:57 -05:00
Hykilpikonna 9ce66652ab [+] Implement family create/join segue 2021-01-24 18:36:56 -05:00
Hykilpikonna c8484d4cc1 [+] Create family views 2021-01-24 18:36:40 -05:00
Hykilpikonna cdbc7a5dd4 [+] Specify that username and password are both case-sensitive 2021-01-24 18:35:32 -05:00
Aaron 7a94b3fbf8 Notification plays critical sound at max volume. 2021-01-24 18:25:46 -05:00
Aaron 49576828b6 Added alarmTone property to Alarm class. 2021-01-24 18:09:40 -05:00
Aaron 26aa07f4b1 Sends notif for DebugAlarm
(Notifications are only sent when not in app)
2021-01-24 17:47:49 -05:00
Aaron b05064e87b Merge branch 'main' of github.com:hykilpikonna/ProjectClock into main 2021-01-24 17:40:16 -05:00
Aaron 4f31b80707 Schedules alarm notification when added 2021-01-24 17:40:05 -05:00
Dallon Archibald da4ea70b05 Merge branch 'main' of github.com:hykilpikonna/ProjectClock into main 2021-01-24 17:34:41 -05:00
Dallon Archibald 4c5f806dd2 Implemented Accelerometer 2021-01-24 17:34:17 -05:00
Aaron 6d7b19a478 Merge branch 'main' of github.com:hykilpikonna/ProjectClock into main 2021-01-24 17:29:03 -05:00
Aaron acfbf6e0da Renamed TestingViewController to DebugViewController 2021-01-24 17:28:55 -05:00
Dallon Archibald 9f9d5751f6 Testing Basic Accelerometer 2021-01-24 17:26:00 -05:00
Aaron d362b5cd3e Disabled unimplemented WVMs 2021-01-24 16:52:31 -05:00
Aaron 160c96047c Displays repeats in abbreviated forms.
Repeating on weekends now only says weekend, instead of listing the days and so on.
2021-01-24 16:49:11 -05:00
Aaron 90ba42d7f7 Dyamically updates ETA on any modification in AddAlarm 2021-01-24 16:38:50 -05:00
Aaron fb2b92c381 Adjusted autoformatting of TimeInterval string
Removed unneccessary zeroes.
2021-01-24 16:37:21 -05:00
Aaron b758087a96 Dynamically sets time until alarm during addition. 2021-01-24 16:27:15 -05:00
Aaron 599fc8a125 Dynamically sets ActiveAlarm Date and Time 2021-01-24 15:57:26 -05:00
Aaron 54eabb62d2 RPS WVM Completed 2021-01-24 15:40:27 -05:00
Hykilpikonna 7471f71212 [+] Prevent user from adding duplicate alarms 2021-01-24 12:49:45 -05:00
Hykilpikonna 137ee91845 [+] Implement turning on/off an alarm 2021-01-24 12:43:11 -05:00
Hykilpikonna f8f01c0bcb [F] Make oneTime a computed property 2021-01-24 12:33:03 -05:00
Hykilpikonna 4256eb45e8 [F] Properly display alarm enable status 2021-01-24 12:26:29 -05:00
Hykilpikonna 8ae533d801 [O] Clarify comments 2021-01-24 09:12:34 -05:00
Hykilpikonna 6df3790e51 [+] Add comments for table data source 2021-01-24 09:10:59 -05:00
Hykilpikonna 52d5691b5b [S] Make the stopwatch buttons rounded 2021-01-24 09:07:12 -05:00
Hykilpikonna 05cb6f9003 [O] Combine reset and lap button 2021-01-24 09:06:58 -05:00
Hykilpikonna 419726f94e [O] Make start/stop one button 2021-01-24 08:56:54 -05:00
Hykilpikonna 606461d75a [O] Optimize stopwatch implementation 2021-01-24 08:46:25 -05:00
Hykilpikonna e636e80a82 [F] Fix stopwatch layout 2021-01-24 08:37:08 -05:00
Hykilpikonna dda2d48ea0 [F] Fix repeats 2021-01-23 23:54:24 -05:00
Hykilpikonna 3129dc5b80 [PR] Merge back to main branch
[PR] Merge back to main branch
2021-01-23 23:26:48 -05:00
59 changed files with 2230 additions and 1356 deletions
+3
View File
@@ -0,0 +1,3 @@
[submodule "Backend"]
path = Backend
url = https://github.com/VergeDX/clock_api
Submodule
+1
Submodule Backend added at d67bc41886
-37
View File
@@ -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/
-45
View File
@@ -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()
}
Binary file not shown.
-5
View File
@@ -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
-185
View File
@@ -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" "$@"
-89
View File
@@ -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
View File
@@ -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 */; };
4FD642E025B4D5F30069171E /* AlarmActivationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FD642DF25B4D5F30069171E /* AlarmActivationViewController.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 */; };
4FF0684825A5F18800304E6B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4FF0684725A5F18800304E6B /* Assets.xcassets */; };
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 */; };
7C83963625AF375B0027A94C /* NotificationLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C83963525AF375B0027A94C /* NotificationLogic.swift */; };
7C83963925AF68980027A94C /* TestingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C83963825AF68980027A94C /* TestingViewController.swift */; };
7C60741E25C11DC000B0A154 /* AlarmLogo1.png in Resources */ = {isa = PBXBuildFile; fileRef = 7C60741D25C11DC000B0A154 /* AlarmLogo1.png */; };
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 */; };
F0DF7C0725BCD9FC0064A26B /* StopwatchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0DF7C0625BCD9FC0064A26B /* StopwatchViewController.swift */; };
/* End PBXBuildFile section */
@@ -34,18 +36,20 @@
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>"; };
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>"; };
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>"; };
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>"; };
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>"; };
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>"; };
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 /* TestingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestingViewController.swift; sourceTree = "<group>"; };
7C83963825AF68980027A94C /* DebugViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugViewController.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>"; };
F0DF7C0625BCD9FC0064A26B /* StopwatchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopwatchViewController.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -73,7 +77,7 @@
4FF0683C25A5F18700304E6B /* Products */ = {
isa = PBXGroup;
children = (
4FF0683B25A5F18700304E6B /* ProjectClock.app */,
4FF0683B25A5F18700304E6B /* GetGoing.app */,
);
name = Products;
sourceTree = "<group>";
@@ -81,24 +85,26 @@
4FF0683D25A5F18700304E6B /* ProjectClock */ = {
isa = PBXGroup;
children = (
7C5DAE9B25AF812200E44C52 /* clock.png */,
4FF0684C25A5F18800304E6B /* Info.plist */,
7C83962D25AF34F00027A94C /* ProjectClock.entitlements */,
4FF0684925A5F18800304E6B /* LaunchScreen.storyboard */,
4FF0684725A5F18800304E6B /* Assets.xcassets */,
4FF0683E25A5F18700304E6B /* AppDelegate.swift */,
4FF0684225A5F18700304E6B /* AccountViewController.swift */,
4FA419AE25AF93EC004CE0FC /* AlarmViewController.swift */,
7CD385A425BE4639007E9890 /* Sounds */,
4FF0684225A5F18700304E6B /* Account.swift */,
4F8A607025A9160400D88DC3 /* AddAlarmViewController.swift */,
7C83963825AF68980027A94C /* TestingViewController.swift */,
4FD642DF25B4D5F30069171E /* AlarmActivationViewController.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 */,
F0DF7C0625BCD9FC0064A26B /* StopwatchViewController.swift */,
7C83963525AF375B0027A94C /* NotificationLogic.swift */,
4F98955125A9260400F51321 /* Net.swift */,
4F509BD125AE22D100726227 /* Models.swift */,
C7E638E725B88F8B00799512 /* MathExpressions.swift */,
4F509BD125AE22D100726227 /* Models.swift */,
4F98955125A9260400F51321 /* Net.swift */,
7C12BC7725BE25C000E5659C /* Notification.swift */,
7C83962D25AF34F00027A94C /* ProjectClock.entitlements */,
F0DF7C0625BCD9FC0064A26B /* StopwatchViewController.swift */,
4FD642DA25B4B7F60069171E /* Utils.swift */,
);
path = ProjectClock;
@@ -112,12 +118,20 @@
name = Frameworks;
sourceTree = "<group>";
};
7CD385A425BE4639007E9890 /* Sounds */ = {
isa = PBXGroup;
children = (
7CD385A525BE4649007E9890 /* notif.caf */,
);
path = Sounds;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
4FF0683A25A5F18700304E6B /* ProjectClock */ = {
4FF0683A25A5F18700304E6B /* GetGoing */ = {
isa = PBXNativeTarget;
buildConfigurationList = 4FF0684F25A5F18800304E6B /* Build configuration list for PBXNativeTarget "ProjectClock" */;
buildConfigurationList = 4FF0684F25A5F18800304E6B /* Build configuration list for PBXNativeTarget "GetGoing" */;
buildPhases = (
4FF0683725A5F18700304E6B /* Sources */,
4FF0683825A5F18700304E6B /* Frameworks */,
@@ -127,9 +141,9 @@
);
dependencies = (
);
name = ProjectClock;
name = GetGoing;
productName = ProjectClock;
productReference = 4FF0683B25A5F18700304E6B /* ProjectClock.app */;
productReference = 4FF0683B25A5F18700304E6B /* GetGoing.app */;
productType = "com.apple.product-type.application";
};
/* 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";
developmentRegion = en;
hasScannedForEncodings = 0;
@@ -162,7 +176,7 @@
projectDirPath = "";
projectRoot = "";
targets = (
4FF0683A25A5F18700304E6B /* ProjectClock */,
4FF0683A25A5F18700304E6B /* GetGoing */,
);
};
/* End PBXProject section */
@@ -172,10 +186,12 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
7C60741E25C11DC000B0A154 /* AlarmLogo1.png in Resources */,
4FF0684B25A5F18800304E6B /* LaunchScreen.storyboard in Resources */,
7C5DAE9C25AF812200E44C52 /* clock.png in Resources */,
4FF0684825A5F18800304E6B /* Assets.xcassets in Resources */,
4FF0684625A5F18700304E6B /* Main.storyboard in Resources */,
7CD385A625BE4649007E9890 /* notif.caf in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -188,16 +204,16 @@
files = (
4F8A607125A9160400D88DC3 /* AddAlarmViewController.swift in Sources */,
4F98955225A9260400F51321 /* Net.swift in Sources */,
7C83963925AF68980027A94C /* TestingViewController.swift in Sources */,
4FF0684325A5F18700304E6B /* AccountViewController.swift in Sources */,
7C83963925AF68980027A94C /* DebugViewController.swift in Sources */,
4FF0684325A5F18700304E6B /* Account.swift in Sources */,
F0DF7C0725BCD9FC0064A26B /* StopwatchViewController.swift in Sources */,
4FF0683F25A5F18700304E6B /* AppDelegate.swift in Sources */,
4FD642D325B48C380069171E /* AlarmActivator.swift in Sources */,
4FD642DB25B4B7F60069171E /* Utils.swift in Sources */,
4FA419AF25AF93EC004CE0FC /* AlarmViewController.swift in Sources */,
4F509BD225AE22D100726227 /* Models.swift in Sources */,
7C12BC7825BE25C000E5659C /* Notification.swift in Sources */,
C7E638E825B88F8B00799512 /* MathExpressions.swift in Sources */,
7C83963625AF375B0027A94C /* NotificationLogic.swift in Sources */,
4FD642E025B4D5F30069171E /* AlarmActivationViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -353,7 +369,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = org.hydev.ProjectClock;
PRODUCT_BUNDLE_IDENTIFIER = org.hydev.GetGoing;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -373,7 +389,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = org.hydev.ProjectClock;
PRODUCT_BUNDLE_IDENTIFIER = org.hydev.GetGoing;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -383,7 +399,7 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
4FF0683625A5F18700304E6B /* Build configuration list for PBXProject "ProjectClock" */ = {
4FF0683625A5F18700304E6B /* Build configuration list for PBXProject "GetGoing" */ = {
isa = XCConfigurationList;
buildConfigurations = (
4FF0684D25A5F18800304E6B /* Debug */,
@@ -392,7 +408,7 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
4FF0684F25A5F18800304E6B /* Build configuration list for PBXNativeTarget "ProjectClock" */ = {
4FF0684F25A5F18800304E6B /* Build configuration list for PBXNativeTarget "GetGoing" */ = {
isa = XCConfigurationList;
buildConfigurations = (
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>
+21
View File
@@ -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>
+542
View File
@@ -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()
}
}
}
}
}
-179
View File
@@ -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)
}
}
}
+140 -14
View File
@@ -7,8 +7,42 @@
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
@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var scrollViewInner: UIView!
@@ -21,26 +55,46 @@ class AddAlarmViewController: UIViewController
// Pickers
@IBOutlet weak var timePicker: UIDatePicker!
@IBOutlet weak var wvmPicker: UIPickerView!
@IBOutlet weak var ringtonePicker: UIPickerView!
// UI Elements
@IBOutlet weak var repeatWeekdaysSwitch: UISwitch!
@IBOutlet weak var repeatWeekendsSwitch: UISwitch!
@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
*/
@IBAction func cancelAlarmButton(_ sender: Any) {
if editMode {
removeCurrentAlarm()
}
self.dismiss(animated: true, completion: nil)
//might need to reset all UI elements
}
@@ -48,24 +102,68 @@ class AddAlarmViewController: UIViewController
/**
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()
// Create the alarm
let alarm = Alarm(hour: h, minute: m,
text: alarmNameTextField.text ?? "Alarm",
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();
// Dismiss this view
self.dismiss(animated: true, completion: nil)
/**
Dynamically the ETA label for the alarm
*/
func updateETA() {
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
}
}
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
}
}
+133 -63
View File
@@ -7,115 +7,185 @@
import UIKit
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 puzzleQuestionLabel: UILabel!
@IBOutlet weak var puzzleAnswerInput: UITextField!
var puzzleAnswers: [Int] = []
//RPS Outlets
// RPS Outlets
@IBOutlet weak var rpsView: UIView!
@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)
{
self.currentAlarm = currentAlarm
//print(currentAlarm.wakeMethod)
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()
{
super.viewDidLoad()
//Hide all inactive wakemethods
puzzleView.isHidden = true
rpsView.isHidden = true
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(AlarmActivationViewController.playSound), userInfo: nil, repeats: true)
setAlarmType()
//print(MathExpression.random())
// Set the time and date
dateLabel.text = Date().str("MMM d, Y")
timeLabel.text = currentAlarm.timeText
// Hide all inactive wakemethods
puzzleView.hide()
rpsView.hide()
shakeView.hide()
// Play sound
playSound()
vibrate()
// Run alarm
runAlarm()
// End edit on return
puzzleAnswerInput.delegate = self
}
@objc func playSound()
/**
Play alarm sound
*/
func playSound()
{
AudioServicesPlayAlertSound(SystemSoundID(1005))
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate)
AudioServicesPlayAlertSoundWithCompletion(currentAlarm.alarmTone) {
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 {
case "Walk":
walkAction()
case "Jump":
jumpAction()
// Check if the device has accelerometer
var wvm = currentAlarm.wakeMethod.name
if wvm == "Shake" && !motion.isDeviceMotionAvailable
{
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":
self.puzzleAnswers = factorAction(puzzleQuestionLabel: puzzleQuestionLabel)
puzzleView.isHidden = false
case "Smash":
print("")
initFactorProblem()
puzzleView.show()
case "RPS":
rpsView.isHidden = false
//Get Choice here
//rpsAction(choice: choice)
rpsView.show()
case "Shake":
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:
print("Invalid alarm type")
}
}
alarmStarted = true
}
//Verfies and ends factoring WVM
@IBAction func checkBinomialSolution(_ sender: Any) {
if let input = puzzleAnswerInput.text {
if let numericalInput = Int(input) {
if puzzleAnswers.contains(numericalInput) {
func initFactorProblem()
{
let problem = QuadraticProb()
puzzleAnswers = problem.getAnswer()
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()
}
}
}
}
//Gets RPS choice
@IBAction func rockChoice(_ sender: Any) {
if rpsAction(choice: .rock)! {
/**
Gets RPS choice
*/
@IBAction func rpsChoice(_ sender: UIButton)
{
if RPS.playRPS(you: [.rock, .paper, .scissors][sender.tag], computer: RPS.choices.randomElement()!)
{
endAlarm()
} else {
rpsResult.text = "Paper: You lost, try again"
}
}
@IBAction func paperChoice(_ sender: Any) {
if rpsAction(choice: .paper)! {
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"
else
{
rpsResult.text = "\(["Paper", "Scissors", "Rock"][sender.tag]): You lost, try again"
}
}
//Standard way to turn off and close the alarm
func endAlarm() {
timer?.invalidate()
/**
Standard way to turn off and close the alarm
*/
func endAlarm()
{
alarmStarted = false
print("Alarm solved")
dismiss(animated: true, completion: nil)
}
+60 -4
View File
@@ -22,9 +22,24 @@ class AlarmActivator: UITabBarController
var timer: Timer?
var alarm: Alarm?
/// Timer for getting family alarm updates
var familyTimer: Timer?
/**
Called when the app started
*/
override func viewDidLoad()
{
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 }
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 {
$0.lastActivate = Date()
if $0.oneTime { $0.enabled = false }
else {
Notification.scheduleNotification(alarm: alarm)
}
}
alarms.localSave()
self.alarm = alarm
// Segue
//NSLog(JSON.stringify(alarm)!)
performSegue(withIdentifier: "activate-alarm", sender: alarm)
// Avoid starting duplicate alarms
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!)
}
/**
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()
}
}
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

+48 -5
View File
@@ -46,6 +46,14 @@ extension AlarmViewController: UITableViewDelegate, UITableViewDataSource
/// IDK what this does (TODO: Find out what this does)
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 repeatText: 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
//WARNING:Terrible code lies ahead! You WILL be dissapointed! But it works, and that is all that matters.
var alarm: Alarm!
/**
Update information on the cell to information in the alarm object
*/
func setData(_ alarm: Alarm)
{
self.alarm = alarm
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
ampm.text = alarm.hour < 12 || alarm.hour == 24 ? "AM" : "PM"
let hour = alarm.hour <= 12 ? alarm.hour : alarm.hour - 12
time.text = alarm.minute < 10 ? "\(hour):0\(alarm.minute)" : "\(hour):\(alarm.minute)"
var hour = alarm.hour <= 12 ? alarm.hour : alarm.hour - 12
hour = alarm.hour == 0 ? 12 : hour
time.text = String(format: "%i:%02i", hour, alarm.minute)
// displays the specific days alarm is activated
let daysDict = ["Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat"]
var daysActive : [String] = []
if alarm.oneTime {repeatText.text = "No Repeat"}
if alarm.oneTime {repeatText.text = "One-time Alarm"}
else {
for (index, element) in alarm.repeats.enumerated() {
if element {
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: ", ")
}
}
updateActivationTime()
}
func updateActivationTime()
{
// Show next activation date
if alarm.enabled, let n = alarm.nextActivate {
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()
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@@ -1,91 +1,109 @@
{
"images" : [
{
"filename" : "40.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "60.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"filename" : "58.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "87.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"filename" : "80.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "120.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"filename" : "120-1.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "180.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"filename" : "20.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"filename" : "40-1.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "29.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"filename" : "58-1.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "40-2.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"filename" : "80-1.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "76.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"filename" : "152.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"filename" : "167.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"filename" : "appstore.png",
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

File diff suppressed because it is too large Load Diff
+64
View File
@@ -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
}
}
}
+43 -69
View File
@@ -6,6 +6,7 @@
//
import Foundation
import CoreMotion
/**
Math element for problem generation (Credit: https://stackoverflow.com/a/43132311/7346633)
@@ -39,11 +40,10 @@ enum MathOperator : String {
case plus = "+"
case minus = "-"
case multiply = "*"
case divide = "/"
case power = "**"
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)))
return allMathOperators[index]
@@ -82,50 +82,42 @@ class MathExpression : CustomStringConvertible {
return "\(leftString) \(self.op.rawValue) \(rightString)"
}
var result : Int? {
var result: Int {
let format = "\(lhs.nsExpressionFormatString) \(op.rawValue) \(rhs.nsExpressionFormatString)"
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 {
let op: MathOperator = .random()
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
*/
class AlgProb2 : MathExpression {
let a = MathExpression.random()
let b = MathExpression.random()
class MathExpProblem
{
let prob: String
let ans: Int
func getProblem() -> String {
return "\(a) + \(b)"
init(size: Int)
{
var expressions: [String] = []
var answer = 0
for _ in 1...size
{
let exp = MathExpression.random()
expressions.append(exp.description)
answer += exp.result
}
func getAnswer() -> String {
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!)"
prob = expressions.joined(separator: " + ")
ans = answer
}
}
@@ -156,43 +148,25 @@ class QuadraticProb {
}
}
class RPS {
//@IBOutlet weak var resultsLabel: UILabel!
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)
}
/**
Rock paper scissors
*/
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
}
}
+70 -21
View File
@@ -6,51 +6,89 @@
//
import Foundation
struct User: Codable
{
var id: Int
var name: String
var email: String
var pass: String
}
import AVFoundation
struct Family: Codable
{
var fid: Int
var fname: String
var members: [String]
var name: String
var members: String
// 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
{
let index: Int
let name: String
let desc: String
}
let wvms = [
WVM(name: "Factor", desc: "Factor a binomial"),
WVM(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")
WVM(index: 0, name: "Shake", desc: "Shake your phone... aggresively!"),
WVM(index: 1, name: "Math 1", desc: "Easy math expression"),
WVM(index: 2, name: "Math 2", desc: "Medium math expression"),
WVM(index: 3, name: "Math 3", desc: "Hard math expression"),
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 hour: Int // Hour (24)
var minute: Int
var text: String
var wakeMethod: WVM
var alarmTone: SystemSoundID
var notificationID: String
/// What days does it repeat (Sun, Mon, Tue, Wed, Thu, Fri, Sat)
var repeats: [Bool]
/// Does it automatically disable after activating once
var oneTime: Bool
/// When is the last time that the alarm went off
var lastActivate: Date
@@ -58,7 +96,10 @@ class Alarm: Codable
init(enabled: Bool = true,
hour: Int, minute: Int, text: String, wakeMethod: WVM,
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
@@ -67,10 +108,17 @@ class Alarm: Codable
self.text = text
self.wakeMethod = wakeMethod
self.repeats = repeats
self.oneTime = oneTime
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?
var nextActivate: Date?
{
@@ -103,6 +151,7 @@ class Alarms: Codable
/// Save alarms to local storage
func localSave()
{
list.sort { ($0.hour * 60 + $0.minute) < ($1.hour * 60 + $1.minute) }
localStorage.setValue(JSON.stringify(list)!, forKey: "alarms")
// Reload table view
+38 -24
View File
@@ -84,7 +84,7 @@ class APIs
## Returns
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.
@@ -95,13 +95,24 @@ class APIs
## Returns
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
## Parameters (Besides from username and password)
- fname: Family name
- name: Family name
- pin: Admin pin
## Returns
@@ -109,54 +120,56 @@ class APIs
*/
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
## Parameters (Besides from username and password)
- fid: Family ID
- orig_pin: Original admin pin
- new_pin: New admin pin
- oldPin: Original admin pin
- newPin: New admin pin
## 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)
- fid: Family ID
- pin: Admin pin
- action: Join / Leave / Delete
## Returns
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)
- fid: Family ID
- pin: Admin pin
- to: Family member's username
- alarm: Alarm json
## 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() {}
}
@@ -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!["password"] == nil { params!["password"] = localStorage.string(forKey: "pass") }
if params!["fid"] == nil, let f = Family.fromLocal() { params!["fid"] = String(f.fid) }
}
// Create task
+71
View File
@@ -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)
}
}
-42
View File
@@ -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() {
}
Binary file not shown.
+102 -76
View File
@@ -7,123 +7,149 @@
import UIKit
class StopwatchViewController: UIViewController {
@IBOutlet weak var hourLabel: UILabel!
@IBOutlet weak var minuteLabel: UILabel!
@IBOutlet weak var secondLabel: UILabel!
/**
Stopwatch feature
*/
class StopwatchViewController: UIViewController
{
// UI Components
@IBOutlet weak var timeLabel: UILabel!
@IBOutlet weak var startButton: UIButton!
@IBOutlet weak var stopButton: UIButton!
@IBOutlet weak var resetButton: UIButton!
@IBOutlet weak var lapButton: UIButton!
@IBOutlet weak var tableView: UITableView!
// Time Components
var hours = 0
var minutes = 0
var seconds = 0
var started = false
var lappedTimes: [String] = []
var timer = Timer()
override func viewDidLoad() {
super.viewDidLoad()
//lapButton.isHidden = true
}
@IBAction func start(_ sender: UIButton) {
/**
Start/stop stopwatch
*/
@IBAction func start(_ sender: UIButton)
{
if !started
{
// Start timer
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(count), userInfo: nil, repeats: true)
//startButton.isHidden = true
//lapButton.isHidden = false
started = true
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
if seconds == 60 {
if seconds == 60
{
minutes += 1
seconds = 0
}
if minutes == 60 {
if minutes == 60
{
hours += 1
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)" }
if minutes >= 10 { minuteLabel.text = "\(minutes)" }
else { minuteLabel.text = "0\(minutes)" }
if hours >= 10 { hourLabel.text = "\(hours)" }
else { hourLabel.text = "0\(hours)" }
/**
Lap/reset button
*/
@IBAction func lapOrReset(_ sender: UIButton)
{
if started
{
// Insert lap
lappedTimes.insert(timeLabel.text!, at: 0)
tableView.reloadData()
}
@IBAction func stop(_ sender: UIButton) {
timer.invalidate()
//startButton.isHidden = false
}
@IBAction func reset(_ sender: UIButton) {
resetTimes()
}
func resetTimes() {
else
{
// Reset
seconds = 0
minutes = 0
seconds = 0
lappedTimes = []
timer.invalidate()
secondLabel.text = "00"
minuteLabel.text = "00"
hourLabel.text = "00"
timeLabel.text = "00:00:00"
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 {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
/**
Table data source
*/
extension StopwatchViewController: UITableViewDelegate, UITableViewDataSource
{
/**
Define row count
*/
func tableView(_ _: UITableView, numberOfRowsInSection _: Int) -> Int
{
return lappedTimes.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "lapCell", for: indexPath)
cell.textLabel?.text = lappedTimes[indexPath.row]
/**
Set cell at i
*/
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
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
lappedTimes.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
/**
Swipe left to delete cells at i
*/
func tableView(_ view: UITableView, commit: UITableViewCell.EditingStyle, forRowAt i: IndexPath)
{
if commit == .delete
{
lappedTimes.remove(at: i.row)
view.deleteRows(at: [i], 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)
}
}
+3 -28
View File
@@ -8,7 +8,7 @@
import UIKit
import UserNotifications
class TestingViewController: UIViewController
class DebugViewController: UIViewController
{
override func viewDidLoad()
{
@@ -27,38 +27,13 @@ class TestingViewController: UIViewController
//Sends a test notification
@IBAction func sendNotification(_ sender: Any)
{
let alarm = Alarm(hour: 7, minute: 20, text: "Good morning!", wakeMethod: WVM(name: "walking", desc: "Walk"))
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)
Notification(alarm: Alarms.fromLocal().listEnabled[0]).scheduleNotification()
}
@IBAction func addAlarm(_ sender: Any)
{
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)
+81 -9
View File
@@ -13,10 +13,10 @@ import UIKit
extension Date
{
/// Add toString to Date
func str() -> String
func str(_ format: String = "yyyy-MM-dd hh:mm:ss") -> String
{
let f = DateFormatter()
f.dateFormat = "yyyy-MM-dd hh:mm:ss"
f.dateFormat = format
return f.string(from: self)
}
@@ -76,6 +76,7 @@ extension TimeInterval
{
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 days != 0 && hours == 0 { return "\(days)d \(minutes)m \(seconds)s"}
else if minutes != 0 { return "\(minutes)m \(seconds)s" }
else { return "\(seconds)s" }
}
@@ -107,6 +108,7 @@ extension Digest
extension String
{
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
*/
@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
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
// 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
self.present(alert, animated: true, completion: nil)
@@ -136,7 +138,10 @@ extension UIViewController
/// A message is an okayable alert
@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
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
*/
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
let a = alert(title, "Please Wait")
send(api, params) { it in a.dismiss { success(it) } }
err:
{
// Call callback error function
if let err = err { err($0); return }
// Display error message
print("===== Error: \($0) =====")
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) }
}
}
/**
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
{
convenience init(_ pattern: String)
@@ -177,6 +217,9 @@ extension NSRegularExpression
}
}
/**
String convenience functions
*/
extension String
{
static func ~= (lhs: String, rhs: String) -> Bool
@@ -185,12 +228,29 @@ extension String
let range = NSRange(location: 0, length: lhs.utf16.count)
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
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
{
subscript<T>(key: String) -> T?
@@ -209,3 +269,15 @@ extension UserDefaults
set { self[key] = newValue?.rawValue }
}
}
class EndEditingOnReturn: UIViewController, UITextFieldDelegate
{
/**
End editing on return
*/
func textFieldShouldReturn(_ scoreText: UITextField) -> Bool
{
self.view.endEditing(true)
return true
}
}
+2 -2
View File
@@ -1,3 +1,3 @@
# ProjectClock
# GetGoing
TODO: Write this
An alarm clock app that makes sure you woke up.