WIP
This commit is contained in:
@@ -2,6 +2,15 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>NSAppTransportSecurity</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<key>UIBackgroundModes</key>
|
||||||
|
<array>
|
||||||
|
<string>audio</string>
|
||||||
|
</array>
|
||||||
<key>UILaunchScreen</key>
|
<key>UILaunchScreen</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>UIColorName</key>
|
<key>UIColorName</key>
|
||||||
@@ -9,14 +18,5 @@
|
|||||||
<key>UIImageName</key>
|
<key>UIImageName</key>
|
||||||
<string>AppIcon</string>
|
<string>AppIcon</string>
|
||||||
</dict>
|
</dict>
|
||||||
<key>NSHealthShareUsageDescription</key>
|
|
||||||
<string>Read your heart reate</string>
|
|
||||||
<key>NSHealthUpdateUsageDescription</key>
|
|
||||||
<string>Read your heart reate</string>
|
|
||||||
<key>NSAppTransportSecurity</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSAllowsArbitraryLoads</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
1C485C8A2A492BB400A6F896 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C485C892A492BB400A6F896 /* LoginView.swift */; };
|
1C485C8A2A492BB400A6F896 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C485C892A492BB400A6F896 /* LoginView.swift */; };
|
||||||
1C485C8C2A49D65600A6F896 /* WorkoutHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C485C8B2A49D65600A6F896 /* WorkoutHistoryView.swift */; };
|
1C485C8C2A49D65600A6F896 /* WorkoutHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C485C8B2A49D65600A6F896 /* WorkoutHistoryView.swift */; };
|
||||||
1C485C8D2A49D95700A6F896 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A822A42347D0042FFBD /* Extensions.swift */; };
|
1C485C8D2A49D95700A6F896 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A822A42347D0042FFBD /* Extensions.swift */; };
|
||||||
|
1C6BF28F2A56602B00450FD7 /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C6BF28E2A56602B00450FD7 /* Keychain.swift */; };
|
||||||
1CAF4D8A2A5132F900B00E50 /* PlannedWorkout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CAF4D892A5132F900B00E50 /* PlannedWorkout.swift */; };
|
1CAF4D8A2A5132F900B00E50 /* PlannedWorkout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CAF4D892A5132F900B00E50 /* PlannedWorkout.swift */; };
|
||||||
1CAF4D8C2A51339200B00E50 /* PlannedWorkouts.json in Resources */ = {isa = PBXBuildFile; fileRef = 1CAF4D8B2A51339200B00E50 /* PlannedWorkouts.json */; };
|
1CAF4D8C2A51339200B00E50 /* PlannedWorkouts.json in Resources */ = {isa = PBXBuildFile; fileRef = 1CAF4D8B2A51339200B00E50 /* PlannedWorkouts.json */; };
|
||||||
1CAF4D952A52180600B00E50 /* PlanWorkoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CAF4D942A52180600B00E50 /* PlanWorkoutView.swift */; };
|
1CAF4D952A52180600B00E50 /* PlanWorkoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CAF4D942A52180600B00E50 /* PlanWorkoutView.swift */; };
|
||||||
@@ -111,6 +112,7 @@
|
|||||||
1C485C862A4915C400A6F896 /* CreateWorkoutItemPickerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateWorkoutItemPickerView.swift; sourceTree = "<group>"; };
|
1C485C862A4915C400A6F896 /* CreateWorkoutItemPickerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateWorkoutItemPickerView.swift; sourceTree = "<group>"; };
|
||||||
1C485C892A492BB400A6F896 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
|
1C485C892A492BB400A6F896 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
|
||||||
1C485C8B2A49D65600A6F896 /* WorkoutHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutHistoryView.swift; sourceTree = "<group>"; };
|
1C485C8B2A49D65600A6F896 /* WorkoutHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutHistoryView.swift; sourceTree = "<group>"; };
|
||||||
|
1C6BF28E2A56602B00450FD7 /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = "<group>"; };
|
||||||
1C6D0A3C2A4BEC9700D98B06 /* AVKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVKit.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS9.4.sdk/System/Library/Frameworks/AVKit.framework; sourceTree = DEVELOPER_DIR; };
|
1C6D0A3C2A4BEC9700D98B06 /* AVKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVKit.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS9.4.sdk/System/Library/Frameworks/AVKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||||
1C6D0A3D2A4BEC9700D98B06 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS9.4.sdk/System/Library/Frameworks/AVFoundation.framework; sourceTree = DEVELOPER_DIR; };
|
1C6D0A3D2A4BEC9700D98B06 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS9.4.sdk/System/Library/Frameworks/AVFoundation.framework; sourceTree = DEVELOPER_DIR; };
|
||||||
1C6D0A402A4BECA400D98B06 /* AVKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.4.sdk/System/Library/Frameworks/AVKit.framework; sourceTree = DEVELOPER_DIR; };
|
1C6D0A402A4BECA400D98B06 /* AVKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.4.sdk/System/Library/Frameworks/AVKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||||
@@ -238,6 +240,7 @@
|
|||||||
1CF65A4F2A3A1EA90042FFBD /* BridgeModule.swift */,
|
1CF65A4F2A3A1EA90042FFBD /* BridgeModule.swift */,
|
||||||
1CF65A802A412AA30042FFBD /* DataStore.swift */,
|
1CF65A802A412AA30042FFBD /* DataStore.swift */,
|
||||||
1CF65AB92A4894430042FFBD /* UserStore.swift */,
|
1CF65AB92A4894430042FFBD /* UserStore.swift */,
|
||||||
|
1C6BF28E2A56602B00450FD7 /* Keychain.swift */,
|
||||||
1CF65A3F2A3973840042FFBD /* Views */,
|
1CF65A3F2A3973840042FFBD /* Views */,
|
||||||
1CF65A3E2A39737D0042FFBD /* APIModels */,
|
1CF65A3E2A39737D0042FFBD /* APIModels */,
|
||||||
1CF65A3D2A3973760042FFBD /* Network */,
|
1CF65A3D2A3973760042FFBD /* Network */,
|
||||||
@@ -520,6 +523,7 @@
|
|||||||
1CF65A6B2A3C1EAC0042FFBD /* CreateWorkoutMainView.swift in Sources */,
|
1CF65A6B2A3C1EAC0042FFBD /* CreateWorkoutMainView.swift in Sources */,
|
||||||
1CF65A7B2A3F83440042FFBD /* CreateWorkoutSupersetActionsView.swift in Sources */,
|
1CF65A7B2A3F83440042FFBD /* CreateWorkoutSupersetActionsView.swift in Sources */,
|
||||||
1CF65A262A3972840042FFBD /* Werkout_iosApp.swift in Sources */,
|
1CF65A262A3972840042FFBD /* Werkout_iosApp.swift in Sources */,
|
||||||
|
1C6BF28F2A56602B00450FD7 /* Keychain.swift in Sources */,
|
||||||
1CF65A3C2A3972CE0042FFBD /* ExternalWorkoutDetailView.swift in Sources */,
|
1CF65A3C2A3972CE0042FFBD /* ExternalWorkoutDetailView.swift in Sources */,
|
||||||
1CF65A632A3BF6A30042FFBD /* AllWorkoutsView.swift in Sources */,
|
1CF65A632A3BF6A30042FFBD /* AllWorkoutsView.swift in Sources */,
|
||||||
1CF65A692A3C018F0042FFBD /* AccountView.swift in Sources */,
|
1CF65A692A3C018F0042FFBD /* AccountView.swift in Sources */,
|
||||||
@@ -708,6 +712,8 @@
|
|||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "Werkout-ios-Info.plist";
|
INFOPLIST_FILE = "Werkout-ios-Info.plist";
|
||||||
|
INFOPLIST_KEY_NSHealthShareUsageDescription = "Read your heart reate";
|
||||||
|
INFOPLIST_KEY_NSHealthUpdateUsageDescription = "Read your heart reate";
|
||||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
||||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
|
||||||
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
|
||||||
@@ -748,6 +754,8 @@
|
|||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "Werkout-ios-Info.plist";
|
INFOPLIST_FILE = "Werkout-ios-Info.plist";
|
||||||
|
INFOPLIST_KEY_NSHealthShareUsageDescription = "Read your heart reate";
|
||||||
|
INFOPLIST_KEY_NSHealthUpdateUsageDescription = "Read your heart reate";
|
||||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
||||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
|
||||||
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
|
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
|
||||||
|
|||||||
172
Werkout_ios/Keychain.swift
Normal file
172
Werkout_ios/Keychain.swift
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
//
|
||||||
|
// Keychain.swift
|
||||||
|
// Werkout_ios
|
||||||
|
//
|
||||||
|
// Created by Trey Tartt on 7/5/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class KeychainInterface {
|
||||||
|
enum KeychainError: Error {
|
||||||
|
// Attempted read for an item that does not exist.
|
||||||
|
case itemNotFound
|
||||||
|
|
||||||
|
// Attempted save to override an existing item.
|
||||||
|
// Use update instead of save to update existing items
|
||||||
|
case duplicateItem
|
||||||
|
|
||||||
|
// A read of an item in any format other than Data
|
||||||
|
case invalidItemFormat
|
||||||
|
|
||||||
|
// Any operation result status than errSecSuccess
|
||||||
|
case unexpectedStatus(OSStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
static let serviceID = "werkout.fitness"
|
||||||
|
|
||||||
|
static func save(password: Data, service: String = KeychainInterface.serviceID, account: String) throws {
|
||||||
|
let query: [String: AnyObject] = [
|
||||||
|
// kSecAttrService, kSecAttrAccount, and kSecClass
|
||||||
|
// uniquely identify the item to save in Keychain
|
||||||
|
kSecAttrService as String: service as AnyObject,
|
||||||
|
kSecAttrAccount as String: account as AnyObject,
|
||||||
|
kSecClass as String: kSecClassGenericPassword,
|
||||||
|
kSecAttrSynchronizable as String: kCFBooleanTrue,
|
||||||
|
|
||||||
|
// kSecValueData is the item value to save
|
||||||
|
kSecValueData as String: password as AnyObject
|
||||||
|
]
|
||||||
|
|
||||||
|
// SecItemAdd attempts to add the item identified by
|
||||||
|
// the query to keychain
|
||||||
|
let status = SecItemAdd(
|
||||||
|
query as CFDictionary,
|
||||||
|
nil
|
||||||
|
)
|
||||||
|
|
||||||
|
// errSecDuplicateItem is a special case where the
|
||||||
|
// item identified by the query already exists. Throw
|
||||||
|
// duplicateItem so the client can determine whether
|
||||||
|
// or not to handle this as an error
|
||||||
|
if status == errSecDuplicateItem {
|
||||||
|
throw KeychainError.duplicateItem
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any status other than errSecSuccess indicates the
|
||||||
|
// save operation failed.
|
||||||
|
guard status == errSecSuccess else {
|
||||||
|
throw KeychainError.unexpectedStatus(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func update(password: Data, service: String = KeychainInterface.serviceID, account: String) throws {
|
||||||
|
let query: [String: AnyObject] = [
|
||||||
|
// kSecAttrService, kSecAttrAccount, and kSecClass
|
||||||
|
// uniquely identify the item to update in Keychain
|
||||||
|
kSecAttrService as String: service as AnyObject,
|
||||||
|
kSecAttrAccount as String: account as AnyObject,
|
||||||
|
kSecAttrSynchronizable as String: kCFBooleanTrue,
|
||||||
|
kSecClass as String: kSecClassGenericPassword
|
||||||
|
]
|
||||||
|
|
||||||
|
// attributes is passed to SecItemUpdate with
|
||||||
|
// kSecValueData as the updated item value
|
||||||
|
let attributes: [String: AnyObject] = [
|
||||||
|
kSecValueData as String: password as AnyObject
|
||||||
|
]
|
||||||
|
|
||||||
|
// SecItemUpdate attempts to update the item identified
|
||||||
|
// by query, overriding the previous value
|
||||||
|
let status = SecItemUpdate(
|
||||||
|
query as CFDictionary,
|
||||||
|
attributes as CFDictionary
|
||||||
|
)
|
||||||
|
|
||||||
|
// errSecItemNotFound is a special status indicating the
|
||||||
|
// item to update does not exist. Throw itemNotFound so
|
||||||
|
// the client can determine whether or not to handle
|
||||||
|
// this as an error
|
||||||
|
guard status != errSecItemNotFound else {
|
||||||
|
throw KeychainError.itemNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any status other than errSecSuccess indicates the
|
||||||
|
// update operation failed.
|
||||||
|
guard status == errSecSuccess else {
|
||||||
|
throw KeychainError.unexpectedStatus(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func readPassword(service: String = KeychainInterface.serviceID, account: String) throws -> Data {
|
||||||
|
let query: [String: AnyObject] = [
|
||||||
|
// kSecAttrService, kSecAttrAccount, and kSecClass
|
||||||
|
// uniquely identify the item to read in Keychain
|
||||||
|
kSecAttrService as String: service as AnyObject,
|
||||||
|
kSecAttrAccount as String: account as AnyObject,
|
||||||
|
kSecClass as String: kSecClassGenericPassword,
|
||||||
|
kSecAttrSynchronizable as String: kCFBooleanTrue,
|
||||||
|
|
||||||
|
// kSecMatchLimitOne indicates keychain should read
|
||||||
|
// only the most recent item matching this query
|
||||||
|
kSecMatchLimit as String: kSecMatchLimitOne,
|
||||||
|
|
||||||
|
// kSecReturnData is set to kCFBooleanTrue in order
|
||||||
|
// to retrieve the data for the item
|
||||||
|
kSecReturnData as String: kCFBooleanTrue
|
||||||
|
]
|
||||||
|
|
||||||
|
// SecItemCopyMatching will attempt to copy the item
|
||||||
|
// identified by query to the reference itemCopy
|
||||||
|
var itemCopy: AnyObject?
|
||||||
|
let status = SecItemCopyMatching(
|
||||||
|
query as CFDictionary,
|
||||||
|
&itemCopy
|
||||||
|
)
|
||||||
|
|
||||||
|
// errSecItemNotFound is a special status indicating the
|
||||||
|
// read item does not exist. Throw itemNotFound so the
|
||||||
|
// client can determine whether or not to handle
|
||||||
|
// this case
|
||||||
|
guard status != errSecItemNotFound else {
|
||||||
|
throw KeychainError.itemNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any status other than errSecSuccess indicates the
|
||||||
|
// read operation failed.
|
||||||
|
guard status == errSecSuccess else {
|
||||||
|
throw KeychainError.unexpectedStatus(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This implementation of KeychainInterface requires all
|
||||||
|
// items to be saved and read as Data. Otherwise,
|
||||||
|
// invalidItemFormat is thrown
|
||||||
|
guard let password = itemCopy as? Data else {
|
||||||
|
throw KeychainError.invalidItemFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
return password
|
||||||
|
}
|
||||||
|
|
||||||
|
static func deletePassword(service: String = KeychainInterface.serviceID, account: String) throws {
|
||||||
|
let query: [String: AnyObject] = [
|
||||||
|
// kSecAttrService, kSecAttrAccount, and kSecClass
|
||||||
|
// uniquely identify the item to delete in Keychain
|
||||||
|
kSecAttrService as String: service as AnyObject,
|
||||||
|
kSecAttrAccount as String: account as AnyObject,
|
||||||
|
kSecAttrSynchronizable as String: kCFBooleanTrue,
|
||||||
|
kSecClass as String: kSecClassGenericPassword
|
||||||
|
]
|
||||||
|
|
||||||
|
// SecItemDelete attempts to perform a delete operation
|
||||||
|
// for the item identified by query. The status indicates
|
||||||
|
// if the operation succeeded or failed.
|
||||||
|
let status = SecItemDelete(query as CFDictionary)
|
||||||
|
|
||||||
|
// Any status other than errSecSuccess indicates the
|
||||||
|
// delete operation failed.
|
||||||
|
guard status == errSecSuccess else {
|
||||||
|
throw KeychainError.unexpectedStatus(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,9 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class UserStore: ObservableObject {
|
class UserStore: ObservableObject {
|
||||||
|
static let userNameKeychainValue = "username"
|
||||||
|
static let passwordKeychainValue = "password"
|
||||||
|
|
||||||
static let userDefaultsRegisteredUserKey = "registeredUserKey"
|
static let userDefaultsRegisteredUserKey = "registeredUserKey"
|
||||||
static let shared = UserStore()
|
static let shared = UserStore()
|
||||||
|
|
||||||
@@ -34,6 +37,13 @@ class UserStore: ObservableObject {
|
|||||||
LoginFetchable(postData: postData).fetch(completion: { result in
|
LoginFetchable(postData: postData).fetch(completion: { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let model):
|
case .success(let model):
|
||||||
|
if let email = postData["email"] as? String,
|
||||||
|
let password = postData["password"] as? String,
|
||||||
|
let data = password.data(using: .utf8) {
|
||||||
|
try? KeychainInterface.save(password: data,
|
||||||
|
account: email)
|
||||||
|
}
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.registeredUser = model
|
self.registeredUser = model
|
||||||
let data = try! JSONEncoder().encode(model)
|
let data = try! JSONEncoder().encode(model)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ struct LoginView: View {
|
|||||||
.font(.title)
|
.font(.title)
|
||||||
|
|
||||||
TextField("Email", text: $email)
|
TextField("Email", text: $email)
|
||||||
|
.textContentType(.username)
|
||||||
.autocapitalization(.none)
|
.autocapitalization(.none)
|
||||||
.frame(height: 55)
|
.frame(height: 55)
|
||||||
.textFieldStyle(PlainTextFieldStyle())
|
.textFieldStyle(PlainTextFieldStyle())
|
||||||
@@ -26,7 +27,8 @@ struct LoginView: View {
|
|||||||
.overlay(RoundedRectangle(cornerRadius: 16).stroke(Color(uiColor: .clear))).background(Color(uiColor: .init(red: 200/255, green: 200/255, blue: 200/255, alpha: 0.2)))
|
.overlay(RoundedRectangle(cornerRadius: 16).stroke(Color(uiColor: .clear))).background(Color(uiColor: .init(red: 200/255, green: 200/255, blue: 200/255, alpha: 0.2)))
|
||||||
.cornerRadius(8)
|
.cornerRadius(8)
|
||||||
|
|
||||||
TextField("Password", text: $password)
|
SecureField("Password", text: $password)
|
||||||
|
.textContentType(.password)
|
||||||
.autocapitalization(.none)
|
.autocapitalization(.none)
|
||||||
.frame(height: 55)
|
.frame(height: 55)
|
||||||
.textFieldStyle(PlainTextFieldStyle())
|
.textFieldStyle(PlainTextFieldStyle())
|
||||||
|
|||||||
@@ -330,23 +330,6 @@ struct ExerciseListView: View {
|
|||||||
videoExercise = obj.exercise
|
videoExercise = obj.exercise
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if i == bridgeModule.currentExerciseIdx {
|
|
||||||
HStack {
|
|
||||||
if obj.exercise.isReps {
|
|
||||||
Text("is reps")
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
||||||
}
|
|
||||||
if obj.exercise.isWeight {
|
|
||||||
Text("is weight")
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
||||||
}
|
|
||||||
if obj.exercise.isDuration {
|
|
||||||
Text("is duration")
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: bridgeModule.currentExerciseIdx, perform: { newValue in
|
.onChange(of: bridgeModule.currentExerciseIdx, perform: { newValue in
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>com.apple.developer.associated-domains</key>
|
||||||
|
<array>
|
||||||
|
<string>dev.werkout.fitness</string>
|
||||||
|
</array>
|
||||||
<key>com.apple.developer.healthkit</key>
|
<key>com.apple.developer.healthkit</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.developer.healthkit.access</key>
|
<key>com.apple.developer.healthkit.access</key>
|
||||||
|
|||||||
Reference in New Issue
Block a user