Skip to content

Commit

Permalink
Managed max attachments&change bgColor&when get error add focus&make …
Browse files Browse the repository at this point in the history
…btn disable
  • Loading branch information
cp-nirali-s committed Jan 6, 2025
1 parent e3c11e0 commit e4536bf
Show file tree
Hide file tree
Showing 12 changed files with 134 additions and 131 deletions.
1 change: 1 addition & 0 deletions BaseStyle/BaseStyle/Resource/AppColors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ public let disableText = Color.disableText
public let inverseDisableText = Color.inverseDisableText

public let secondaryText = Color.secondaryText
public let secondaryLightText = Color.secondaryLightText
public let lowestText = Color.lowestText
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.600",
"blue" : "0x00",
"green" : "0x00",
"red" : "0x00"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
34 changes: 16 additions & 18 deletions Splito/UI/Home/Account/Feedback/FeedbackView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ struct FeedbackView: View {
.toastView(toast: $viewModel.toast)
.onAppear {
focusField = .title
UIScrollView.appearance().keyboardDismissMode = .interactive
}
.toolbarRole(.editor)
.toolbar {
Expand All @@ -65,6 +64,7 @@ struct FeedbackView: View {
}
.sheet(isPresented: $viewModel.showMediaPicker) {
MultiMediaSelectionPickerView(isPresented: $viewModel.showMediaPicker,
attachmentLimit: abs(viewModel.attachmentsUrls.count - viewModel.MAX_ATTACHMENTS),
onDismiss: viewModel.onMediaPickerSheetDismiss(attachments:))
}
}
Expand All @@ -87,7 +87,7 @@ private struct FeedbackTitleView: View {
.tracking(-0.4)
.frame(maxWidth: .infinity, alignment: .leading)

VSpacer(10)
VSpacer(12)

TextField("", text: $titleText)
.font(.subTitle1())
Expand All @@ -97,14 +97,7 @@ private struct FeedbackTitleView: View {
.autocorrectionDisabled()
.textInputAutocapitalization(.sentences)
.padding(8)
.submitLabel(.next)
.onSubmit {
if focusField.wrappedValue == .title {
focusField.wrappedValue = .description
} else if focusField.wrappedValue == .description {
focusField.wrappedValue = nil
}
}
.submitLabel(.done)
.onTapGestureForced {
focusField.wrappedValue = .title
}
Expand All @@ -114,14 +107,19 @@ private struct FeedbackTitleView: View {
isSelected ? primaryColor : outlineColor, lineWidth: 1)
)

VSpacer(3)
VSpacer(4)

Text(shouldShowValidationMessage ? (isValidTitle ? " " : "Minimum 3 characters are required") : " ")
.foregroundColor(errorColor)
.font(.body1(12))
.foregroundColor(errorColor)
.minimumScaleFactor(0.5)
.lineLimit(1)
.onChange(of: shouldShowValidationMessage) { showMessage in
if showMessage && !isValidTitle {
focusField.wrappedValue = .title
}
}
}
}
}
Expand All @@ -134,7 +132,7 @@ private struct FeedbackDescriptionView: View {
let isSelected: Bool

var body: some View {
VStack(spacing: 8) {
VStack(spacing: 12) {
Text("Description")
.font(.body2())
.foregroundColor(disableText)
Expand Down Expand Up @@ -204,8 +202,9 @@ private struct FeedbackAddAttachmentView: View {
Text("Add attachment")
.font(.body1())
}
.foregroundColor(uploadingAttachments.isEmpty ? disableText : primaryText)
.foregroundColor(disableText)
}
.disabled(!uploadingAttachments.isEmpty)
.buttonStyle(.scale)
}
.frame(maxWidth: .infinity, alignment: .leading)
Expand Down Expand Up @@ -271,25 +270,24 @@ private struct AttachmentThumbnailView: View {
}

Rectangle()
.frame(width: 50, height: 50, alignment: .leading)
.foregroundColor(isUploading || showRetryButton ? lowestText : .clear)
.foregroundColor(isUploading || showRetryButton ? secondaryLightText : .clear)

if attachment.video != nil && !isUploading {
Image(systemName: "play.fill")
.font(.system(size: 20))
.font(.system(size: 22))
.foregroundColor(primaryColor)
}

if isUploading {
ImageLoaderView(tintColor: primaryText)
ImageLoaderView(tintColor: primaryDarkText)
}

if showRetryButton {
Button {
onRetryButtonTap(attachment)
} label: {
Image(systemName: "arrow.clockwise.circle.fill")
.font(.system(size: 20))
.font(.system(size: 22))
.foregroundColor(primaryColor)
}
}
Expand Down
6 changes: 6 additions & 0 deletions Splito/UI/Home/Account/Feedback/FeedbackViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Data
import BaseStyle

class FeedbackViewModel: BaseViewModel, ObservableObject {
let MAX_ATTACHMENTS = 5
private let TITLE_CHARACTER_MIN_LIMIT = 3
private let VIDEO_SIZE_LIMIT_IN_BYTES = 5000000 // 5 MB
private let IMAGE_SIZE_LIMIT_IN_BYTES = 3000000 // 3 MB
Expand Down Expand Up @@ -86,6 +87,11 @@ extension FeedbackViewModel {
}

func handleAttachmentTap() {
if attachmentsUrls.count >= MAX_ATTACHMENTS {
handleError(message: "Maximum \(MAX_ATTACHMENTS) attachments allowed.")
return
}

if selectedAttachments.isEmpty {
showMediaPicker = true
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,18 @@ public struct MultiMediaSelectionPickerView: UIViewControllerRepresentable {

@Binding var isPresented: Bool

let attachmentLimit: Int
let onDismiss: ([Attachment]) -> Void

public init(isPresented: Binding<Bool>, onDismiss: @escaping ([Attachment]) -> Void) {
public init(isPresented: Binding<Bool>, attachmentLimit: Int, onDismiss: @escaping ([Attachment]) -> Void) {
self._isPresented = isPresented
self.attachmentLimit = attachmentLimit
self.onDismiss = onDismiss
}

public func makeUIViewController(context: Context) -> PHPickerViewController {
var configuration = PHPickerConfiguration()
configuration.selectionLimit = 5
configuration.selectionLimit = attachmentLimit
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = context.coordinator
picker.view.tintColor = UIColor(infoColor)
Expand Down Expand Up @@ -58,10 +60,10 @@ public struct MultiMediaSelectionPickerView: UIViewControllerRepresentable {

let dispatchGroup = DispatchGroup()

for attachment in results {
if attachment.itemProvider.canLoadObject(ofClass: UIImage.self) {
dispatchGroup.enter()
results.forEach { attachment in
dispatchGroup.enter()

if attachment.itemProvider.canLoadObject(ofClass: UIImage.self) {
attachment.itemProvider.loadObject(ofClass: UIImage.self) { newImage, error in
if let selectedImage = newImage as? UIImage, let fileName = attachment.itemProvider.suggestedName {
let imageObject = Attachment(image: selectedImage.resizeImageIfNeededWhilePreservingAspectRatio(), name: fileName)
Expand All @@ -72,8 +74,6 @@ public struct MultiMediaSelectionPickerView: UIViewControllerRepresentable {
dispatchGroup.leave()
}
} else if attachment.itemProvider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) {
dispatchGroup.enter()

attachment.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { url, error in // it will return the temporary file address, so immediately retrieving video as data from the temporary url.
if let url = url, let fileName = attachment.itemProvider.suggestedName {
do {
Expand All @@ -83,7 +83,7 @@ public struct MultiMediaSelectionPickerView: UIViewControllerRepresentable {
} catch {
LogE("MultiMediaSelectionPickerCoordinator: \(#function) Error loading data from URL: \(error)")
}
} else if let error = error {
} else if let error {
LogE("MultiMediaSelectionPickerCoordinator: \(#function) Error in loading video: \(error)")
}
dispatchGroup.leave()
Expand Down
25 changes: 0 additions & 25 deletions functions/firebase-debug.log

This file was deleted.

38 changes: 0 additions & 38 deletions functions/src/contact_support_service/contact_support_service.ts

This file was deleted.

48 changes: 48 additions & 0 deletions functions/src/feedback/feedback_service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* eslint-disable */

import {MailService} from "../mail/mail_service";

export class FeedbackService {
private mailService: MailService;

constructor(mailService: MailService) {
this.mailService = mailService;
}

async onFeedbackCreated(support: any): Promise<any> {
let body = "Feedback request created\n\n";

for (const key of Object.keys(support)) {
let value = support[key];

// Check if the value is a Date object or has to be formatted & Convert created_at to local (IST) time
if (key === "created_at" && typeof value === "object") {
const date = new Date(value.seconds * 1000); // Convert Firebase Timestamp to Date
value = date.toLocaleString("en-IN", {
timeZone: "Asia/Kolkata", // Set timezone to IST
year: "numeric",
month: "long",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: true,
});
}

// If the value is attachment urls separate them with a line break
if (key === "attachment_urls" && Array.isArray(value)) {
value = value.join("\n"); // Each URL in a new line
}

body += `${key}: ${value}\n`;
}

await this.mailService.sendEmail(
["[email protected]"],
"[email protected]",
"Splito: Feedback Request Created",
body,
);
}
}
47 changes: 24 additions & 23 deletions functions/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
/* eslint-disable */

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import * as logger from 'firebase-functions/logger';
import { onGroupWrite } from './users_service/users_service';
import { onActivityCreate } from './notifications_service/notifications_service';
import { MailService } from './mail_service/mail_service';
import { SupportService } from './contact_support_service/contact_support_service';
import { onGroupWrite } from './users/users_service';
import { onActivityCreate } from './notifications/notifications_service';
import {FeedbackService} from "./feedback/feedback_service";
import {onDocumentCreated} from "firebase-functions/v2/firestore";
import { MailService } from './mail/mail_service';

// Initialize Firebase app if not already initialized
if (admin.apps.length === 0) {
Expand All @@ -21,25 +20,27 @@ if (admin.apps.length === 0) {
logger.debug('Firebase app already initialized');
}

// Initialize MailService and SupportService
const mailService = new MailService();
const supportService = new SupportService(mailService);
const REGION = "asia-south1";
const feedbackService = new FeedbackService(mailService);

// Cloud Function to send support emails via HTTP request
exports.sendSupportEmail = functions.https.onRequest(async (req, res) => {
try {
const supportData = req.body; // Assuming support data is sent in the body of the request
if (!supportData) {
res.status(400).send('Missing support data');
exports.onGroupWrite = onGroupWrite;
exports.onActivityCreate = onActivityCreate;

export const feedbackCreateObserver = onDocumentCreated(
{ region: REGION, document: "feedbacks/{feedbackId}" },
async (event) => {
const snapshot = event.data;
if (!snapshot) {
logger.error("No data associated with the event");
return;
}
await supportService.onContactSupportCreated(supportData);
res.status(200).send('Support email sent successfully');
} catch (error) {
logger.error('Error sending support email:', error);
res.status(500).send('Error sending support email');
}
});
const data = snapshot.data();

exports.onGroupWrite = onGroupWrite;
exports.onActivityCreate = onActivityCreate;
try {
await feedbackService.onFeedbackCreated(data);
} catch (error) {
logger.error('Error handling feedback:', error);
}
}
);
Loading

0 comments on commit e4536bf

Please sign in to comment.