Skip to content

Commit e7698cf

Browse files
[MBL-19480][S/T/P] Immersive Video Player for Canvas Media Uploads (#3755)
refs: MBL-19480 affects: Student, Teacher, Parent builds: Student, Teacher, Parent release note: Introduced immersive experience for video player. Co-authored-by: Attila Varga <[email protected]>
1 parent 830ff28 commit e7698cf

File tree

33 files changed

+1229
-49
lines changed

33 files changed

+1229
-49
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//
2+
// This file is part of Canvas.
3+
// Copyright (C) 2025-present Instructure, Inc.
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU Affero General Public License as
7+
// published by the Free Software Foundation, either version 3 of the
8+
// License, or (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU Affero General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU Affero General Public License
16+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
//
18+
19+
import Foundation
20+
21+
public struct APIFeatureFlagState: Codable {
22+
23+
public enum State: String, Codable {
24+
/// (valid only for account context) The feature is `off` in the account, but may be enabled in sub-accounts and courses by setting a feature flag `on` the sub-account or course.
25+
case allowed
26+
/// (valid only for account context) The feature is `on` in the account, but may be disabled in sub-accounts and courses by setting a feature flag `off` the sub-account or course.
27+
case allowed_on
28+
/// The feature is turned `on` unconditionally for the user, course, or account and sub-accounts.
29+
case on
30+
/// The feature is not available for the course, user, or account and sub-accounts.
31+
case off
32+
}
33+
34+
public let feature: String
35+
public let state: State
36+
public let locked: Bool
37+
38+
private let context_id: String
39+
private let context_type: String
40+
41+
init(feature: String, state: State, locked: Bool, context_id: String, context_type: String) {
42+
self.feature = feature
43+
self.state = state
44+
self.locked = locked
45+
self.context_id = context_id
46+
self.context_type = context_type
47+
}
48+
49+
public var contextType: ContextType? {
50+
return ContextType(rawValue: context_type.lowercased())
51+
}
52+
53+
public var canvasContextID: String {
54+
return "\(context_type.lowercased())_\(context_id)"
55+
}
56+
57+
public func overriden(state: State, context: Context) -> Self {
58+
APIFeatureFlagState(
59+
feature: feature,
60+
state: state,
61+
locked: locked,
62+
context_id: context.id,
63+
context_type: context.contextType.rawValue
64+
)
65+
}
66+
}

Core/Core/Common/CommonModels/AppEnvironment/FeatureFlags/FeatureFlag.swift

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@ import Foundation
2020
import CoreData
2121

2222
public struct APIFeatureFlag {
23-
public enum Key: String {
24-
case assignmentEnhancements = "assignments_2_student"
25-
}
2623
public let key: String
2724
public let isEnabled: Bool
2825
public let canvasContextID: String
@@ -55,10 +52,24 @@ public final class FeatureFlag: NSManagedObject, WriteableModel {
5552
flag.isEnvironmentFlag = item.isEnvironmentFlag
5653
return flag
5754
}
55+
56+
@discardableResult
57+
public static func save(_ item: APIFeatureFlagState, in context: NSManagedObjectContext) -> FeatureFlag {
58+
let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
59+
NSPredicate(format: "%K == %@", #keyPath(FeatureFlag.canvasContextID), item.canvasContextID),
60+
NSPredicate(format: "%K == %@", #keyPath(FeatureFlag.name), item.feature)
61+
])
62+
let flag: FeatureFlag = context.fetch(predicate).first ?? context.insert()
63+
flag.name = item.feature
64+
flag.enabled = item.state == .on
65+
flag.canvasContextID = item.canvasContextID
66+
flag.isEnvironmentFlag = false
67+
return flag
68+
}
5869
}
5970

6071
extension Collection where Element == FeatureFlag {
61-
public func isFeatureFlagEnabled(_ key: APIFeatureFlag.Key) -> Bool {
72+
public func isFeatureFlagEnabled(_ key: FeatureFlagName) -> Bool {
6273
isFeatureFlagEnabled(name: key.rawValue)
6374
}
6475

Core/Core/Common/CommonModels/AppEnvironment/FeatureFlags/GetEnabledFeatureFlags.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public class GetEnabledFeatureFlags: CollectionUseCase {
6464
}
6565

6666
extension Store where U == GetEnabledFeatureFlags {
67-
public func isFeatureFlagEnabled(_ key: APIFeatureFlag.Key) -> Bool {
67+
public func isFeatureFlagEnabled(_ key: FeatureFlagName) -> Bool {
6868
all.isFeatureFlagEnabled(key)
6969
}
7070
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//
2+
// This file is part of Canvas.
3+
// Copyright (C) 2025-present Instructure, Inc.
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU Affero General Public License as
7+
// published by the Free Software Foundation, either version 3 of the
8+
// License, or (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU Affero General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU Affero General Public License
16+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
//
18+
19+
import Foundation
20+
import CoreData
21+
22+
public class GetFeatureFlagState: CollectionUseCase {
23+
public typealias Model = FeatureFlag
24+
25+
public let featureName: FeatureFlagName
26+
public let context: Context
27+
28+
public var scope: Scope {
29+
let contextPredicate = NSPredicate(format: "%K == %@", #keyPath(FeatureFlag.canvasContextID), context.canvasContextID)
30+
let namePredicate = NSPredicate(format: "%K == %@", #keyPath(FeatureFlag.name), featureName.rawValue)
31+
let predicate = NSCompoundPredicate(
32+
andPredicateWithSubpredicates: [
33+
contextPredicate,
34+
namePredicate
35+
]
36+
)
37+
return Scope(predicate: predicate, order: [NSSortDescriptor(key: #keyPath(FeatureFlag.name), ascending: true)])
38+
}
39+
40+
public var cacheKey: String? {
41+
return "get-\(context.canvasContextID)-\(featureName.rawValue)-feature-flag-state"
42+
}
43+
44+
public var request: GetFeatureFlagStateRequest {
45+
return GetFeatureFlagStateRequest(featureName: featureName, context: context)
46+
}
47+
48+
public init(featureName: FeatureFlagName, context: Context) {
49+
self.featureName = featureName
50+
self.context = context
51+
}
52+
53+
public func write(response: APIFeatureFlagState?, urlResponse: URLResponse?, to client: NSManagedObjectContext) {
54+
guard var item = response else { return }
55+
56+
/// This as workaround for an API limitation, where
57+
/// requesting feature state of a course context,
58+
/// if not set on that course, would return the feature state of
59+
/// the most higher level (which is the account)
60+
if item.contextType != context.contextType && item.contextType == .account {
61+
item = item.overriden(
62+
state: item.state == .allowed_on ? .on : item.state,
63+
context: context
64+
)
65+
}
66+
67+
FeatureFlag.save(item, in: client)
68+
}
69+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//
2+
// This file is part of Canvas.
3+
// Copyright (C) 2025-present Instructure, Inc.
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU Affero General Public License as
7+
// published by the Free Software Foundation, either version 3 of the
8+
// License, or (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU Affero General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU Affero General Public License
16+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
//
18+
19+
import Foundation
20+
21+
// https://canvas.instructure.com/doc/api/feature_flags.html#method.feature_flags.show
22+
public struct GetFeatureFlagStateRequest: APIRequestable {
23+
public typealias Response = APIFeatureFlagState
24+
25+
public let context: Context
26+
public let featureName: FeatureFlagName
27+
28+
public var path: String {
29+
return "\(context.pathComponent)/features/flags/\(featureName.rawValue)"
30+
}
31+
32+
public init(featureName: FeatureFlagName, context: Context) {
33+
self.featureName = featureName
34+
self.context = context
35+
}
36+
}
37+
38+
// MARK: - Parameters
39+
40+
public enum FeatureFlagName: String {
41+
case assignmentEnhancements = "assignments_2_student"
42+
case studioEmbedImprovements = "rce_studio_embed_improvements"
43+
}

0 commit comments

Comments
 (0)