11package app.grapheneos.camera.ui.composable.components
22
3- import android.annotation.SuppressLint
4- import android.content.Context
5- import android.media.MediaMetadataRetriever
6- import android.net.Uri
7- import android.provider.MediaStore.MediaColumns
8- import android.provider.OpenableColumns
93import androidx.compose.foundation.background
104import androidx.compose.foundation.layout.Box
115import androidx.compose.foundation.layout.Column
12- import androidx.compose.foundation.layout.fillMaxHeight
6+
137import androidx.compose.foundation.layout.padding
148import androidx.compose.foundation.rememberScrollState
159import androidx.compose.foundation.shape.RoundedCornerShape
@@ -18,9 +12,6 @@ import androidx.compose.material3.ProvideTextStyle
1812import androidx.compose.material3.Text
1913import androidx.compose.material3.TextButton
2014import androidx.compose.runtime.Composable
21- import androidx.compose.runtime.MutableState
22- import androidx.compose.runtime.mutableStateOf
23- import androidx.compose.runtime.saveable.Saver
2415import androidx.compose.ui.Alignment
2516import androidx.compose.ui.Modifier
2617import androidx.compose.ui.graphics.Color
@@ -30,153 +21,9 @@ import androidx.compose.ui.text.font.FontWeight
3021import androidx.compose.ui.unit.dp
3122import androidx.compose.ui.unit.sp
3223import androidx.compose.ui.window.Dialog
33- import androidxc.exifinterface.media.ExifInterface
34- import app.grapheneos.camera.CapturedItem
35- import app.grapheneos.camera.ITEM_TYPE_VIDEO
3624import app.grapheneos.camera.R
25+ import app.grapheneos.camera.ui.composable.data.MediaItemDetails
3726import app.grapheneos.camera.ui.theme.AppColor
38- import app.grapheneos.camera.util.storageLocationToUiString
39- import java.text.SimpleDateFormat
40- import java.util.Date
41- import java.util.Locale
42- import java.util.TimeZone
43-
44- class MediaItemDetails private constructor(
45- val filePath : String? ,
46- val fileName : String? ,
47- val size : String? ,
48- var dateAdded : String? ,
49- var dateModified : String? ,
50- ) {
51-
52- companion object {
53-
54-
55- val Saver = Saver <MutableState <MediaItemDetails ?>, Array <String ?>>(
56- save = {
57- val item = it.value ? : return @Saver arrayOf()
58- arrayOf(item.filePath, item.fileName, item.size, item.dateAdded, item.dateModified)
59- },
60- restore = {
61- if (it.isEmpty()) return @Saver null
62- mutableStateOf(MediaItemDetails (it[0 ], it[1 ], it[2 ], it[3 ], it[4 ]))
63- },
64- )
65-
66- fun forCapturedItem (context : Context , item : CapturedItem ) : MediaItemDetails {
67-
68- var relativePath: String? = null
69- var fileName: String? = null
70- var size: String? = null
71-
72- var dateAdded: String? = null
73- var dateModified: String? = null
74-
75- val projection = arrayOf(MediaColumns .RELATIVE_PATH , OpenableColumns .DISPLAY_NAME , OpenableColumns .SIZE )
76-
77- context.contentResolver.query(item.uri, projection, null ,null )?.use {
78- if (it.moveToFirst()) {
79- fileName = it.getString(1 )
80-
81- if (fileName == null ) {
82- throw Exception (" File name not found for file" )
83- }
84-
85- relativePath = getRelativePath(context, item.uri, it.getString(0 ), fileName!! )
86- size = String .format(Locale .ROOT , " %.2f MB" , (it.getLong(2 ) / (1000f * 1000f )))
87- }
88- }
89-
90- if (item.type == ITEM_TYPE_VIDEO ) {
91- MediaMetadataRetriever ().use {
92- it.setDataSource(context, item.uri)
93- dateAdded = convertTimeForVideo(it.extractMetadata(MediaMetadataRetriever .METADATA_KEY_DATE )!! )
94- dateModified = dateAdded
95-
96- }
97- } else {
98- context.contentResolver.openInputStream(item.uri)?.use { stream ->
99- val eInterface = ExifInterface (stream)
100-
101- val offset = eInterface.getAttribute(ExifInterface .TAG_OFFSET_TIME )
102-
103- if (eInterface.hasAttribute(ExifInterface .TAG_DATETIME_ORIGINAL )) {
104- dateAdded = convertTimeForPhoto(
105- eInterface.getAttribute(ExifInterface .TAG_DATETIME_ORIGINAL )!! ,
106- offset
107- )
108- }
109-
110- if (eInterface.hasAttribute(ExifInterface .TAG_DATETIME )) {
111- dateModified = convertTimeForPhoto(
112- eInterface.getAttribute(ExifInterface .TAG_DATETIME )!! ,
113- offset
114- )
115- }
116- }
117- }
118-
119- return MediaItemDetails (
120- relativePath,
121- fileName,
122- size,
123- dateAdded,
124- dateModified,
125- )
126- }
127-
128- @SuppressLint(" SimpleDateFormat" )
129- fun convertTime (time : Long , showTimeZone : Boolean = true): String {
130- val date = Date (time)
131- val format = SimpleDateFormat (
132- if (showTimeZone) {
133- " yyyy-MM-dd HH:mm:ss z"
134- } else {
135- " yyyy-MM-dd HH:mm:ss"
136- }
137- )
138- format.timeZone = TimeZone .getDefault()
139- return format.format(date)
140- }
141-
142- private fun convertTimeForVideo (time : String ): String {
143- val dateFormat = SimpleDateFormat (" yyyyMMdd'T'HHmmss.SSS'Z'" , Locale .US )
144- dateFormat.timeZone = TimeZone .getTimeZone(" UTC" )
145- val parsedDate = dateFormat.parse(time)
146- return convertTime(parsedDate?.time ? : 0 )
147- }
148-
149- private fun convertTimeForPhoto (time : String , offset : String? = null): String {
150- val timestamp = if (offset != null ) {
151- " $time $offset "
152- } else {
153- time
154- }
155-
156- val dateFormat = SimpleDateFormat (
157- if (offset == null ) {
158- " yyyy:MM:dd HH:mm:ss"
159- } else {
160- " yyyy:MM:dd HH:mm:ss Z"
161- }, Locale .US
162- )
163-
164- if (offset == null ) {
165- dateFormat.timeZone = TimeZone .getDefault()
166- }
167- val parsedDate = dateFormat.parse(timestamp)
168- return convertTime(parsedDate?.time ? : 0 , offset != null )
169- }
170-
171- private fun getRelativePath (ctx : Context , uri : Uri , path : String? , fileName : String ): String {
172- if (path == null ) {
173- return storageLocationToUiString(ctx, uri.toString())
174- }
175-
176- return " ${ctx.getString(R .string.main_storage)} /$path$fileName "
177- }
178- }
179- }
18027
18128@Composable
18229fun MediaInfoDialog (
0 commit comments