Reformat code
parent
3fb209a994
commit
4e89b101b2
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
)
|
|
@ -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
|
||||
)
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue