1
0
Fork 0

Added demo mode for app review

This commit is contained in:
Atridad Lahiji 2025-03-19 12:23:49 -06:00
parent bbad8d6948
commit dc17130468
Signed by: atridad
SSH key fingerprint: SHA256:LGomp8Opq0jz+7kbwNcdfTcuaLRb5Nh0k5AchDDb438
5 changed files with 294 additions and 22 deletions

View file

@ -36,14 +36,25 @@ struct CodeUse: Codable, Identifiable, Sendable {
} }
// User model // User model
struct PDSUser: Identifiable, Hashable, Sendable { class PDSUser: Identifiable, Hashable, Sendable, ObservableObject {
var id: String // DID let id: String // DID
var handle: String let joinedAt: Date
var displayName: String let avatar: URL?
var description: String
var joinedAt: Date @Published var handle: String
var avatar: URL? @Published var displayName: String
var isActive: Bool = true @Published var description: String
@Published var isActive: Bool = true
init(id: String, handle: String, displayName: String, description: String, joinedAt: Date, avatar: URL?, isActive: Bool = true) {
self.id = id
self.handle = handle
self.displayName = displayName
self.description = description
self.joinedAt = joinedAt
self.avatar = avatar
self.isActive = isActive
}
// Add Hashable conformance // Add Hashable conformance
func hash(into hasher: inout Hasher) { func hash(into hasher: inout Hasher) {

View file

@ -6,6 +6,11 @@ import Combine
class PDSViewModel: ObservableObject { class PDSViewModel: ObservableObject {
@Published var pdsService = PDSService() @Published var pdsService = PDSService()
@Published var alertItem: AlertItem? @Published var alertItem: AlertItem?
@Published var demoMode: Bool = false
// Mock data for demo mode
private var mockUsers: [PDSUser] = []
private var mockInviteCodes: [InviteCode] = []
var errorMessage: String? { var errorMessage: String? {
pdsService.errorMessage pdsService.errorMessage
@ -17,6 +22,9 @@ class PDSViewModel: ObservableObject {
var isAuthenticated: Bool { var isAuthenticated: Bool {
get { get {
if demoMode {
return true // Always authenticated in demo mode
}
let value = pdsService.isAuthenticated let value = pdsService.isAuthenticated
print("PDSViewModel: isAuthenticated getter called, value = \(value)") print("PDSViewModel: isAuthenticated getter called, value = \(value)")
return value return value
@ -24,11 +32,17 @@ class PDSViewModel: ObservableObject {
} }
var users: [PDSUser] { var users: [PDSUser] {
pdsService.users if demoMode {
return mockUsers
}
return pdsService.users
} }
var inviteCodes: [InviteCode] { var inviteCodes: [InviteCode] {
pdsService.inviteCodes if demoMode {
return mockInviteCodes
}
return pdsService.inviteCodes
} }
// Add listeners for PDSService changes // Add listeners for PDSService changes
@ -48,30 +62,214 @@ class PDSViewModel: ObservableObject {
// Method to manually refresh UI data // Method to manually refresh UI data
func refreshUI() { func refreshUI() {
print("PDSViewModel: refreshUI called")
DispatchQueue.main.async { DispatchQueue.main.async {
print("PDSViewModel: sending objectWillChange notification")
self.objectWillChange.send() self.objectWillChange.send()
if self.demoMode {
print("Demo users status: \(self.mockUsers.map { "\($0.handle): \($0.isActive)" }.joined(separator: ", "))")
}
} }
} }
// Enable demo mode with mock data
func enableDemoMode() {
demoMode = true
createMockData()
refreshUI()
}
// Disable demo mode
func disableDemoMode() {
demoMode = false
refreshUI()
}
// Create mock data for demo mode
private func createMockData() {
// Create mock users
mockUsers = [
PDSUser(
id: "did:plc:abcdef123456",
handle: "alice",
displayName: "Alice Smith",
description: "Product designer and developer. Working on decentralized social apps.",
joinedAt: Date().addingTimeInterval(-86400 * 30), // 30 days ago
avatar: nil,
isActive: true
),
PDSUser(
id: "did:plc:ghijkl789012",
handle: "bob",
displayName: "Bob Johnson",
description: "Software engineer. Interested in decentralized protocols and privacy.",
joinedAt: Date().addingTimeInterval(-86400 * 15), // 15 days ago
avatar: nil,
isActive: true
),
PDSUser(
id: "did:plc:mnopqr345678",
handle: "charlie",
displayName: "Charlie Brown",
description: "Community manager and content creator.",
joinedAt: Date().addingTimeInterval(-86400 * 5), // 5 days ago
avatar: nil,
isActive: false
)
]
// Create mock invite codes
mockInviteCodes = [
InviteCode(
id: "abc-123-xyz",
uses: [],
createdAt: Date().addingTimeInterval(-86400 * 20), // 20 days ago
disabled: false
),
InviteCode(
id: "def-456-uvw",
uses: [
CodeUse(usedBy: "did:plc:abcdef123456", usedAt: ISO8601DateFormatter().string(from: Date().addingTimeInterval(-86400 * 10)))
],
createdAt: Date().addingTimeInterval(-86400 * 25), // 25 days ago
disabled: false
),
InviteCode(
id: "ghi-789-rst",
uses: [],
createdAt: Date().addingTimeInterval(-86400 * 15), // 15 days ago
disabled: true
)
]
}
// Refresh invite codes with UI update // Refresh invite codes with UI update
func refreshInviteCodes() async { func refreshInviteCodes() async {
if demoMode {
// No need to do anything in demo mode, data is already loaded
refreshUI()
return
}
await pdsService.fetchInviteCodes() await pdsService.fetchInviteCodes()
refreshUI() refreshUI()
} }
// Refresh users with UI update // Refresh users with UI update
func refreshUsers() async { func refreshUsers() async {
if demoMode {
// No need to do anything in demo mode, data is already loaded
refreshUI()
return
}
await pdsService.fetchUsers() await pdsService.fetchUsers()
refreshUI() refreshUI()
} }
// Disable invite code with guaranteed UI update // Disable invite code with guaranteed UI update
func disableInviteCode(_ code: String) async -> Bool { func disableInviteCode(_ code: String) async -> Bool {
if demoMode {
// In demo mode, just update our mock data
if let index = mockInviteCodes.firstIndex(where: { $0.id == code }) {
mockInviteCodes[index].disabled = true
refreshUI()
return true
}
return false
}
let result = await pdsService.disableInviteCode(code) let result = await pdsService.disableInviteCode(code)
refreshUI() refreshUI()
return result return result
} }
// Create invite code in demo mode
func createInviteCode() async -> InviteCode? {
if demoMode {
// Generate a random code in demo mode
let randomCode = "demo-\(Int.random(in: 100...999))-\(UUID().uuidString.prefix(4))"
let newCode = InviteCode(
id: randomCode,
uses: [],
createdAt: Date(),
disabled: false
)
mockInviteCodes.append(newCode)
refreshUI()
return newCode
}
let code = await pdsService.createInviteCode()
await refreshInviteCodes()
return code
}
// Suspend user in demo mode
func suspendUser(userId: String) async -> Bool {
if demoMode {
print("Suspending user in demo mode: \(userId)")
return await withCheckedContinuation { continuation in
DispatchQueue.main.async {
if let user = self.mockUsers.first(where: { $0.id == userId }) {
print("Found user \(user.displayName), setting isActive to false")
user.isActive = false
self.refreshUI()
continuation.resume(returning: true)
} else {
print("User not found in mockUsers: \(userId)")
continuation.resume(returning: false)
}
}
}
}
let success = await pdsService.suspendUser(userId: userId, reason: "Demo suspension")
await refreshUsers()
return success
}
// Reactivate user in demo mode
func reactivateUser(userId: String) async -> Bool {
if demoMode {
print("Reactivating user in demo mode: \(userId)")
return await withCheckedContinuation { continuation in
DispatchQueue.main.async {
if let user = self.mockUsers.first(where: { $0.id == userId }) {
print("Found user \(user.displayName), setting isActive to true")
user.isActive = true
self.refreshUI()
continuation.resume(returning: true)
} else {
print("User not found in mockUsers: \(userId)")
continuation.resume(returning: false)
}
}
}
}
let success = await pdsService.reactivateUser(userId: userId)
await refreshUsers()
return success
}
// Edit user handle in demo mode
func editUserHandle(userId: String, newHandle: String) async -> Bool {
if demoMode {
if let user = mockUsers.first(where: { $0.id == userId }) {
user.handle = newHandle
refreshUI()
return true
}
return false
}
let success = await pdsService.editUserHandle(userId: userId, newHandle: newHandle)
await refreshUsers()
return success
}
func login(serverURL: String, username: String, password: String) async { func login(serverURL: String, username: String, password: String) async {
print("PDSViewModel: login called") print("PDSViewModel: login called")
if let credentials = PDSCredentials(serverURL: serverURL, username: username, password: password) { if let credentials = PDSCredentials(serverURL: serverURL, username: username, password: password) {
@ -87,7 +285,11 @@ class PDSViewModel: ObservableObject {
func logout() { func logout() {
print("PDSViewModel: logout called") print("PDSViewModel: logout called")
pdsService.logout() if demoMode {
disableDemoMode()
} else {
pdsService.logout()
}
objectWillChange.send() objectWillChange.send()
} }
} }

View file

@ -83,12 +83,22 @@ struct InviteCodesView: View {
} }
.navigationTitle("Invite Codes") .navigationTitle("Invite Codes")
.toolbar { .toolbar {
ToolbarItem(placement: .navigationBarLeading) {
if viewModel.demoMode {
Text("DEMO MODE")
.font(.caption)
.padding(5)
.background(Color.green.opacity(0.2))
.foregroundColor(.green)
.cornerRadius(4)
}
}
ToolbarItem(placement: .navigationBarTrailing) { ToolbarItem(placement: .navigationBarTrailing) {
Button { Button {
isCreatingCode = true isCreatingCode = true
Task { Task {
await viewModel.pdsService.createInviteCode() await viewModel.createInviteCode()
await viewModel.refreshInviteCodes()
isCreatingCode = false isCreatingCode = false
} }
} label: { } label: {

View file

@ -64,11 +64,32 @@ struct LoginView: View {
.cornerRadius(8) .cornerRadius(8)
} }
Button("Toggle Debug Info") { Divider()
.padding(.vertical, 10)
.padding(.horizontal, 40)
Button {
enterDemoMode()
} label: {
HStack {
Image(systemName: "rectangle.fill.on.rectangle.fill.circle")
.imageScale(.medium)
Text("Enter Demo Mode")
}
.frame(maxWidth: .infinity)
.padding()
}
.background(Color.green)
.foregroundColor(.white)
.cornerRadius(10)
.padding(.horizontal, 40)
Button("Show Debug Info") {
showingDebugInfo.toggle() showingDebugInfo.toggle()
} }
.font(.caption) .font(.caption)
.padding(.top, 20) .foregroundColor(.secondary)
.padding(.top, 10)
if showingDebugInfo { if showingDebugInfo {
ScrollView { ScrollView {
@ -99,6 +120,11 @@ struct LoginView: View {
} }
} }
private func enterDemoMode() {
debugLogs.append("Entering demo mode...")
viewModel.enableDemoMode()
}
private func login() { private func login() {
isLoggingIn = true isLoggingIn = true

View file

@ -72,6 +72,18 @@ struct UserListView: View {
} }
} }
.navigationTitle("Users") .navigationTitle("Users")
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
if viewModel.demoMode {
Text("DEMO MODE")
.font(.caption)
.padding(5)
.background(Color.green.opacity(0.2))
.foregroundColor(.green)
.cornerRadius(4)
}
}
}
.refreshable { .refreshable {
print("⏳ Pull-to-refresh: Fetching users") print("⏳ Pull-to-refresh: Fetching users")
await viewModel.refreshUsers() await viewModel.refreshUsers()
@ -93,9 +105,8 @@ struct UserListView: View {
Button("Save") { Button("Save") {
Task { Task {
if await viewModel.pdsService.editUserHandle(userId: user.id, newHandle: newHandle) { if await viewModel.editUserHandle(userId: user.id, newHandle: newHandle) {
showingEditSheet = false showingEditSheet = false
await viewModel.refreshUsers() // Refresh to show updated handle
} }
} }
} }
@ -112,9 +123,15 @@ struct UserListView: View {
Button("Cancel", role: .cancel) { } Button("Cancel", role: .cancel) { }
Button("Suspend", role: .destructive) { Button("Suspend", role: .destructive) {
if let user = selectedUser { if let user = selectedUser {
print("Attempting to suspend user: \(user.id)")
Task { Task {
await viewModel.pdsService.suspendUser(userId: user.id, reason: suspensionReason) let success = await viewModel.suspendUser(userId: user.id)
await viewModel.refreshUsers() // Refresh after suspension print("Suspend user result: \(success)")
// Force UI update
await MainActor.run {
viewModel.refreshUI()
}
} }
} }
} }
@ -127,9 +144,15 @@ struct UserListView: View {
Button("Cancel", role: .cancel) { } Button("Cancel", role: .cancel) { }
Button("Reactivate") { Button("Reactivate") {
if let user = selectedUser { if let user = selectedUser {
print("Attempting to reactivate user: \(user.id)")
Task { Task {
await viewModel.pdsService.reactivateUser(userId: user.id) let success = await viewModel.reactivateUser(userId: user.id)
await viewModel.refreshUsers() // Refresh after reactivation print("Reactivate user result: \(success)")
// Force UI update
await MainActor.run {
viewModel.refreshUI()
}
} }
} }
} }
@ -149,7 +172,7 @@ struct UserListView: View {
} }
struct UserRow: View { struct UserRow: View {
let user: PDSUser @ObservedObject var user: PDSUser
var formattedDate: String { var formattedDate: String {
let formatter = DateFormatter() let formatter = DateFormatter()