Skip to content

Commit

Permalink
General: Release 1.6.1 (#293)
Browse files Browse the repository at this point in the history
  • Loading branch information
anian03 authored Feb 19, 2025
2 parents da3018f + 0fb5362 commit c3104fe
Show file tree
Hide file tree
Showing 32 changed files with 549 additions and 49 deletions.
4 changes: 2 additions & 2 deletions Artemis.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APP_VERSION_NUMBER = 1.6.0;
APP_VERSION_NUMBER = 1.6.1;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
Expand Down Expand Up @@ -570,7 +570,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APP_VERSION_NUMBER = 1.6.0;
APP_VERSION_NUMBER = 1.6.1;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"originHash" : "008193f82a18024045696bb777eb551728aa12ae494d1eb655cdd7ae99cb3cdf",
"originHash" : "c6a20b5bee29af2f7d9e5859468f6fab85cc423cfc28d52785acd9a2fc8165ae",
"pins" : [
{
"identity" : "apollon-ios-module",
Expand All @@ -15,8 +15,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/ls1intum/artemis-ios-core-modules",
"state" : {
"revision" : "23fa0aaf0b16db7643c2589a65bf1e09ff257546",
"version" : "15.3.0"
"revision" : "6ed532bc24ec5f3ca86ff62c755b6bf3e0df8ab9",
"version" : "15.5.0"
}
},
{
Expand Down Expand Up @@ -186,17 +186,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/tomlokhorst/XcodeEdit",
"state" : {
"revision" : "1e761a55dd8d73b4e9cc227a297f438413953571",
"version" : "2.11.1"
"revision" : "9bc684439d16c07d7bede66ae43bb577a25d6bb1",
"version" : "2.12.0"
}
},
{
"identity" : "yams",
"kind" : "remoteSourceControl",
"location" : "https://github.com/jpsim/Yams.git",
"state" : {
"revision" : "3036ba9d69cf1fd04d433527bc339dc0dc75433d",
"version" : "5.1.3"
"revision" : "2688707e563b44d7d87c29ba6c5ca04ce86ae58b",
"version" : "5.3.0"
}
}
],
Expand Down
8 changes: 6 additions & 2 deletions ArtemisKit/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/onmyway133/Smile.git", revision: "6bacbf7"),
.package(url: "https://github.com/ls1intum/apollon-ios-module", .upToNextMajor(from: "1.0.2")),
.package(url: "https://github.com/ls1intum/artemis-ios-core-modules", .upToNextMajor(from: "15.3.0")),
.package(url: "https://github.com/ls1intum/artemis-ios-core-modules", .upToNextMajor(from: "15.5.0")),
.package(url: "https://github.com/mac-cain13/R.swift.git", from: "7.7.0")
],
targets: [
Expand All @@ -35,7 +35,11 @@ let package = Package(
"Messages",
"Navigation",
"Notifications",
.product(name: "Login", package: "artemis-ios-core-modules")
.product(name: "Login", package: "artemis-ios-core-modules"),
.product(name: "ProfileInfo", package: "artemis-ios-core-modules")
],
plugins: [
.plugin(name: "RswiftGeneratePublicResources", package: "R.swift")
]),
.target(
name: "CourseRegistration",
Expand Down
7 changes: 6 additions & 1 deletion ArtemisKit/Sources/ArtemisKit/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
Task {
await PushNotificationServiceFactory.shared.register(deviceToken: String(deviceToken: deviceToken))
_ = await PushNotificationServiceFactory.shared.register(deviceToken: String(deviceToken: deviceToken))
PushNotificationHandler.scheduleNotificationForSessionExpired()
}
log.info("Device Token: \(String(deviceToken: deviceToken))")
}
Expand Down Expand Up @@ -75,6 +76,10 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
) {
if notification.request.identifier == LocalNotificationIdentifiers.sessionExpired {
UserSessionFactory.shared.setTokenExpired(expired: true)
UserSessionFactory.shared.setUserLoggedIn(isLoggedIn: false)
}
completionHandler([.banner, .badge, .sound])
}

Expand Down
92 changes: 92 additions & 0 deletions ArtemisKit/Sources/ArtemisKit/ForceAppUpdate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//
// ForceAppUpdate.swift
// ArtemisKit
//
// Created by Anian Schleyer on 10.02.25.
//

import SwiftUI

struct ForceAppUpdateViewModifier: ViewModifier {
@Binding var updateRequirement: UpdateRequirement

private var presentUpdateSheet: Binding<Bool> {
Binding(
get: {
updateRequirement != .upToDate
},
set: { newValue in
if !newValue {
updateRequirement = .upToDate
}
}
)
}

func body(content: Content) -> some View {
content
.sheet(isPresented: presentUpdateSheet) {
UpdateAvailableView(updateRequirement: updateRequirement)
.interactiveDismissDisabled()
}
}
}

private struct UpdateAvailableView: View {
@Environment(\.openURL) var openURL
@Environment(\.dismiss) var dismiss
let updateRequirement: UpdateRequirement

var body: some View {
ScrollView {
VStack(spacing: .xl) {
Image(systemName: "app.badge.fill")
.resizable()
.scaledToFit()
.frame(width: 100, height: 100)
.foregroundStyle(.blue)
.padding(.top)

Text(R.string.localizable.updateAvailable())
.font(.title.bold())

Text(R.string.localizable.updateDescription())
.multilineTextAlignment(.center)

switch updateRequirement {
case let .requiresUpdate(current, min):
Text(R.string.localizable.smallestVersion(current, min))
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
default:
EmptyView()
}
}
}
.contentMargins(.xl, for: .scrollContent)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.safeAreaInset(edge: .bottom) {
VStack(spacing: 20) {
Button {
if let url = URL(string: "https://apps.apple.com/app/artemis-learning/id6478965616") {
openURL(url)
}
} label: {
Text(R.string.localizable.download())
.frame(maxWidth: .infinity)
.padding(.vertical, .m)
}
.buttonStyle(.borderedProminent)

if updateRequirement == .recommendsUpdate {
Button(R.string.localizable.notNow()) {
dismiss()
}
}
}
.padding(20)
.frame(maxWidth: .infinity)
.background(.bar)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"updateAvailable" = "New update available";
"updateDescription" = "Please update Artemis to the latest version to ensure you have a smooth experience.\nYou can download the latest version from the App Store.";
"smallestVersion" = "You are currently using version %@ while the oldest version currently supported is %@.";
"download" = "Download";
"notNow" = "Not now";
7 changes: 7 additions & 0 deletions ArtemisKit/Sources/ArtemisKit/RootView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import SwiftUI

public struct RootView: View {

@Environment(\.scenePhase) var scenePhase
@StateObject private var viewModel = RootViewModel()

@ObservedObject private var navigationController: NavigationController
Expand Down Expand Up @@ -61,6 +62,12 @@ public struct RootView: View {
}
}
})
.modifier(ForceAppUpdateViewModifier(updateRequirement: $viewModel.updateRequirement))
.onChange(of: scenePhase) {
if scenePhase == .active {
Task { await viewModel.checkForUpdates() }
}
}
}
}

Expand Down
67 changes: 67 additions & 0 deletions ArtemisKit/Sources/ArtemisKit/RootViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Combine
import Common
import Foundation
import PushNotifications
import ProfileInfo
import SwiftUI
import SharedServices
import UserStore
Expand All @@ -20,11 +21,13 @@ class RootViewModel: ObservableObject {
@Published var isLoading = true
@Published var isLoggedIn = false
@Published var didSetupNotifications = false
@Published var updateRequirement: UpdateRequirement = .upToDate

private let userSession: UserSession
private let accountService: AccountService

private var cancellable: Set<AnyCancellable> = Set()
private var lastUpdateCheck: Date = .distantPast

init(
userSession: UserSession = UserSessionFactory.shared,
Expand All @@ -35,6 +38,33 @@ class RootViewModel: ObservableObject {

start()
}

/// Makes a request to check whether the current app version is still compatible with the sever
func checkForUpdates() async {
// Check once per day or after a restart
guard userSession.isLoggedIn && lastUpdateCheck.timeIntervalSinceNow < -60 * 60 * 24 else {
return
}
let profileService = ProfileInfoServiceFactory.shared
let response = await profileService.getProfileInfo()
let versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
let currentVersion = AppVersion(versionString)

switch response {
case .loading, .failure:
break
case .done(let info):
lastUpdateCheck = .now
let supportedVersions = info.compatibleVersions?.ios
if let min = supportedVersions?.min, currentVersion < AppVersion(min) {
updateRequirement = .requiresUpdate(current: versionString, min: min)
} else if let min = supportedVersions?.recommended, currentVersion < AppVersion(min) {
updateRequirement = .recommendsUpdate
} else {
updateRequirement = .upToDate
}
}
}
}

private extension RootViewModel {
Expand All @@ -47,6 +77,9 @@ private extension RootViewModel {

if !self.isLoggedIn && self.userSession.isLoggedIn {
self.updateDeviceToken()
Task {
await self.checkForUpdates()
}
}
self.isLoggedIn = self.userSession.isLoggedIn
self.didSetupNotifications = self.userSession.getCurrentNotificationDeviceConfiguration() != nil
Expand Down Expand Up @@ -93,3 +126,37 @@ private extension RootViewModel {
}
}
}

enum UpdateRequirement: Equatable {
case upToDate
case requiresUpdate(current: String, min: String)
case recommendsUpdate
}

private struct AppVersion: Comparable {
static func < (lhs: AppVersion, rhs: AppVersion) -> Bool {
lhs.major < rhs.major ||
lhs.major == rhs.major && lhs.minor < rhs.minor ||
lhs.major == rhs.major && lhs.minor == rhs.minor && lhs.bugfix < rhs.bugfix
}

var major: Int = 0
var minor: Int = 0
var bugfix: Int = 0

init(_ versionString: String) {
let components = versionString.split(separator: ".")

if components.count >= 1 {
major = Int(components[0]) ?? 0
}

if components.count > 1 {
minor = Int(components[1]) ?? 0
}

if components.count > 2 {
bugfix = Int(components[2]) ?? 0
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ struct TextUnitSheetContent: View {
var body: some View {
ScrollView {
ArtemisMarkdownView(string: textUnit.content ?? "")
.padding(.horizontal)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ struct VideoUnitSheetContent: View {

Link(R.string.localizable.openVideo(), destination: url)
.buttonStyle(ArtemisButton())
.padding(.horizontal)
} else {
Text(R.string.localizable.videoCouldNotBeLoaded())
.foregroundColor(.red)
Expand Down
31 changes: 27 additions & 4 deletions ArtemisKit/Sources/Dashboard/CourseGrid.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,44 @@

import CourseRegistration
import DesignLibrary
import SharedModels
import SwiftUI

struct CourseGrid: View {
private static let layout = [GridItem(.adaptive(minimum: 380, maximum: .infinity), spacing: .l, alignment: .center)]

@ObservedObject var viewModel: DashboardViewModel
@Bindable var viewModel: DashboardViewModel
@State private var isCourseRegistrationPresented = false

var body: some View {
DataStateView(data: $viewModel.coursesForDashboard) {
await viewModel.loadCourses()
} content: { coursesForDashboard in
} content: { _ in
ScrollView {
if !viewModel.recentCourses.isEmpty && viewModel.searchText.isEmpty {
Text(R.string.localizable.recentlyAccessed())
.frame(maxWidth: .infinity, alignment: .leading)
.font(.title.bold())

LazyVGrid(columns: Self.layout, spacing: .l) {
ForEach(viewModel.recentCourses) { course in
CourseGridCell(courseForDashboard: course, viewModel: viewModel)
}
}

Text(R.string.localizable.allCourses())
.frame(maxWidth: .infinity, alignment: .leading)
.font(.title.bold())
.padding(.top, .l)
}
LazyVGrid(columns: Self.layout, spacing: .l) {
ForEach(coursesForDashboard.courses ?? [], content: CourseGridCell.init)
ForEach(viewModel.filteredCourses) { course in
CourseGridCell(courseForDashboard: course, viewModel: viewModel)
}
}
if viewModel.filteredCourses.isEmpty && !viewModel.searchText.isEmpty {
ContentUnavailableView.search
}
.padding(.horizontal, .l)

HStack {
Spacer()
Expand All @@ -34,6 +55,8 @@ struct CourseGrid: View {
Spacer()
}
}
.contentMargins(.horizontal, .l, for: .scrollContent)
.searchable(text: $viewModel.searchText)
.refreshable {
await viewModel.loadCourses()
}
Expand Down
Loading

0 comments on commit c3104fe

Please sign in to comment.