Skip to content

Commit

Permalink
Merge pull request #23 from Ditectrev/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
danieldanielecki authored Jun 4, 2024
2 parents a672ff7 + 2fcab3c commit 3b456ac
Show file tree
Hide file tree
Showing 7 changed files with 364 additions and 278 deletions.
12 changes: 10 additions & 2 deletions CloudMaster.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

/* Begin PBXBuildFile section */
8D26A31C2C0EA9C100E9B015 /* QuestionNavbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D26A31B2C0EA9C100E9B015 /* QuestionNavbar.swift */; };
8D26A31E2C0EE3F400E9B015 /* QuestionImages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D26A31D2C0EE3F400E9B015 /* QuestionImages.swift */; };
8D26A3202C0EE4A000E9B015 /* QuestionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D26A31F2C0EE4A000E9B015 /* QuestionView.swift */; };
8D8D8A862C05A23600ACC61C /* CloudMasterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D8D8A852C05A23600ACC61C /* CloudMasterTests.swift */; };
8D8D8A902C05A23600ACC61C /* CloudMasterUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D8D8A8F2C05A23600ACC61C /* CloudMasterUITests.swift */; };
8D8D8A922C05A23600ACC61C /* CloudMasterUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D8D8A912C05A23600ACC61C /* CloudMasterUITestsLaunchTests.swift */; };
Expand Down Expand Up @@ -63,6 +65,8 @@

/* Begin PBXFileReference section */
8D26A31B2C0EA9C100E9B015 /* QuestionNavbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestionNavbar.swift; sourceTree = "<group>"; };
8D26A31D2C0EE3F400E9B015 /* QuestionImages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestionImages.swift; sourceTree = "<group>"; };
8D26A31F2C0EE4A000E9B015 /* QuestionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestionView.swift; sourceTree = "<group>"; };
8D8D8A712C05A23400ACC61C /* CloudMaster Swift.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "CloudMaster Swift.app"; sourceTree = BUILT_PRODUCTS_DIR; };
8D8D8A812C05A23600ACC61C /* CloudMasterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CloudMasterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
8D8D8A852C05A23600ACC61C /* CloudMasterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudMasterTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -132,6 +136,8 @@
children = (
8D8D8A9F2C05A27800ACC61C /* ConfirmPopup.swift */,
8D26A31B2C0EA9C100E9B015 /* QuestionNavbar.swift */,
8D26A31D2C0EE3F400E9B015 /* QuestionImages.swift */,
8D26A31F2C0EE4A000E9B015 /* QuestionView.swift */,
);
path = Components;
sourceTree = "<group>";
Expand Down Expand Up @@ -586,9 +592,11 @@
8D8D8ACF2C05A27800ACC61C /* DownloadOverlayView.swift in Sources */,
8D8D8AD32C05A27800ACC61C /* UserExamData.swift in Sources */,
8D8D8AD82C05A27800ACC61C /* PreviousExamsView.swift in Sources */,
8D26A31E2C0EE3F400E9B015 /* QuestionImages.swift in Sources */,
8D8D8AE42C05A27800ACC61C /* QuestionLoader.swift in Sources */,
8D8D8ACE2C05A27800ACC61C /* ConfirmPopup.swift in Sources */,
8D8D8AE32C05A27800ACC61C /* FavoriteStorage.swift in Sources */,
8D26A3202C0EE4A000E9B015 /* QuestionView.swift in Sources */,
8D8D8AD62C05A27800ACC61C /* ExamSummaryView.swift in Sources */,
8D8D8AD92C05A27800ACC61C /* HomeView.swift in Sources */,
8D8D8AD42C05A27800ACC61C /* ExamModesView.swift in Sources */,
Expand Down Expand Up @@ -774,7 +782,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.2;
MARKETING_VERSION = 1.0.3;
PRODUCT_BUNDLE_IDENTIFIER = com.ditectrev.cloudmasterswift;
PRODUCT_NAME = "CloudMaster Swift";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down Expand Up @@ -809,7 +817,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.2;
MARKETING_VERSION = 1.0.3;
PRODUCT_BUNDLE_IDENTIFIER = com.ditectrev.cloudmasterswift;
PRODUCT_NAME = "CloudMaster Swift";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down
100 changes: 5 additions & 95 deletions CloudMaster/Features/Exam/Views/ExamView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,12 @@ struct ExamView: View {
}
.padding(.horizontal)

ExamQuestion(
QuestionView(
mode: .exam,
question: questions[currentQuestionIndex],
selectedChoices: selectedChoices[questions[currentQuestionIndex].id] ?? [],
isMultipleResponse: questions[currentQuestionIndex].multipleResponse,
isResultShown: false, // Exam mode does not show result immediately
onChoiceSelected: { choiceId in
if questions[currentQuestionIndex].multipleResponse {
if selectedChoices[questions[currentQuestionIndex].id]?.contains(choiceId) == true {
Expand All @@ -60,6 +62,7 @@ struct ExamView: View {
}
}
)

Button(action: {
if currentQuestionIndex < questions.count - 1 {
currentQuestionIndex += 1
Expand All @@ -86,7 +89,7 @@ struct ExamView: View {
}
.padding()
} else {
Text("No que")
Text("No Questions available! Please download course")
}
}
.navigationDestination(isPresented: $navigateToSummary) {
Expand Down Expand Up @@ -164,96 +167,3 @@ struct ExamView: View {
return String(format: "%02d:%02d", minutes, seconds)
}
}

struct ExamQuestion: View {
let question: Question
let selectedChoices: Set<UUID>?
let isMultipleResponse: Bool
let onChoiceSelected: (UUID) -> Void

var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
Text(question.question)
.font(.system(size: adjustedFontSize(for: question.question), weight: .bold))
.minimumScaleFactor(0.5)
.lineLimit(nil) // Allow text to wrap as needed
.fixedSize(horizontal: false, vertical: true)
.padding(.horizontal)
.multilineTextAlignment(.leading) // Justify the text
.lineSpacing(2)

if let imagePath = question.imagePath,
let image = loadImage(from: imagePath) {
Image(uiImage: image)
.resizable()
.cornerRadius(2)
.aspectRatio(contentMode: .fit)
.frame(maxWidth: .infinity)
.padding(.horizontal)
}

if isMultipleResponse {
VStack {
Text("Multiple response - Pick \(question.responseCount)")
.font(.subheadline)
.multilineTextAlignment(.center)
.opacity(0.7)
.padding(.vertical, 5)
.frame(minWidth: 0, maxWidth: .infinity)
}
.background(Color.gray.opacity(0.2))
.cornerRadius(10)
.padding(.horizontal)
}

ForEach(question.choices) { choice in
ExamChoice(choice: choice, isSelected: selectedChoices?.contains(choice.id) == true, onChoiceSelected: onChoiceSelected)
}
}
.padding()
}
}

private func loadImage(from imagePath: String) -> UIImage? {
let fileManager = FileManager.default
let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
let imageURL = documentsURL.appendingPathComponent(imagePath)
return UIImage(contentsOfFile: imageURL.path)
}

private func adjustedFontSize(for text: String) -> CGFloat {
let maxWidth = UIScreen.main.bounds.width - 32
let baseFontSize: CGFloat = 24
let minFontSize: CGFloat = 14

// Scale the font size based on the text length
let lengthFactor = CGFloat(text.count) / 100.0
let scaledFontSize = max(baseFontSize - lengthFactor, minFontSize)

return scaledFontSize
}
}

struct ExamChoice: View {
let choice: Choice
let isSelected: Bool
let onChoiceSelected: (UUID) -> Void

var body: some View {
Button(action: {
onChoiceSelected(choice.id)
}) {
Text(choice.text)
.padding()
.frame(minWidth: 0, maxWidth: .infinity, alignment: .center)
.multilineTextAlignment(.center)
}
.background(isSelected ? Color.gray.opacity(0.3) : Color.clear)
.cornerRadius(10)
.padding(.horizontal)
.foregroundColor(.white)

Divider()
}
}
104 changes: 104 additions & 0 deletions CloudMaster/Features/Shared/Components/QuestionImages.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import SwiftUI

struct QuestionImages: View {
let images: [Question.ImageInfo]
@Binding var currentImageIndex: Int
@Binding var isFullscreenImageShown: Bool
@Binding var selectedImageIndex: Int

var body: some View {
if !images.isEmpty {
TabView(selection: $currentImageIndex) {
ForEach(images.indices, id: \.self) { index in
let imageInfo = images[index]
if let image = loadImage(from: imageInfo.path) {
Image(uiImage: image)
.resizable()
.cornerRadius(2)
.aspectRatio(contentMode: .fit)
.frame(maxWidth: .infinity)
.padding(.horizontal)
.tag(index)
.onTapGesture {
selectedImageIndex = index
isFullscreenImageShown = true
}
} else if let url = imageInfo.url, let urlImage = loadImage(from: url) {
Image(uiImage: urlImage)
.resizable()
.cornerRadius(2)
.aspectRatio(contentMode: .fit)
.frame(maxWidth: .infinity)
.padding(.horizontal)
.tag(index)
.onTapGesture {
selectedImageIndex = index
isFullscreenImageShown = true
}
}
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))
.frame(height: 300)
}
}

private func loadImage(from imagePath: String) -> UIImage? {
let fileManager = FileManager.default
let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
let imageURL = documentsURL.appendingPathComponent(imagePath)
return UIImage(contentsOfFile: imageURL.path)
}
}


struct FullscreenImageView: View {
let images: [Question.ImageInfo]
@Binding var selectedImageIndex: Int
@Binding var isShown: Bool

@State private var scale: CGFloat = 1.0
@State private var lastScale: CGFloat = 1.0

var body: some View {
ZStack {
Color.black.opacity(0.8)
.edgesIgnoringSafeArea(.all)

if let imageInfo = images[safe: selectedImageIndex],
let uiImage = loadImage(from: imageInfo.path) ?? loadImage(from: imageInfo.url) {
Image(uiImage: uiImage)
.resizable()
.aspectRatio(contentMode: .fit)
.scaleEffect(scale)
.gesture(
MagnificationGesture()
.onChanged { value in
self.scale = self.lastScale * value
}
.onEnded { _ in
self.lastScale = self.scale
}
)
.onTapGesture {
isShown = false
}
}
}
}

private func loadImage(from imagePath: String?) -> UIImage? {
guard let imagePath = imagePath else { return nil }
let fileManager = FileManager.default
let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
let imageURL = documentsURL.appendingPathComponent(imagePath)
return UIImage(contentsOfFile: imageURL.path)
}
}

// extension to safely access array elements to avoid out-of-bounds
extension Array {
subscript(safe index: Int) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
Loading

0 comments on commit 3b456ac

Please sign in to comment.