1
0
Fork 0

Compare commits

..

2 commits

Author SHA1 Message Date
7ff6026c16
1.0.2 2025-03-20 10:52:49 -06:00
48f5c0309d
1.0.2 2025-03-20 10:52:32 -06:00
4 changed files with 487 additions and 626 deletions

View file

@ -395,7 +395,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 6;
CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_ASSET_PATHS = "\"PDSMan/Preview Content\"";
DEVELOPMENT_TEAM = 4BC9Y2LL4B;
ENABLE_PREVIEWS = YES;
@ -412,7 +412,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.1;
MARKETING_VERSION = 1.0.2;
PRODUCT_BUNDLE_IDENTIFIER = com.atridad.PDSMan;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
@ -431,7 +431,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 6;
CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_ASSET_PATHS = "\"PDSMan/Preview Content\"";
DEVELOPMENT_TEAM = 4BC9Y2LL4B;
ENABLE_PREVIEWS = YES;
@ -448,7 +448,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.1;
MARKETING_VERSION = 1.0.2;
PRODUCT_BUNDLE_IDENTIFIER = com.atridad.PDSMan;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";

View file

@ -18,6 +18,11 @@ struct PDSCredentials: Sendable {
}
}
// Shared date formatters to improve performance
extension ISO8601DateFormatter {
static let shared = ISO8601DateFormatter()
}
// Invite code model
struct InviteCode: Identifiable, Sendable {
var id: String
@ -26,6 +31,20 @@ struct InviteCode: Identifiable, Sendable {
var createdAt: Date
var disabled: Bool
var isDisabled: Bool { disabled } // For backwards compatibility
// Returns a formatted date string for display
var formattedDate: String {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
return formatter.string(from: createdAt)
}
// Returns the number of available uses
var availableUses: Int {
let usedCount = uses?.count ?? 0
return max(0, 1 - usedCount) // Assuming default of 1 use per code
}
}
// Invite use model
@ -33,6 +52,20 @@ struct CodeUse: Codable, Identifiable, Sendable {
var id: String { usedBy }
var usedBy: String
var usedAt: String
// Parsed date for the usedAt string
var date: Date? {
ISO8601DateFormatter.shared.date(from: usedAt)
}
// Formatted date for display
var formattedDate: String {
guard let date = date else { return "Unknown date" }
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
return formatter.string(from: date)
}
}
// User model
@ -46,6 +79,14 @@ class PDSUser: Identifiable, Hashable, Sendable, ObservableObject {
@Published var description: String
@Published var isActive: Bool = true
// Formatted date for display
var formattedJoinDate: String {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .none
return formatter.string(from: joinedAt)
}
init(id: String, handle: String, displayName: String, description: String, joinedAt: Date, avatar: URL?, isActive: Bool = true) {
self.id = id
self.handle = handle
@ -66,6 +107,23 @@ class PDSUser: Identifiable, Hashable, Sendable, ObservableObject {
}
}
// Shared DateFormatter for consistent date formatting across the app
extension DateFormatter {
static let shared: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
return formatter
}()
static let dateOnly: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .none
return formatter
}()
}
// Auth state
enum AuthState: Sendable {
case loggedOut

File diff suppressed because it is too large Load diff

View file

@ -27,6 +27,9 @@ struct LoginView: View {
.autocapitalization(.none)
.disableAutocorrection(true)
.keyboardType(.URL)
.placeholder(when: serverURL.isEmpty) {
Text("example.com").foregroundColor(.gray.opacity(0.5))
}
SecureField("Admin Password", text: $password)
.textFieldStyle(RoundedBorderTextFieldStyle())
@ -114,9 +117,9 @@ struct LoginView: View {
.onAppear {
// Add some example connection info
debugLogs.append("Example URLs:")
debugLogs.append("https://bsky.social - Main Bluesky server")
debugLogs.append("https://bsky.atri.dad - Your local server")
debugLogs.append("http://localhost:3000 - Local development PDS")
debugLogs.append("bsky.social - Main Bluesky server")
debugLogs.append("bsky.atri.dad - Your local server")
debugLogs.append("localhost:3000 - Local development PDS")
}
}
@ -131,19 +134,8 @@ struct LoginView: View {
// Add debug info
debugLogs.append("Attempting login to: \(serverURL)")
// Sanitize the URL to ensure it has the correct format
var cleanURL = serverURL.trimmingCharacters(in: .whitespacesAndNewlines)
if !cleanURL.hasPrefix("http://") && !cleanURL.hasPrefix("https://") {
cleanURL = "http://\(cleanURL)"
debugLogs.append("Added http:// prefix: \(cleanURL)")
}
// Remove trailing slash if present
if cleanURL.hasSuffix("/") {
cleanURL.removeLast()
debugLogs.append("Removed trailing slash: \(cleanURL)")
}
// Normalize the URL to ensure it has the correct format
var cleanURL = normalizeServerURL(serverURL)
debugLogs.append("Using final URL: \(cleanURL)")
Task {
@ -157,4 +149,44 @@ struct LoginView: View {
}
}
}
private func normalizeServerURL(_ url: String) -> String {
// Remove any leading/trailing whitespace
var cleanURL = url.trimmingCharacters(in: .whitespacesAndNewlines)
// Strip any existing protocol prefix
if cleanURL.hasPrefix("http://") {
cleanURL = String(cleanURL.dropFirst(7))
debugLogs.append("Removed http:// prefix")
} else if cleanURL.hasPrefix("https://") {
cleanURL = String(cleanURL.dropFirst(8))
debugLogs.append("Removed https:// prefix")
}
// Remove trailing slash if present
if cleanURL.hasSuffix("/") {
cleanURL.removeLast()
debugLogs.append("Removed trailing slash")
}
// Add https:// prefix (always use HTTPS by default)
cleanURL = "https://\(cleanURL)"
debugLogs.append("Added https:// prefix")
return cleanURL
}
}
// Placeholder extension for TextField
extension View {
func placeholder<Content: View>(
when shouldShow: Bool,
alignment: Alignment = .leading,
@ViewBuilder placeholder: () -> Content
) -> some View {
ZStack(alignment: alignment) {
placeholder().opacity(shouldShow ? 1 : 0)
self
}
}
}