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:
Ole-Morten Duesund 2026-02-27 15:24:17 +01:00
commit 41a95885c1
6 changed files with 2 additions and 207 deletions

View file

@ -18,7 +18,7 @@ A dedicated Android camera app for tilt-shift photography with real-time preview
## Requirements ## Requirements
- Android 8.0 (API 26) or higher - Android 15 (API 35) or higher
- Device with camera - Device with camera
- OpenGL ES 2.0 support - OpenGL ES 2.0 support
@ -66,8 +66,7 @@ app/src/main/java/no/naiv/tiltshift/
│ ├── ZoomControl.kt # Zoom UI component │ ├── ZoomControl.kt # Zoom UI component
│ └── LensSwitcher.kt # Lens selection UI │ └── LensSwitcher.kt # Lens selection UI
├── storage/ ├── storage/
│ ├── PhotoSaver.kt # MediaStore integration │ └── PhotoSaver.kt # MediaStore integration & EXIF handling
│ └── ExifWriter.kt # EXIF metadata handling
└── util/ └── util/
├── OrientationDetector.kt ├── OrientationDetector.kt
├── LocationProvider.kt ├── LocationProvider.kt

View file

@ -88,11 +88,4 @@ class LensController {
return availableLenses[currentLensIndex] 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)
}
} }

View file

@ -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"
}
}

View file

@ -3,9 +3,6 @@ package no.naiv.tiltshift.storage
import android.content.ContentValues import android.content.ContentValues
import android.content.Context import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Paint
import android.location.Location import android.location.Location
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
@ -15,8 +12,6 @@ import android.util.Log
import androidx.exifinterface.media.ExifInterface import androidx.exifinterface.media.ExifInterface
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileOutputStream
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
@ -38,8 +33,6 @@ class PhotoSaver(private val context: Context) {
private const val TAG = "PhotoSaver" private const val TAG = "PhotoSaver"
} }
private val exifWriter = ExifWriter()
private val fileNameFormat = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US) 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?) { private fun writeExifToUri(uri: Uri, orientation: Int, location: Location?) {
try { try {
context.contentResolver.openFileDescriptor(uri, "rw")?.use { pfd -> context.contentResolver.openFileDescriptor(uri, "rw")?.use { pfd ->

View file

@ -9,9 +9,6 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape 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.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue 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)
)
}
}

View file

@ -5,8 +5,6 @@ import android.os.Build
import android.os.VibrationEffect import android.os.VibrationEffect
import android.os.Vibrator import android.os.Vibrator
import android.os.VibratorManager import android.os.VibratorManager
import android.view.HapticFeedbackConstants
import android.view.View
/** /**
* Provides haptic feedback for user interactions. * 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) 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)
}
}
} }