Remove dead code
- Delete ExifWriter.kt (instantiated but never called) - Remove saveJpegFile() and unused imports from PhotoSaver - Remove CameraFlipButton() and unused imports from LensSwitcher - Remove companion object and unused imports from HapticFeedback - Remove getZoomPresets() from LensController - Update README to reflect ExifWriter removal and actual minSdk (35) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ef350e9fb7
commit
41a95885c1
6 changed files with 2 additions and 207 deletions
|
|
@ -18,7 +18,7 @@ A dedicated Android camera app for tilt-shift photography with real-time preview
|
|||
|
||||
## Requirements
|
||||
|
||||
- Android 8.0 (API 26) or higher
|
||||
- Android 15 (API 35) or higher
|
||||
- Device with camera
|
||||
- OpenGL ES 2.0 support
|
||||
|
||||
|
|
@ -66,8 +66,7 @@ app/src/main/java/no/naiv/tiltshift/
|
|||
│ ├── ZoomControl.kt # Zoom UI component
|
||||
│ └── LensSwitcher.kt # Lens selection UI
|
||||
├── storage/
|
||||
│ ├── PhotoSaver.kt # MediaStore integration
|
||||
│ └── ExifWriter.kt # EXIF metadata handling
|
||||
│ └── PhotoSaver.kt # MediaStore integration & EXIF handling
|
||||
└── util/
|
||||
├── OrientationDetector.kt
|
||||
├── LocationProvider.kt
|
||||
|
|
|
|||
|
|
@ -88,11 +88,4 @@ class LensController {
|
|||
return availableLenses[currentLensIndex]
|
||||
}
|
||||
|
||||
/**
|
||||
* Common zoom levels that can be achieved through digital zoom.
|
||||
* These are presented as quick-select buttons.
|
||||
*/
|
||||
fun getZoomPresets(): List<Float> {
|
||||
return listOf(0.5f, 1.0f, 2.0f, 5.0f)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,96 +0,0 @@
|
|||
package no.naiv.tiltshift.storage
|
||||
|
||||
import android.location.Location
|
||||
import androidx.exifinterface.media.ExifInterface
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Writes EXIF metadata to captured images.
|
||||
*/
|
||||
class ExifWriter {
|
||||
|
||||
private val dateTimeFormat = SimpleDateFormat("yyyy:MM:dd HH:mm:ss", Locale.US)
|
||||
|
||||
/**
|
||||
* Writes EXIF data to the specified image file.
|
||||
*/
|
||||
fun writeExifData(
|
||||
file: File,
|
||||
orientation: Int,
|
||||
location: Location?,
|
||||
make: String = "Android",
|
||||
model: String = android.os.Build.MODEL
|
||||
) {
|
||||
try {
|
||||
val exif = ExifInterface(file)
|
||||
|
||||
// Orientation
|
||||
exif.setAttribute(ExifInterface.TAG_ORIENTATION, orientation.toString())
|
||||
|
||||
// Date/time
|
||||
val dateTime = dateTimeFormat.format(Date())
|
||||
exif.setAttribute(ExifInterface.TAG_DATETIME, dateTime)
|
||||
exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, dateTime)
|
||||
exif.setAttribute(ExifInterface.TAG_DATETIME_DIGITIZED, dateTime)
|
||||
|
||||
// Camera info
|
||||
exif.setAttribute(ExifInterface.TAG_MAKE, make)
|
||||
exif.setAttribute(ExifInterface.TAG_MODEL, model)
|
||||
exif.setAttribute(ExifInterface.TAG_SOFTWARE, "Tilt-Shift Camera")
|
||||
|
||||
// GPS location
|
||||
if (location != null) {
|
||||
setLocationExif(exif, location)
|
||||
}
|
||||
|
||||
exif.saveAttributes()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setLocationExif(exif: ExifInterface, location: Location) {
|
||||
// Latitude
|
||||
val latitude = location.latitude
|
||||
val latRef = if (latitude >= 0) "N" else "S"
|
||||
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, convertToDMS(Math.abs(latitude)))
|
||||
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, latRef)
|
||||
|
||||
// Longitude
|
||||
val longitude = location.longitude
|
||||
val lonRef = if (longitude >= 0) "E" else "W"
|
||||
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, convertToDMS(Math.abs(longitude)))
|
||||
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, lonRef)
|
||||
|
||||
// Altitude
|
||||
if (location.hasAltitude()) {
|
||||
val altitude = location.altitude
|
||||
val altRef = if (altitude >= 0) "0" else "1"
|
||||
exif.setAttribute(ExifInterface.TAG_GPS_ALTITUDE, "${Math.abs(altitude).toLong()}/1")
|
||||
exif.setAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF, altRef)
|
||||
}
|
||||
|
||||
// Timestamp
|
||||
val gpsTimeFormat = SimpleDateFormat("HH:mm:ss", Locale.US)
|
||||
val gpsDateFormat = SimpleDateFormat("yyyy:MM:dd", Locale.US)
|
||||
val timestamp = Date(location.time)
|
||||
exif.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, gpsTimeFormat.format(timestamp))
|
||||
exif.setAttribute(ExifInterface.TAG_GPS_DATESTAMP, gpsDateFormat.format(timestamp))
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts decimal degrees to DMS (degrees/minutes/seconds) format for EXIF.
|
||||
*/
|
||||
private fun convertToDMS(coordinate: Double): String {
|
||||
val degrees = coordinate.toInt()
|
||||
val minutesDecimal = (coordinate - degrees) * 60
|
||||
val minutes = minutesDecimal.toInt()
|
||||
val seconds = (minutesDecimal - minutes) * 60
|
||||
|
||||
// EXIF format: "degrees/1,minutes/1,seconds/1000"
|
||||
return "$degrees/1,$minutes/1,${(seconds * 1000).toLong()}/1000"
|
||||
}
|
||||
}
|
||||
|
|
@ -3,9 +3,6 @@ package no.naiv.tiltshift.storage
|
|||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.location.Location
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
|
|
@ -15,8 +12,6 @@ import android.util.Log
|
|||
import androidx.exifinterface.media.ExifInterface
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
|
@ -38,8 +33,6 @@ class PhotoSaver(private val context: Context) {
|
|||
private const val TAG = "PhotoSaver"
|
||||
}
|
||||
|
||||
private val exifWriter = ExifWriter()
|
||||
|
||||
private val fileNameFormat = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US)
|
||||
|
||||
/**
|
||||
|
|
@ -97,61 +90,6 @@ class PhotoSaver(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a JPEG file (from CameraX ImageCapture) to the gallery.
|
||||
*/
|
||||
suspend fun saveJpegFile(
|
||||
sourceFile: File,
|
||||
orientation: Int,
|
||||
location: Location?
|
||||
): SaveResult = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val fileName = "TILTSHIFT_${fileNameFormat.format(Date())}.jpg"
|
||||
|
||||
val contentValues = ContentValues().apply {
|
||||
put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
|
||||
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
|
||||
put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000)
|
||||
put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis())
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
put(MediaStore.Images.Media.RELATIVE_PATH, "${Environment.DIRECTORY_PICTURES}/TiltShift")
|
||||
put(MediaStore.Images.Media.IS_PENDING, 1)
|
||||
}
|
||||
}
|
||||
|
||||
val contentResolver = context.contentResolver
|
||||
val uri = contentResolver.insert(
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
contentValues
|
||||
) ?: return@withContext SaveResult.Error("Failed to create MediaStore entry")
|
||||
|
||||
// Copy file to MediaStore
|
||||
contentResolver.openOutputStream(uri)?.use { outputStream ->
|
||||
sourceFile.inputStream().use { inputStream ->
|
||||
inputStream.copyTo(outputStream)
|
||||
}
|
||||
} ?: return@withContext SaveResult.Error("Failed to open output stream")
|
||||
|
||||
// Write EXIF data
|
||||
writeExifToUri(uri, orientation, location)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
contentValues.clear()
|
||||
contentValues.put(MediaStore.Images.Media.IS_PENDING, 0)
|
||||
contentResolver.update(uri, contentValues, null, null)
|
||||
}
|
||||
|
||||
// Clean up source file
|
||||
sourceFile.delete()
|
||||
|
||||
val path = getPathFromUri(uri)
|
||||
SaveResult.Success(uri, path)
|
||||
} catch (e: Exception) {
|
||||
SaveResult.Error("Failed to save photo: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun writeExifToUri(uri: Uri, orientation: Int, location: Location?) {
|
||||
try {
|
||||
context.contentResolver.openFileDescriptor(uri, "rw")?.use { pfd ->
|
||||
|
|
|
|||
|
|
@ -9,9 +9,6 @@ import androidx.compose.foundation.layout.Row
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.CameraRear
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
|
|
@ -90,28 +87,3 @@ private fun LensButton(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple camera flip button (for future front camera support).
|
||||
*/
|
||||
@Composable
|
||||
fun CameraFlipButton(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color(0x80000000))
|
||||
.clickable(onClick = onClick),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.CameraRear,
|
||||
contentDescription = "Switch Camera",
|
||||
tint = Color.White,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ import android.os.Build
|
|||
import android.os.VibrationEffect
|
||||
import android.os.Vibrator
|
||||
import android.os.VibratorManager
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.View
|
||||
|
||||
/**
|
||||
* Provides haptic feedback for user interactions.
|
||||
|
|
@ -86,13 +84,4 @@ class HapticFeedback(private val context: Context) {
|
|||
vibrator.vibrate(longArrayOf(0, 50, 30, 50, 30, 50), -1)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Use system haptic feedback on a View for standard interactions.
|
||||
*/
|
||||
fun performHapticFeedback(view: View, feedbackConstant: Int = HapticFeedbackConstants.VIRTUAL_KEY) {
|
||||
view.performHapticFeedback(feedbackConstant)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue