Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
4f787c1
feat: design
IvanStepanok Feb 17, 2025
ef59ab2
feat: add endpoints and update models
IvanStepanok Mar 12, 2025
c662442
feat: add routing logic
IvanStepanok Mar 18, 2025
da6f649
feat: update course routing
IvanStepanok Mar 21, 2025
d961e61
feat: add pagination
IvanStepanok Mar 24, 2025
265d3d9
feat: add offline mode, feature flag, pagination
IvanStepanok Mar 25, 2025
3524c8e
feat: add refreshable extension and fix minor bugs
IvanStepanok Mar 25, 2025
3d78afe
feat: add analytics
IvanStepanok Mar 25, 2025
1fddc22
feat: update to swift 6
IvanStepanok Mar 25, 2025
dc69bf9
feat: add error handling
IvanStepanok Mar 25, 2025
8fbaf84
feat: add shift due dates
IvanStepanok Mar 25, 2025
fe50bff
feat: update reset date deadlines logic
IvanStepanok Mar 28, 2025
4ac0a7c
feat: add cache-first logic
IvanStepanok Mar 28, 2025
a61a74a
feat: prepare for tests
IvanStepanok Mar 28, 2025
8cbf9ad
feat: update logic
IvanStepanok Mar 29, 2025
d99bd0f
feat: add index to core data model
IvanStepanok Mar 29, 2025
3828fea
Merge branch 'develop' into feat/app_level_dates
IvanStepanok Mar 29, 2025
6c206c4
feat: update navbar icon
IvanStepanok Mar 29, 2025
e2ab504
feat: update library version
IvanStepanok Mar 29, 2025
3649d72
feat: add AppDatesTests
IvanStepanok Mar 31, 2025
51df2cc
feat: remove prints
IvanStepanok Mar 31, 2025
9852c55
fix: address feedback
IvanStepanok Mar 31, 2025
cd95691
feat: remove dates sorting
IvanStepanok Apr 3, 2025
a17c47f
Merge branch 'develop' into feat/app_level_dates
IvanStepanok Apr 3, 2025
82be032
fix: merge conflicts and tests
IvanStepanok Apr 3, 2025
eddc065
fix: update feature flag
IvanStepanok Apr 3, 2025
6d2186b
fix: address feedback
IvanStepanok Apr 6, 2025
62dc849
fix: convert folders to groups
IvanStepanok Apr 8, 2025
2776525
feat: address feedback (design)
IvanStepanok Apr 16, 2025
9964cc7
feat: address feedback
IvanStepanok Apr 16, 2025
1de79e4
Merge branch 'develop' into feat/app_level_dates
IvanStepanok Oct 8, 2025
e6470d7
fix: issues after merge request
IvanStepanok Oct 8, 2025
cac59af
fix: update mocks for tests
IvanStepanok Oct 8, 2025
b3ca735
fix: update logic for duplicates
IvanStepanok Oct 13, 2025
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
3 changes: 3 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ excluded: # paths to ignore during linting. Takes precedence over `included`.
- Carthage
- DerivedData
- Pods
- DerivedData
- Core/CoreTests
- Authorization/AuthorizationTests
- Course/CourseTests
Expand All @@ -30,6 +31,7 @@ excluded: # paths to ignore during linting. Takes precedence over `included`.
- Profile/ProfileTests
- WhatsNew/WhatsNewTests
- Theme/ThemeTests
- AppDates/AppDatesTests
- vendor
- Core/Core/SwiftGen
- Authorization/Authorization/SwiftGen
Expand All @@ -41,6 +43,7 @@ excluded: # paths to ignore during linting. Takes precedence over `included`.
- Profile/Profile/SwiftGen
- WhatsNew/WhatsNew/SwiftGen
- Theme/Theme/SwiftGen
- AppDates/AppDates/SwiftGen
# - Source/ExcludedFile.swift
# - Source/*/ExcludedFile.swift # Exclude files with a wildcard
#analyzer_rules: # Rules run by `swiftlint analyze` (experimental)
Expand Down
99 changes: 99 additions & 0 deletions AppDates/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

## User settings
xcuserdata/*
/AppDates.xcodeproj/xcuserdata/
/AppDates.xcworkspace/xcuserdata/

## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout

## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3

## Obj-C/Swift specific
*.hmap

## App packaging
*.ipa
*.dSYM.zip
*.dSYM

## R.swift
R.generated.swift

## Playgrounds
timeline.xctimeline
playground.xcworkspace

# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
# Package.resolved
# *.xcodeproj
#
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
# hence it is not needed unless you have added a package configuration file to your project
# .swiftpm

.build/

# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
Pods/
#
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace

# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts

Carthage/Build/

# Accio dependency management
Dependencies/
.accio/

# fastlane
#
# It is recommended to not store the screenshots in the git repo.
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control

fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output

# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode

iOSInjectionProject/

.DS_Store
.idea
xcode-frameworks
1,628 changes: 1,628 additions & 0 deletions AppDates/AppDates.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions AppDates/AppDates/AppDates.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// AppDates.h
// AppDates
//
// Created by Ivan Stepanok on 15.02.2025.
//

#import <Foundation/Foundation.h>

//! Project version number for AppDates.
FOUNDATION_EXPORT double AppDatesVersionNumber;

//! Project version string for AppDates.
FOUNDATION_EXPORT const unsigned char AppDatesVersionString[];

// In this header, you should import all the public headers of your framework using statements like #import <AppDates/PublicHeader.h>


28 changes: 28 additions & 0 deletions AppDates/AppDates/Data/DatesPersistenceProtocol.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// DatesPersistenceProtocol.swift
// AppDates
//
// Created by Ivan Stepanok on 15.02.2025.
//

import Foundation
import Core

//sourcery: AutoMockable
public protocol DatesPersistenceProtocol: Sendable {
func loadCourseDates(limit: Int?, offset: Int?) async throws -> [CourseDate]
func saveCourseDates(dates: [CourseDate], startIndex: Int) async
func clearAllCourseDates() async
}

#if DEBUG
public struct DatesPersistenceMock: DatesPersistenceProtocol {
public func loadCourseDates(limit: Int?, offset: Int?) async throws -> [CourseDate] {[]}
public func saveCourseDates(dates: [CourseDate], startIndex: Int) async {}
public func clearAllCourseDates() async {}
}
#endif

public final class AppDatesBundle {
private init() {}
}
184 changes: 184 additions & 0 deletions AppDates/AppDates/Data/DatesRepository.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
//
// DatesRepository.swift
// AppDates
//
// Created by Ivan Stepanok on 15.02.2025.
//

import Foundation
import Core
import OEXFoundation

public protocol DatesRepositoryProtocol: Sendable {
func getCourseDates(page: Int) async throws -> ([CourseDate], String?)
func getCourseDatesOffline(limit: Int?, offset: Int?) async throws -> [CourseDate]
func resetAllRelativeCourseDeadlines() async throws
}

public actor DatesRepository: DatesRepositoryProtocol {

private let api: API
private let storage: CoreStorage
private let config: ConfigProtocol
private let persistence: DatesPersistenceProtocol
private var totalItemsCount: Int = 0

public init(api: API, storage: CoreStorage, config: ConfigProtocol, persistence: DatesPersistenceProtocol) {
self.api = api
self.storage = storage
self.config = config
self.persistence = persistence
}

public func getCourseDates(page: Int) async throws -> ([CourseDate], String?) {
let response = try await api.requestData(
DatesEndpoint.getCourseDates(username: storage.user?.username ?? "", page: page)
)
.mapResponse(DataLayer.CourseDatesResponse.self)

let dates = response.domain

if page == 1 {
await persistence.clearAllCourseDates()
totalItemsCount = 0
}

let startIndex = totalItemsCount
let indexedDates = dates.enumerated().map { offset, date in
CourseDate(
date: date.date,
title: date.title,
courseName: date.courseName,
courseId: date.courseId,
blockId: date.blockId,
hasAccess: date.hasAccess,
order: startIndex + offset
)
}
totalItemsCount += indexedDates.count

await persistence.saveCourseDates(dates: indexedDates, startIndex: startIndex)

return (indexedDates, response.next)
}

public func getCourseDatesOffline(limit: Int? = nil, offset: Int? = nil) async throws -> [CourseDate] {
return try await persistence.loadCourseDates(limit: limit, offset: offset)
}

public func resetAllRelativeCourseDeadlines() async throws {
try await api.request(DatesEndpoint.resetAllRelativeCourseDeadlines)
}
}

// Mark - For testing and SwiftUI preview
#if DEBUG
public final class DatesRepositoryMock: DatesRepositoryProtocol {

public init() {}

public func getCourseDates(page: Int) async throws -> ([CourseDate], String?) {
let dates = [
CourseDate(
date: Date().addingTimeInterval(-86400 * 2),
title: "Assignment from the Day Before Yesterday",
courseName: "Course 6",
courseId: "course-v1:1+1+daybeforeyesterday",
blockId: "block-v1:1+1+daybeforeyesterday+type@sequential+block@assignment",
hasAccess: true
),
CourseDate(
date: Date().addingTimeInterval(-86400),
title: "Assignment from Yesterday",
courseName: "Course 7",
courseId: "course-v1:1+1+yesterday",
blockId: "block-v1:1+1+yesterday+type@sequential+block@assignment",
hasAccess: true
),
CourseDate(
date: Date(),
title: "Today's Assignment 1",
courseName: "Course 1",
courseId: "course-v1:1+1+today1",
blockId: "block-v1:1+1+today1+type@sequential+block@assignment1",
hasAccess: true
),
CourseDate(
date: Date(),
title: "Today's Assignment 2",
courseName: "Course 1",
courseId: "course-v1:1+1+today2",
blockId: "block-v1:1+1+today2+type@sequential+block@assignment2",
hasAccess: true
),
CourseDate(
date: Date().addingTimeInterval(86400), // 1 day
title: "Tomorrow's Assignment",
courseName: "Course 2",
courseId: "course-v1:1+1+tomorrow1",
blockId: "block-v1:1+1+tomorrow1+type@sequential+block@assignment3",
hasAccess: true
),
CourseDate(
date: Date().addingTimeInterval(86400 * 5),
title: "Assignment in 5 Days",
courseName: "Course 3",
courseId: "course-v1:1+1+5days1",
blockId: "block-v1:1+1+5days1+type@sequential+block@assignment4",
hasAccess: true
),
CourseDate(
date: Date().addingTimeInterval(86400 * 5),
title: "Assignment in 5 Days (Part 2)",
courseName: "Course 3",
courseId: "course-v1:1+1+5days2",
blockId: "block-v1:1+1+5days2+type@sequential+block@assignment5",
hasAccess: true
),
CourseDate(
date: Date().addingTimeInterval(86400 * 10),
title: "Assignment in 10 Days",
courseName: "Course 4",
courseId: "course-v1:1+1+10days1",
blockId: "block-v1:1+1+10days1+type@sequential+block@assignment6",
hasAccess: true
),
CourseDate(
date: Date().addingTimeInterval(86400 * 20),
title: "Assignment in 20 Days 1",
courseName: "Course 5",
courseId: "course-v1:1+1+20days1",
blockId: "block-v1:1+1+20days1+type@sequential+block@assignment7",
hasAccess: true
),
CourseDate(
date: Date().addingTimeInterval(86400 * 20),
title: "Assignment in 20 Days 2",
courseName: "Course 5",
courseId: "course-v1:1+1+20days2",
blockId: "block-v1:1+1+20days2+type@sequential+block@assignment8",
hasAccess: true
)
]

return (dates, nil)
}

public func getCourseDatesOffline(limit: Int? = nil, offset: Int? = nil) async throws -> [CourseDate] {
return [
CourseDate(
date: Date().addingTimeInterval(-86400 * 3),
title: "Offline Assignment",
courseName: "Cached Course",
courseId: "course-v1:1+1+offline",
blockId: "block-v1:1+1+offline+type@sequential+block@bafd854414124f6db42fee42ca8acc19",
hasAccess: true
)
]
}

public func resetAllRelativeCourseDeadlines() async throws {}

public func clearAllCourseDates() async {}
}
#endif
Loading
Loading