Skip to content

Commit 36fe3f8

Browse files
Implement file storage for profile pictures and university logos
Co-authored-by: abdallahmehiz <54363735+abdallahmehiz@users.noreply.github.com>
1 parent f731b71 commit 36fe3f8

File tree

10 files changed

+245
-43
lines changed

10 files changed

+245
-43
lines changed

composeApp/src/commonMain/kotlin/di/ApplicationModule.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ package di
33
import org.koin.core.module.Module
44
import org.koin.dsl.module
55
import utils.CredentialManager
6+
import utils.FileStorageManager
67
import utils.PlatformUtils
78

89
val ApplicationModule: (
910
credentialManger: CredentialManager,
10-
platformUtils: PlatformUtils
11-
) -> Module = { credentialManager, platformUtils ->
11+
platformUtils: PlatformUtils,
12+
dataPath: String
13+
) -> Module = { credentialManager, platformUtils, dataPath ->
1214
module {
1315
single { platformUtils }
1416
single { credentialManager }
17+
single { FileStorageManager(dataPath) }
1518
}
1619
}

composeApp/src/commonMain/kotlin/di/InitKoin.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ fun initKoin(
1515
PreferencesModule(datastorePath),
1616
DomainModule,
1717
ScreenModelsModule,
18-
ApplicationModule(credentialManager, platformUtils),
18+
ApplicationModule(credentialManager, platformUtils, datastorePath),
1919
UpdateCheckerModule,
2020
)
2121
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package utils
2+
3+
import kotlinx.coroutines.Dispatchers
4+
import kotlinx.coroutines.withContext
5+
import okio.FileSystem
6+
import okio.Path
7+
import okio.Path.Companion.toPath
8+
9+
/**
10+
* Manages file storage operations for profile pictures and university logos
11+
*/
12+
class FileStorageManager(
13+
private val baseDataPath: String
14+
) {
15+
private val fileSystem = FileSystem.SYSTEM
16+
private val imagesDir = "$baseDataPath/images".toPath()
17+
private val profilePicturesDir = imagesDir / "profiles"
18+
private val universityLogosDir = imagesDir / "logos"
19+
20+
init {
21+
// Ensure directories exist
22+
try {
23+
fileSystem.createDirectories(profilePicturesDir)
24+
fileSystem.createDirectories(universityLogosDir)
25+
} catch (e: Exception) {
26+
e.printStackTrace()
27+
}
28+
}
29+
30+
/**
31+
* Saves a profile picture and returns the file path
32+
*/
33+
suspend fun saveProfilePicture(studentId: String, imageData: ByteArray): String? = withContext(Dispatchers.IO) {
34+
try {
35+
val fileName = "profile_${studentId.replace(Regex("[^a-zA-Z0-9]"), "_")}.jpg"
36+
val filePath = profilePicturesDir / fileName
37+
38+
fileSystem.write(filePath) {
39+
write(imageData)
40+
}
41+
42+
filePath.toString()
43+
} catch (e: Exception) {
44+
e.printStackTrace()
45+
null
46+
}
47+
}
48+
49+
/**
50+
* Saves a university logo and returns the file path
51+
*/
52+
suspend fun saveUniversityLogo(establishmentId: String, imageData: ByteArray): String? = withContext(Dispatchers.IO) {
53+
try {
54+
val fileName = "logo_${establishmentId.replace(Regex("[^a-zA-Z0-9]"), "_")}.jpg"
55+
val filePath = universityLogosDir / fileName
56+
57+
fileSystem.write(filePath) {
58+
write(imageData)
59+
}
60+
61+
filePath.toString()
62+
} catch (e: Exception) {
63+
e.printStackTrace()
64+
null
65+
}
66+
}
67+
68+
/**
69+
* Loads image data from a file path
70+
*/
71+
suspend fun loadImage(filePath: String): ByteArray? = withContext(Dispatchers.IO) {
72+
try {
73+
val path = filePath.toPath()
74+
if (fileSystem.exists(path)) {
75+
fileSystem.read(path) {
76+
readByteArray()
77+
}
78+
} else {
79+
null
80+
}
81+
} catch (e: Exception) {
82+
e.printStackTrace()
83+
null
84+
}
85+
}
86+
87+
/**
88+
* Deletes an image file
89+
*/
90+
suspend fun deleteImage(filePath: String): Boolean = withContext(Dispatchers.IO) {
91+
try {
92+
val path = filePath.toPath()
93+
if (fileSystem.exists(path)) {
94+
fileSystem.delete(path)
95+
true
96+
} else {
97+
false
98+
}
99+
} catch (e: Exception) {
100+
e.printStackTrace()
101+
false
102+
}
103+
}
104+
105+
/**
106+
* Deletes all profile pictures
107+
*/
108+
suspend fun deleteAllProfilePictures(): Boolean = withContext(Dispatchers.IO) {
109+
try {
110+
if (fileSystem.exists(profilePicturesDir)) {
111+
fileSystem.deleteRecursively(profilePicturesDir)
112+
fileSystem.createDirectories(profilePicturesDir)
113+
true
114+
} else {
115+
true
116+
}
117+
} catch (e: Exception) {
118+
e.printStackTrace()
119+
false
120+
}
121+
}
122+
123+
/**
124+
* Deletes all university logos
125+
*/
126+
suspend fun deleteAllUniversityLogos(): Boolean = withContext(Dispatchers.IO) {
127+
try {
128+
if (fileSystem.exists(universityLogosDir)) {
129+
fileSystem.deleteRecursively(universityLogosDir)
130+
fileSystem.createDirectories(universityLogosDir)
131+
true
132+
} else {
133+
true
134+
}
135+
} catch (e: Exception) {
136+
e.printStackTrace()
137+
false
138+
}
139+
}
140+
141+
/**
142+
* Checks if an image file exists
143+
*/
144+
fun imageExists(filePath: String): Boolean {
145+
return try {
146+
fileSystem.exists(filePath.toPath())
147+
} catch (e: Exception) {
148+
false
149+
}
150+
}
151+
}

domain/data/src/commonMain/kotlin/mehiz/abdallah/progres/data/daos/IndividualInfoDao.kt

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package mehiz.abdallah.progres.data.daos
22

33
import mehiz.abdallah.progres.data.db.IndividualInfoTable
44
import mehiz.abdallah.progres.data.db.ProgresDB
5+
import utils.FileStorageManager
56

67
class IndividualInfoDao(
7-
db: ProgresDB
8+
db: ProgresDB,
9+
private val fileStorageManager: FileStorageManager
810
) {
911

1012
private val queries = db.individualInfoTableQueries
@@ -18,7 +20,7 @@ class IndividualInfoDao(
1820
firstNameLatin = firstNameLatin,
1921
lastNameLatin = lastNameLatin,
2022
lastNameArabic = lastNameArabic,
21-
photo = photo,
23+
photoPath = photoPath,
2224
dateOfBirth = dateOfBirth,
2325
placeOfBirthLatin = placeOfBirthLatin,
2426
placeOfBirthArabic = placeOfBirthArabic,
@@ -30,11 +32,18 @@ class IndividualInfoDao(
3032
return queries.getById(id).executeAsOneOrNull()
3133
}
3234

33-
fun getIndividualPhotoById(id: Long): ByteArray? {
34-
return queries.getStudentPhotoById(id).executeAsOne().photo
35+
fun getIndividualPhotoPathById(id: Long): String? {
36+
return queries.getStudentPhotoPathById(id).executeAsOneOrNull()
3537
}
3638

37-
fun deleteAllIndividualInfo() {
39+
suspend fun getIndividualPhotoById(id: Long): ByteArray? {
40+
val photoPath = getIndividualPhotoPathById(id)
41+
return photoPath?.let { fileStorageManager.loadImage(it) }
42+
}
43+
44+
suspend fun deleteAllIndividualInfo() {
45+
// Delete all profile pictures before clearing database
46+
fileStorageManager.deleteAllProfilePictures()
3847
queries.delete()
3948
}
4049

domain/data/src/commonMain/kotlin/mehiz/abdallah/progres/data/daos/StudentCardDao.kt

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package mehiz.abdallah.progres.data.daos
22

33
import mehiz.abdallah.progres.data.db.ProgresDB
44
import mehiz.abdallah.progres.data.db.StudentCardTable
5+
import utils.FileStorageManager
56

67
@Suppress("TooManyFunctions")
78
class StudentCardDao(
8-
db: ProgresDB
9+
db: ProgresDB,
10+
private val fileStorageManager: FileStorageManager
911
) {
1012
private val queries = db.studentCardTableQueries
1113

@@ -37,7 +39,7 @@ class StudentCardDao(
3739
levelId = levelId,
3840
establishmentStringLatin = establishmentStringLatin,
3941
establishmentStringArabic = establishmentStringArabic,
40-
establishmentLogo = establishmentLogo,
42+
establishmentLogoPath = establishmentLogoPath,
4143
cycleStringLatin = cycleStringLatin,
4244
cycleStringArabic = cycleStringArabic
4345
)
@@ -60,11 +62,18 @@ class StudentCardDao(
6062
return queries.getCardByAcademicYear(id).executeAsOne()
6163
}
6264

63-
fun deleteCard(id: Long) {
65+
suspend fun deleteCard(id: Long) {
66+
// Get the establishment logo path before deleting
67+
val card = queries.getCard(id).executeAsOneOrNull()
68+
card?.establishmentLogoPath?.let { logoPath ->
69+
fileStorageManager.deleteImage(logoPath)
70+
}
6471
queries.deleteCardWithId(id)
6572
}
6673

67-
fun deleteAllCards() {
74+
suspend fun deleteAllCards() {
75+
// Delete all university logos before clearing database
76+
fileStorageManager.deleteAllUniversityLogos()
6877
queries.deleteAllCards()
6978
}
7079
}

domain/data/src/commonMain/sqldelight/mehiz/abdallah/progres/data/db/IndividualInfoTable.sq

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ lastNameLatin TEXT NOT NULL,
77
dateOfBirth TEXT NOT NULL,
88
placeOfBirthArabic TEXT,
99
placeOfBirthLatin TEXT,
10-
photo BLOB,
10+
photoPath TEXT,
1111
uuid TEXT NOT NULL
1212
);
1313

@@ -21,7 +21,7 @@ lastNameLatin,
2121
dateOfBirth,
2222
placeOfBirthArabic,
2323
placeOfBirthLatin,
24-
photo,
24+
photoPath,
2525
uuid
2626
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
2727

@@ -31,8 +31,8 @@ SELECT * FROM IndividualInfoTable LIMIT 1;
3131
getById:
3232
SELECT * FROM IndividualInfoTable WHERE id = :id;
3333

34-
getStudentPhotoById:
35-
SELECT photo FROM IndividualInfoTable WHERE id = :id;
34+
getStudentPhotoPathById:
35+
SELECT photoPath FROM IndividualInfoTable WHERE id = :id;
3636

3737
delete:
3838
DELETE FROM IndividualInfoTable;

domain/data/src/commonMain/sqldelight/mehiz/abdallah/progres/data/db/StudentCardTable.sq

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ individualPlaceOfBirthArabic TEXT,
1313
individualPlaceOfBirthLatin TEXT,
1414
establishmentStringArabic TEXT NOT NULL,
1515
establishmentStringLatin TEXT NOT NULL,
16-
establishmentLogo BLOB,
16+
establishmentLogoPath TEXT,
1717
levelId INTEGER NOT NULL,
1818
levelStringLongArabic TEXT NOT NULL,
1919
levelStringLongLatin TEXT NOT NULL,
@@ -44,7 +44,7 @@ individualPlaceOfBirthLatin,
4444
establishmentStringArabic,
4545
establishmentStringLatin,
4646
levelId,
47-
establishmentLogo,
47+
establishmentLogoPath,
4848
levelStringLongArabic,
4949
levelStringLongLatin,
5050
registrationNumber,

0 commit comments

Comments
 (0)