Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 122 additions & 7 deletions VITTY/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,30 @@
// Created by Ananya George on 11/7/21.
//



import SwiftUI
import StoreKit

struct ContentView: View {
@State private var communityPageViewModel = CommunityPageViewModel()
@State private var suggestedFriendsViewModel = SuggestedFriendsViewModel()
@State private var friendRequestViewModel = FriendRequestViewModel()
@State private var authViewModel = AuthViewModel()
@State private var requestViewModel = RequestsViewModel()

@State private var academicsViewModel = AcademicsViewModel()

var body: some View {
Group {

if authViewModel.loggedInBackendUser != nil {
HomeView()
.onAppear {

ReviewManager.shared.trackAppUsage()
ReviewManager.shared.requestReviewIfAppropriate()
}
}

else if authViewModel.loggedInFirebaseUser != nil {
InstructionView()
}

else {
LoginView()
}
Expand All @@ -38,8 +38,123 @@ struct ContentView: View {
.environment(suggestedFriendsViewModel)
.environment(friendRequestViewModel)
.environment(academicsViewModel)
.environment(requestViewModel)
.environment(requestViewModel).alert("Update Available", isPresented: .constant(UpdateManager.shared.showUpdateAlert)) {
Button("Update Now") {
UpdateManager.shared.openAppStore()
UpdateManager.shared.dismissUpdateAlert()
}

if let updateInfo = UpdateManager.shared.updateInfo, !updateInfo.isForced {
Button("Skip This Version") {
UpdateManager.shared.skipThisVersion()
}

Button("Later") {
UpdateManager.shared.dismissUpdateAlert()
}
}
} message: {
if let updateInfo = UpdateManager.shared.updateInfo {
Text("Version \(updateInfo.latestVersion) is available.\n\n\(updateInfo.releaseNotes)")
}
}
}
}

// MARK: - Review Manager
class ReviewManager: ObservableObject {
static let shared = ReviewManager()

private let reviewRequestKey = "LastReviewRequestDate"
private let hasReviewedKey = "HasUserReviewed"
private let appUsageCountKey = "AppUsageCount"
private let firstLaunchDateKey = "FirstLaunchDate"

// Configuration
private let minimumUsageCount = 10 // Minimum number of app uses before review
private let minimumDaysOfUsage = 7 // Minimum days since first launch
private let monthsInterval: TimeInterval = 30 * 24 * 60 * 60 // 30 days between requests

private init() {

if UserDefaults.standard.object(forKey: firstLaunchDateKey) == nil {
UserDefaults.standard.set(Date(), forKey: firstLaunchDateKey)
}
}

func trackAppUsage() {
let currentCount = UserDefaults.standard.integer(forKey: appUsageCountKey)
UserDefaults.standard.set(currentCount + 1, forKey: appUsageCountKey)
}

func requestReviewIfAppropriate() {

if UserDefaults.standard.bool(forKey: hasReviewedKey) {
return
}


guard meetsUsageRequirements() else {
return
}


guard hasEnoughTimePassed() else {
return
}


DispatchQueue.main.async {
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
SKStoreReviewController.requestReview(in: windowScene)
}
}


UserDefaults.standard.set(Date(), forKey: reviewRequestKey)
}

private func meetsUsageRequirements() -> Bool {
let usageCount = UserDefaults.standard.integer(forKey: appUsageCountKey)

guard let firstLaunchDate = UserDefaults.standard.object(forKey: firstLaunchDateKey) as? Date else {
return false
}

let daysSinceFirstLaunch = Calendar.current.dateComponents([.day], from: firstLaunchDate, to: Date()).day ?? 0

return usageCount >= minimumUsageCount && daysSinceFirstLaunch >= minimumDaysOfUsage
}

private func hasEnoughTimePassed() -> Bool {
guard let lastRequestDate = UserDefaults.standard.object(forKey: reviewRequestKey) as? Date else {
return true
}

let now = Date()
return now.timeIntervalSince(lastRequestDate) >= monthsInterval
}

func markAsReviewed() {
UserDefaults.standard.set(true, forKey: hasReviewedKey)
}

func resetReviewStatus() {
UserDefaults.standard.removeObject(forKey: hasReviewedKey)
UserDefaults.standard.removeObject(forKey: reviewRequestKey)
UserDefaults.standard.removeObject(forKey: appUsageCountKey)
UserDefaults.standard.removeObject(forKey: firstLaunchDateKey)
}

// MARK: - Debug/Testing Methods
func getCurrentUsageCount() -> Int {
return UserDefaults.standard.integer(forKey: appUsageCountKey)
}

func getDaysSinceFirstLaunch() -> Int {
guard let firstLaunchDate = UserDefaults.standard.object(forKey: firstLaunchDateKey) as? Date else {
return 0
}
return Calendar.current.dateComponents([.day], from: firstLaunchDate, to: Date()).day ?? 0
}
}
4 changes: 4 additions & 0 deletions VITTY/VITTY.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
4B7DA5F52D7237BE007354A3 /* CreateGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7DA5F42D7237B8007354A3 /* CreateGroup.swift */; };
4B80972B2E2947A300FF2F63 /* FriendsTimetableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B80972A2E29479800FF2F63 /* FriendsTimetableView.swift */; };
4B80972D2E2ABD8500FF2F63 /* CircleTimetable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B80972C2E2ABD8100FF2F63 /* CircleTimetable.swift */; };
4B8097552E2B7B6300FF2F63 /* UpdateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8097542E2B7B5F00FF2F63 /* UpdateManager.swift */; };
4B8B32CA2D6D75F4004F01BA /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B8B32C92D6D75F4004F01BA /* WidgetKit.framework */; };
4B8B32CB2D6D75F4004F01BA /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3105871C27A3ECBB00C2FC41 /* SwiftUI.framework */; };
4B8B32DC2D6D75F6004F01BA /* VittyWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 4B8B32C82D6D75F4004F01BA /* VittyWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
Expand Down Expand Up @@ -226,6 +227,7 @@
4B7DA5F42D7237B8007354A3 /* CreateGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateGroup.swift; sourceTree = "<group>"; };
4B80972A2E29479800FF2F63 /* FriendsTimetableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendsTimetableView.swift; sourceTree = "<group>"; };
4B80972C2E2ABD8100FF2F63 /* CircleTimetable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleTimetable.swift; sourceTree = "<group>"; };
4B8097542E2B7B5F00FF2F63 /* UpdateManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateManager.swift; sourceTree = "<group>"; };
4B8B32C82D6D75F4004F01BA /* VittyWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = VittyWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
4B8B32C92D6D75F4004F01BA /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
4B8B33742D7029A3004F01BA /* SideBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideBar.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -471,6 +473,7 @@
314A40A127383C0A0058082F /* Utilities */ = {
isa = PBXGroup;
children = (
4B8097542E2B7B5F00FF2F63 /* UpdateManager.swift */,
31128D0A2773003C0084C9EA /* Constants */,
31128D052772FA030084C9EA /* Extensions */,
31128CF12772F55D0084C9EA /* Fonts */,
Expand Down Expand Up @@ -1211,6 +1214,7 @@
4BF03C992D7819E30098C803 /* Notes.swift in Sources */,
52DBBE882B47B6B30014C57A /* FriendCard.swift in Sources */,
4B47CD7B2D7DCB8B00A46FEF /* CreateReminder.swift in Sources */,
4B8097552E2B7B6300FF2F63 /* UpdateManager.swift in Sources */,
317715DE279F1431009A532E /* IndexedCollection.swift in Sources */,
4B7DA5E72D71AC54007354A3 /* CirclesRow.swift in Sources */,
4B7DA5E52D70B2CA007354A3 /* Circles.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ struct FriendReqCard: View {
Task {
let url = URL(
string:
"\(APIConstants.base_url)/api/v2/requests/\(friend.username)/accept/"
"\(APIConstants.base_url)requests/\(friend.username)/accept/"
)!
var request = URLRequest(url: url)

Expand All @@ -50,7 +50,7 @@ struct FriendReqCard: View {
do {
let (_, _) = try await URLSession.shared.data(for: request)
friendRequestViewModel.fetchFriendRequests(
from: URL(string: "\(APIConstants.base_url)/api/v2/requests/")!,
from: URL(string: "\(APIConstants.base_url)requests/")!,
authToken: authViewModel.loggedInBackendUser?.token ?? "",
loading: false
)
Expand All @@ -68,7 +68,7 @@ struct FriendReqCard: View {
Task {
let url = URL(
string:
"\(APIConstants.base_url)/api/v2/requests/\(friend.username)/decline/"
"\(APIConstants.base_url)requests/\(friend.username)/decline/"
)!
var request = URLRequest(url: url)

Expand All @@ -80,7 +80,7 @@ struct FriendReqCard: View {
do {
let (_, _) = try await URLSession.shared.data(for: request)
friendRequestViewModel.fetchFriendRequests(
from: URL(string: "\(APIConstants.base_url)/api/v2/requests/")!,
from: URL(string: "\(APIConstants.base_url)requests/")!,
authToken: authViewModel.loggedInBackendUser?.token ?? "",
loading: false
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ struct FriendRequestView: View {
.scrollContentBackground(.hidden)
.refreshable {
friendRequestViewModel.fetchFriendRequests(
from: URL(string: "\(APIConstants.base_url)/api/v2/requests/")!,
from: URL(string: "\(APIConstants.base_url)requests/")!,
authToken: authViewModel.loggedInBackendUser?.token ?? "",
loading: false
)
Expand Down
76 changes: 41 additions & 35 deletions VITTY/VITTY/Connect/Search/Views/SearchView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,48 +82,54 @@ struct SearchView: View {

Spacer()
}
.frame(maxWidth: .infinity)
.frame(maxWidth: .infinity, maxHeight: .infinity)
} else if !hasSearched {

VStack(spacing: 20) {

HStack {
Spacer()

Image(systemName: "magnifyingglass.circle")
.font(.system(size: 60))
.foregroundColor(Color("Accent"))

Text("Search for Friends")
.font(Font.custom("Poppins-SemiBold", size: 20))
.foregroundColor(Color.white)

Text("Enter a username or name to find friends on VITTY")
.multilineTextAlignment(.center)
.font(Font.custom("Poppins-Regular", size: 14))
.foregroundColor(Color.white.opacity(0.8))
.padding(.horizontal, 40)

VStack(spacing: 20) {
Image(systemName: "magnifyingglass.circle")
.font(.system(size: 60))
.foregroundColor(Color("Accent"))
Text("Search for Friends")
.font(Font.custom("Poppins-SemiBold", size: 20))
.foregroundColor(Color.white)
Text("Enter a username or name to find friends on VITTY")
.multilineTextAlignment(.center)
.font(Font.custom("Poppins-Regular", size: 14))
.foregroundColor(Color.white.opacity(0.8))
.padding(.horizontal, 40)
}
Spacer()
}
.frame(maxHeight: .infinity)

} else if searchedFriends.isEmpty && !searchText.isEmpty {
VStack(spacing: 20) {

HStack {
Spacer()

Image(systemName: "person.crop.circle.badge.questionmark")
.font(.system(size: 60))
.foregroundColor(Color("Accent"))

Text("No Results Found")
.font(Font.custom("Poppins-SemiBold", size: 20))
.foregroundColor(Color.white)

Text("No users found for '\(searchText)'. Try a different search term.")
.multilineTextAlignment(.center)
.font(Font.custom("Poppins-Regular", size: 14))
.foregroundColor(Color.white.opacity(0.8))
.padding(.horizontal, 40)

VStack(spacing: 20) {
Image(systemName: "person.crop.circle.badge.questionmark")
.font(.system(size: 60))
.foregroundColor(Color("Accent"))
Text("No Results Found")
.font(Font.custom("Poppins-SemiBold", size: 20))
.foregroundColor(Color.white)
Text("No users found for '\(searchText)'. Try a different search term.")
.multilineTextAlignment(.center)
.font(Font.custom("Poppins-Regular", size: 14))
.foregroundColor(Color.white.opacity(0.8))
.padding(.horizontal, 40)
}
Spacer()
}
.frame(maxHeight: .infinity)

} else {
List($searchedFriends, id: \.username) { searchfriend in
AddFriendCardSearch(friend: searchfriend , search: searchText)
Expand Down Expand Up @@ -222,7 +228,7 @@ struct SearchView: View {

cancelSearch()

// Reset all states

searchText = ""
searchedFriends = []
hasSearched = false
Expand Down Expand Up @@ -334,7 +340,7 @@ struct SearchView: View {
}
}

// Update the cancelSearch function to work with Alamofire

func cancelSearch() {
currentSearchTask?.cancel()
currentSearchTask = nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ struct SuggestedFriendsView: View {
.scrollContentBackground(.hidden)
.refreshable {
suggestedFriendsViewModel.fetchData(
from: "\(APIConstants.base_url)/api/v2/users/suggested/",
from: "\(APIConstants.base_url)users/suggested/",
token: authViewModel.loggedInBackendUser?.token ?? "",
loading: false
)
Expand Down
10 changes: 5 additions & 5 deletions VITTY/VITTY/Connect/View/Circles/Components/CreateGroup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ struct CreateGroup: View {
.frame(width: 80, height: 5)
.padding(.top, 10)

Text("Create Group")
Text("Create Circle")
.font(.system(size: 23, weight: .semibold))
.foregroundColor(.white)

Expand Down Expand Up @@ -72,11 +72,11 @@ struct CreateGroup: View {


VStack(alignment: .leading, spacing: 10) {
Text("Enter group name")
Text("Enter circle name")
.font(.system(size: 18, weight: .bold))
.foregroundColor(Color("Accent"))

TextField("Group Name", text: $groupName)
TextField("Circle Name", text: $groupName)
.padding()
.background(Color.black.opacity(0.3))
.cornerRadius(8)
Expand All @@ -93,15 +93,15 @@ struct CreateGroup: View {
}


if groupName.count > 20 {
if groupName.count > 50 {
groupName = String(groupName.prefix(20))
}
}
.autocorrectionDisabled(true)
.textInputAutocapitalization(.never)


Text("No spaces allowed • Max 20 characters")
Text("No spaces allowed • Max 50 characters")
.font(.system(size: 12))
.foregroundColor(.gray)
.padding(.leading, 5)
Expand Down
Loading
Loading