diff --git a/app/src/main/java/gallery/memories/MainActivity.kt b/app/src/main/java/gallery/memories/MainActivity.kt index 514d9863..163a40d5 100644 --- a/app/src/main/java/gallery/memories/MainActivity.kt +++ b/app/src/main/java/gallery/memories/MainActivity.kt @@ -26,7 +26,8 @@ import androidx.media3.exoplayer.hls.HlsMediaSource import androidx.media3.exoplayer.source.ProgressiveMediaSource import gallery.memories.databinding.ActivityMainBinding -@UnstableApi class MainActivity : AppCompatActivity() { +@UnstableApi +class MainActivity : AppCompatActivity() { companion object { val TAG = MainActivity::class.java.simpleName } @@ -124,7 +125,10 @@ import gallery.memories.databinding.ActivityMainBinding private fun initializeWebView() { // Intercept local APIs binding.webview.webViewClient = object : WebViewClient() { - override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { + override fun shouldOverrideUrlLoading( + view: WebView, + request: WebResourceRequest + ): Boolean { val pathMatches = request.url.path?.matches(memoriesRegex) == true val hostMatches = request.url.host.equals(host) if (pathMatches && hostMatches) { @@ -137,7 +141,10 @@ import gallery.memories.databinding.ActivityMainBinding return true } - override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? { + override fun shouldInterceptRequest( + view: WebView, + request: WebResourceRequest + ): WebResourceResponse? { return if (request.url.host == "127.0.0.1") { nativex.handleRequest(request) } else null @@ -194,9 +201,11 @@ import gallery.memories.databinding.ActivityMainBinding host = Uri.parse(memoriesUrl).host // Set authorization header - binding.webview.loadUrl(memoriesUrl, mapOf( - "Authorization" to authHeader - )) + binding.webview.loadUrl( + memoriesUrl, mapOf( + "Authorization" to authHeader + ) + ) return true } @@ -207,7 +216,8 @@ import gallery.memories.databinding.ActivityMainBinding fun ensureStoragePermissions() { val requestPermissionLauncher = registerForActivityResult( - ActivityResultContracts.RequestMultiplePermissions()) { permissions -> + ActivityResultContracts.RequestMultiplePermissions() + ) { permissions -> // we need all of these val isGranted = permissions.all { it.value } @@ -236,10 +246,12 @@ import gallery.memories.databinding.ActivityMainBinding // Request media read permission if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - requestPermissionLauncher.launch(arrayOf( - android.Manifest.permission.READ_MEDIA_IMAGES, - android.Manifest.permission.READ_MEDIA_VIDEO, - )) + requestPermissionLauncher.launch( + arrayOf( + android.Manifest.permission.READ_MEDIA_IMAGES, + android.Manifest.permission.READ_MEDIA_VIDEO, + ) + ) } else { requestPermissionLauncher.launch(arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE)) } @@ -278,7 +290,8 @@ import gallery.memories.databinding.ActivityMainBinding DefaultHttpDataSource.Factory() .setDefaultRequestProperties(mapOf("cookie" to cookies)) .setAllowCrossProtocolRedirects(true) - val dataSourceFactory = DefaultDataSource.Factory(this, httpDataSourceFactory) + val dataSourceFactory = + DefaultDataSource.Factory(this, httpDataSourceFactory) // Check if HLS source from URI (contains .m3u8 anywhere) exoPlayer.addMediaSource( @@ -355,10 +368,15 @@ import gallery.memories.databinding.ActivityMainBinding // Set dark mode setTheme(if (isDark) android.R.style.Theme_Black else android.R.style.Theme_Light) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - val appearance = WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS or WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS - window.insetsController?.setSystemBarsAppearance(if (isDark) 0 else appearance, appearance) + val appearance = + WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS or WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS + window.insetsController?.setSystemBarsAppearance( + if (isDark) 0 else appearance, + appearance + ) } else { - window.decorView.systemUiVisibility = if (isDark) 0 else View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR + window.decorView.systemUiVisibility = + if (isDark) 0 else View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR } // Set colors @@ -390,8 +408,11 @@ import gallery.memories.databinding.ActivityMainBinding // Schedule for resume if not active if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED) || force) { - mNeedRefresh = false - binding.webview.evaluateJavascript("window._nc_event_bus?.emit('files:file:created')", null) + mNeedRefresh = false + binding.webview.evaluateJavascript( + "window._nc_event_bus?.emit('files:file:created')", + null + ) } else { mNeedRefresh = true } diff --git a/app/src/main/java/gallery/memories/dao/AppDatabase.kt b/app/src/main/java/gallery/memories/dao/AppDatabase.kt index 7b1f92c0..8c20c59f 100644 --- a/app/src/main/java/gallery/memories/dao/AppDatabase.kt +++ b/app/src/main/java/gallery/memories/dao/AppDatabase.kt @@ -15,7 +15,8 @@ abstract class AppDatabase : RoomDatabase() { companion object { private val DATABASE_NAME = "memories_room" - @Volatile private var INSTANCE: AppDatabase? = null + @Volatile + private var INSTANCE: AppDatabase? = null fun get(context: Context): AppDatabase { if (INSTANCE == null) { diff --git a/app/src/main/java/gallery/memories/mapper/Bucket.kt b/app/src/main/java/gallery/memories/mapper/Bucket.kt index 9aca8df1..6ace36fa 100644 --- a/app/src/main/java/gallery/memories/mapper/Bucket.kt +++ b/app/src/main/java/gallery/memories/mapper/Bucket.kt @@ -2,7 +2,7 @@ package gallery.memories.mapper import androidx.room.ColumnInfo -data class Bucket ( - @ColumnInfo(name="bucket_id") val id: String, - @ColumnInfo(name="bucket_name") val name: String, +data class Bucket( + @ColumnInfo(name = "bucket_id") val id: String, + @ColumnInfo(name = "bucket_name") val name: String, ) \ No newline at end of file diff --git a/app/src/main/java/gallery/memories/mapper/Day.kt b/app/src/main/java/gallery/memories/mapper/Day.kt index 5a73b876..e7926ba4 100644 --- a/app/src/main/java/gallery/memories/mapper/Day.kt +++ b/app/src/main/java/gallery/memories/mapper/Day.kt @@ -2,7 +2,7 @@ package gallery.memories.mapper import androidx.room.ColumnInfo -data class Day ( - @ColumnInfo(name="dayid") val dayId: Long, - @ColumnInfo(name="count") val count: Long +data class Day( + @ColumnInfo(name = "dayid") val dayId: Long, + @ColumnInfo(name = "count") val count: Long ) \ No newline at end of file diff --git a/app/src/main/java/gallery/memories/mapper/Response.kt b/app/src/main/java/gallery/memories/mapper/Response.kt index 7ae3695b..f189495d 100644 --- a/app/src/main/java/gallery/memories/mapper/Response.kt +++ b/app/src/main/java/gallery/memories/mapper/Response.kt @@ -4,8 +4,9 @@ import org.json.JSONObject class Response { companion object { - val OK get(): JSONObject { - return JSONObject().put("message", "ok") - } + val OK + get(): JSONObject { + return JSONObject().put("message", "ok") + } } } \ No newline at end of file diff --git a/app/src/main/java/gallery/memories/mapper/SystemImage.kt b/app/src/main/java/gallery/memories/mapper/SystemImage.kt index 4b243657..eef47d99 100644 --- a/app/src/main/java/gallery/memories/mapper/SystemImage.kt +++ b/app/src/main/java/gallery/memories/mapper/SystemImage.kt @@ -28,9 +28,9 @@ class SystemImage { var videoDuration = 0L val uri: Uri - get() { - return ContentUris.withAppendedId(mCollection, fileId) - } + get() { + return ContentUris.withAppendedId(mCollection, fileId) + } private var mCollection: Uri = IMAGE_URI @@ -137,72 +137,77 @@ class SystemImage { } } - val json get(): JSONObject { - val obj = JSONObject() - .put(Fields.Photo.FILEID, fileId) - .put(Fields.Photo.BASENAME, baseName) - .put(Fields.Photo.MIMETYPE, mimeType) - .put(Fields.Photo.HEIGHT, height) - .put(Fields.Photo.WIDTH, width) - .put(Fields.Photo.SIZE, size) - .put(Fields.Photo.ETAG, mtime.toString()) - .put(Fields.Photo.EPOCH, epoch) - .put(Fields.Photo.AUID, auid) + val json + get(): JSONObject { + val obj = JSONObject() + .put(Fields.Photo.FILEID, fileId) + .put(Fields.Photo.BASENAME, baseName) + .put(Fields.Photo.MIMETYPE, mimeType) + .put(Fields.Photo.HEIGHT, height) + .put(Fields.Photo.WIDTH, width) + .put(Fields.Photo.SIZE, size) + .put(Fields.Photo.ETAG, mtime.toString()) + .put(Fields.Photo.EPOCH, epoch) + .put(Fields.Photo.AUID, auid) - if (isVideo) { - obj.put(Fields.Photo.ISVIDEO, 1) - .put(Fields.Photo.VIDEO_DURATION, videoDuration / 1000) - } - - return obj - } - - val epoch get(): Long { - return dateTaken / 1000 - } - - val utcDate get(): Long { - // Get EXIF date using ExifInterface if image - if (!isVideo) { - try { - val exif = ExifInterface(dataPath) - val exifDate = exif.getAttribute(ExifInterface.TAG_DATETIME) - ?: throw IOException() - val sdf = SimpleDateFormat("yyyy:MM:dd HH:mm:ss") - sdf.timeZone = TimeZone.GMT_ZONE - sdf.parse(exifDate).let { - return it.time / 1000 - } - } catch (e: Exception) { - Log.e(TAG, "Failed to read EXIF data: " + e.message) + if (isVideo) { + obj.put(Fields.Photo.ISVIDEO, 1) + .put(Fields.Photo.VIDEO_DURATION, videoDuration / 1000) } + + return obj } - // No way to get the actual local date, so just assume current timezone - return (dateTaken + TimeZone.getDefault().getOffset(dateTaken).toLong()) / 1000 - } + val epoch + get(): Long { + return dateTaken / 1000 + } - val auid get(): Long { - val crc = java.util.zip.CRC32() + val utcDate + get(): Long { + // Get EXIF date using ExifInterface if image + if (!isVideo) { + try { + val exif = ExifInterface(dataPath) + val exifDate = exif.getAttribute(ExifInterface.TAG_DATETIME) + ?: throw IOException() + val sdf = SimpleDateFormat("yyyy:MM:dd HH:mm:ss") + sdf.timeZone = TimeZone.GMT_ZONE + sdf.parse(exifDate).let { + return it.time / 1000 + } + } catch (e: Exception) { + Log.e(TAG, "Failed to read EXIF data: " + e.message) + } + } - // pass date taken + size as decimal string - crc.update((epoch.toString() + size.toString()).toByteArray()) + // No way to get the actual local date, so just assume current timezone + return (dateTaken + TimeZone.getDefault().getOffset(dateTaken).toLong()) / 1000 + } - return crc.value - } + val auid + get(): Long { + val crc = java.util.zip.CRC32() - val photo get(): Photo { - val dateCache = utcDate - return Photo( - localId = fileId, - auid = auid, - mtime = mtime, - dateTaken = dateCache, - dayId = dateCache / 86400, - baseName = baseName, - bucketId = bucketId, - bucketName = bucketName, - flag = 0, - ) - } + // pass date taken + size as decimal string + crc.update((epoch.toString() + size.toString()).toByteArray()) + + return crc.value + } + + val photo + get(): Photo { + val dateCache = utcDate + return Photo( + localId = fileId, + auid = auid, + mtime = mtime, + dateTaken = dateCache, + dayId = dateCache / 86400, + baseName = baseName, + bucketId = bucketId, + bucketName = bucketName, + flag = 0, + ) + } } \ No newline at end of file diff --git a/app/src/main/java/gallery/memories/service/AccountService.kt b/app/src/main/java/gallery/memories/service/AccountService.kt index 2306fe4c..488571d7 100644 --- a/app/src/main/java/gallery/memories/service/AccountService.kt +++ b/app/src/main/java/gallery/memories/service/AccountService.kt @@ -17,7 +17,8 @@ import okhttp3.Response import org.json.JSONObject import java.net.SocketTimeoutException -@UnstableApi class AccountService(private val mCtx: MainActivity) { +@UnstableApi +class AccountService(private val mCtx: MainActivity) { companion object { val TAG = AccountService::class.java.simpleName } @@ -89,7 +90,8 @@ import java.net.SocketTimeoutException } val client = OkHttpClient() - val rbody = "token=$pollToken".toRequestBody("application/x-www-form-urlencoded".toMediaTypeOrNull()) + val rbody = + "token=$pollToken".toRequestBody("application/x-www-form-urlencoded".toMediaTypeOrNull()) var pollCount = 0 while (true) { diff --git a/app/src/main/java/gallery/memories/service/ConfigService.kt b/app/src/main/java/gallery/memories/service/ConfigService.kt index 5ce48dc8..670c2ac3 100644 --- a/app/src/main/java/gallery/memories/service/ConfigService.kt +++ b/app/src/main/java/gallery/memories/service/ConfigService.kt @@ -12,14 +12,18 @@ class ConfigService(private val mCtx: Context) { get() { if (mEnabledBuckets != null) return mEnabledBuckets!! mEnabledBuckets = mCtx.getSharedPreferences(mCtx.getString(R.string.preferences_key), 0) - .getStringSet(mCtx.getString(R.string.preferences_enabled_local_folders), null)?.toList() - ?: listOf() + .getStringSet(mCtx.getString(R.string.preferences_enabled_local_folders), null) + ?.toList() + ?: listOf() return mEnabledBuckets!! } set(value) { mEnabledBuckets = value mCtx.getSharedPreferences(mCtx.getString(R.string.preferences_key), 0).edit() - .putStringSet(mCtx.getString(R.string.preferences_enabled_local_folders), value.toSet()) + .putStringSet( + mCtx.getString(R.string.preferences_enabled_local_folders), + value.toSet() + ) .apply() } } \ No newline at end of file diff --git a/app/src/main/java/gallery/memories/service/DownloadService.kt b/app/src/main/java/gallery/memories/service/DownloadService.kt index 95e568c3..54cfbb09 100644 --- a/app/src/main/java/gallery/memories/service/DownloadService.kt +++ b/app/src/main/java/gallery/memories/service/DownloadService.kt @@ -44,7 +44,10 @@ class DownloadService(private val mActivity: AppCompatActivity) { request.addRequestHeader("cookie", cookies) if (filename != "") { // Save the file to external storage - request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "memories/$filename") + request.setDestinationInExternalPublicDir( + Environment.DIRECTORY_DOWNLOADS, + "memories/$filename" + ) } // Start the download @@ -92,7 +95,8 @@ class DownloadService(private val mActivity: AppCompatActivity) { } private fun getDownloadedFileURI(downloadId: Long): String? { - val downloadManager = mActivity.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager + val downloadManager = + mActivity.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager val query = DownloadManager.Query() query.setFilterById(downloadId) val cursor = downloadManager.query(query) diff --git a/app/src/main/java/gallery/memories/service/ImageService.kt b/app/src/main/java/gallery/memories/service/ImageService.kt index 4bc21a9b..a726801b 100644 --- a/app/src/main/java/gallery/memories/service/ImageService.kt +++ b/app/src/main/java/gallery/memories/service/ImageService.kt @@ -14,7 +14,10 @@ class ImageService(private val mCtx: Context) { val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { mCtx.contentResolver.loadThumbnail( - ContentUris.withAppendedId(MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL), id), + ContentUris.withAppendedId( + MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL), + id + ), android.util.Size(2048, 2048), null ) @@ -22,10 +25,10 @@ class ImageService(private val mCtx: Context) { MediaStore.Images.Thumbnails.getThumbnail( mCtx.contentResolver, id, MediaStore.Images.Thumbnails.FULL_SCREEN_KIND, null ) - ?: MediaStore.Video.Thumbnails.getThumbnail( - mCtx.contentResolver, id, MediaStore.Video.Thumbnails.FULL_SCREEN_KIND, null - ) - ?: throw Exception("Thumbnail not found") + ?: MediaStore.Video.Thumbnails.getThumbnail( + mCtx.contentResolver, id, MediaStore.Video.Thumbnails.FULL_SCREEN_KIND, null + ) + ?: throw Exception("Thumbnail not found") } val stream = ByteArrayOutputStream() @@ -42,7 +45,7 @@ class ImageService(private val mCtx: Context) { ImageDecoder.decodeBitmap(ImageDecoder.createSource(mCtx.contentResolver, uri)) } else { MediaStore.Images.Media.getBitmap(mCtx.contentResolver, uri) - ?: throw Exception("Image not found") + ?: throw Exception("Image not found") } val stream = ByteArrayOutputStream() bitmap.compress(Bitmap.CompressFormat.JPEG, 90, stream) diff --git a/app/src/main/java/gallery/memories/service/TimelineQuery.kt b/app/src/main/java/gallery/memories/service/TimelineQuery.kt index 8ba46f35..84bee601 100644 --- a/app/src/main/java/gallery/memories/service/TimelineQuery.kt +++ b/app/src/main/java/gallery/memories/service/TimelineQuery.kt @@ -26,7 +26,8 @@ import java.io.IOException import java.time.Instant import java.util.concurrent.CountDownLatch -@UnstableApi class TimelineQuery(private val mCtx: MainActivity) { +@UnstableApi +class TimelineQuery(private val mCtx: MainActivity) { private val TAG = TimelineQuery::class.java.simpleName private val mConfigService = ConfigService(mCtx) @@ -46,11 +47,12 @@ import java.util.concurrent.CountDownLatch init { // Register intent launcher for callback - deleteIntentLauncher = mCtx.registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result: ActivityResult? -> - synchronized(this) { - deleteCallback?.let { it(result) } + deleteIntentLauncher = + mCtx.registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result: ActivityResult? -> + synchronized(this) { + deleteCallback?.let { it(result) } + } } - } } fun initialize() { @@ -192,7 +194,9 @@ import java.util.concurrent.CountDownLatch // Delete file with media store if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val intent = MediaStore.createTrashRequest(mCtx.contentResolver, uris, true) - deleteIntentLauncher.launch(IntentSenderRequest.Builder(intent.intentSender).build()) + deleteIntentLauncher.launch( + IntentSenderRequest.Builder(intent.intentSender).build() + ) // Wait for response val latch = CountDownLatch(1) @@ -240,7 +244,7 @@ import java.util.concurrent.CountDownLatch // Iterate all images and videos from system store val files = SystemImage.query(mCtx, SystemImage.IMAGE_URI, selection, selectionArgs, null) + - SystemImage.query(mCtx, SystemImage.VIDEO_URI, selection, selectionArgs, null) + SystemImage.query(mCtx, SystemImage.VIDEO_URI, selection, selectionArgs, null) files.forEach { insertItemDb(it) } // Store last sync time