Watch local changes
parent
76f3a270c3
commit
79aecab377
|
@ -12,6 +12,7 @@ import android.view.WindowInsetsController
|
|||
import android.webkit.*
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.common.util.Util
|
||||
|
@ -35,6 +36,8 @@ import gallery.memories.databinding.ActivityMainBinding
|
|||
private var mediaItemIndex = 0
|
||||
private var playbackPosition = 0L
|
||||
|
||||
private var mNeedRefresh = false
|
||||
|
||||
private val memoriesRegex = Regex("/apps/memories/.*$")
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
@ -72,6 +75,9 @@ import gallery.memories.databinding.ActivityMainBinding
|
|||
if (playerUri != null && (Util.SDK_INT <= 23 || player == null)) {
|
||||
initializePlayer(playerUri!!, playerUid!!)
|
||||
}
|
||||
if (mNeedRefresh) {
|
||||
refreshTimeline(true)
|
||||
}
|
||||
}
|
||||
|
||||
public override fun onPause() {
|
||||
|
@ -181,15 +187,28 @@ import gallery.memories.databinding.ActivityMainBinding
|
|||
}
|
||||
|
||||
fun ensureStoragePermissions() {
|
||||
val requestPermissionLauncher =
|
||||
registerForActivityResult(
|
||||
ActivityResultContracts.RequestPermission()
|
||||
) { isGranted: Boolean ->
|
||||
if (isGranted && !hasMediaPermission()) {
|
||||
nativex.query.syncFullDb()
|
||||
}
|
||||
setHasMediaPermission(isGranted)
|
||||
val requestPermissionLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
|
||||
if (isGranted) {
|
||||
val needFullSync = !hasMediaPermission()
|
||||
|
||||
// Run DB operations in separate thread
|
||||
Thread {
|
||||
// Full sync if this is the first time permission was granted
|
||||
if (needFullSync) {
|
||||
nativex.query.syncFullDb()
|
||||
}
|
||||
|
||||
// Run delta sync and register hooks
|
||||
nativex.query.initialize()
|
||||
}.start()
|
||||
}
|
||||
|
||||
// Persist that we have it now
|
||||
setHasMediaPermission(isGranted)
|
||||
}
|
||||
|
||||
// Request media read permission
|
||||
requestPermissionLauncher.launch(android.Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
}
|
||||
|
||||
|
@ -301,4 +320,19 @@ import gallery.memories.databinding.ActivityMainBinding
|
|||
.putBoolean(getString(R.string.preferences_has_media_permission), v)
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun refreshTimeline(force: Boolean = false) {
|
||||
runOnUiThread {
|
||||
// Check webview is loaded
|
||||
if (binding?.webview?.url == null) return@runOnUiThread
|
||||
|
||||
// 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)
|
||||
} else {
|
||||
mNeedRefresh = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,11 +42,6 @@ import java.net.URLDecoder
|
|||
|
||||
init {
|
||||
dlService = DownloadService(mCtx)
|
||||
|
||||
// Synchronize the database if possible
|
||||
if (mCtx.hasMediaPermission()) {
|
||||
query.syncDeltaDb()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -55,6 +50,7 @@ import java.net.URLDecoder
|
|||
|
||||
fun destroy() {
|
||||
dlService = null
|
||||
query.destroy()
|
||||
}
|
||||
|
||||
fun handleRequest(request: WebResourceRequest): WebResourceResponse {
|
||||
|
|
|
@ -2,9 +2,11 @@ package gallery.memories.service
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.database.ContentObserver
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.icu.text.SimpleDateFormat
|
||||
import android.icu.util.TimeZone
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.MediaStore
|
||||
import android.text.TextUtils
|
||||
|
@ -13,9 +15,10 @@ import androidx.activity.result.ActivityResult
|
|||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.IntentSenderRequest
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.collection.ArraySet
|
||||
import androidx.exifinterface.media.ExifInterface
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import gallery.memories.MainActivity
|
||||
import gallery.memories.R
|
||||
import gallery.memories.mapper.Fields
|
||||
import gallery.memories.mapper.SystemImage
|
||||
|
@ -26,7 +29,7 @@ import java.io.IOException
|
|||
import java.time.Instant
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
class TimelineQuery(private val mCtx: AppCompatActivity) {
|
||||
@UnstableApi class TimelineQuery(private val mCtx: MainActivity) {
|
||||
private val mDb: SQLiteDatabase = DbService(mCtx).writableDatabase
|
||||
private val TAG = "TimelineQuery"
|
||||
|
||||
|
@ -38,6 +41,11 @@ class TimelineQuery(private val mCtx: AppCompatActivity) {
|
|||
// Caches
|
||||
var mEnabledBuckets: Set<String>? = null
|
||||
|
||||
// Observers
|
||||
var imageObserver: ContentObserver? = null
|
||||
var videoObserver: ContentObserver? = null
|
||||
var refreshPending: Boolean = false
|
||||
|
||||
init {
|
||||
// Register intent launcher for callback
|
||||
deleteIntentLauncher = mCtx.registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result: ActivityResult? ->
|
||||
|
@ -47,6 +55,57 @@ class TimelineQuery(private val mCtx: AppCompatActivity) {
|
|||
}
|
||||
}
|
||||
|
||||
fun initialize() {
|
||||
if (syncDeltaDb() > 0) {
|
||||
mCtx.refreshTimeline()
|
||||
}
|
||||
registerHooks()
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
if (imageObserver != null) {
|
||||
mCtx.contentResolver.unregisterContentObserver(imageObserver!!)
|
||||
}
|
||||
if (videoObserver != null) {
|
||||
mCtx.contentResolver.unregisterContentObserver(videoObserver!!)
|
||||
}
|
||||
}
|
||||
|
||||
fun registerHooks() {
|
||||
imageObserver = registerContentObserver(SystemImage.IMAGE_URI)
|
||||
videoObserver = registerContentObserver(SystemImage.VIDEO_URI)
|
||||
}
|
||||
|
||||
private fun registerContentObserver(uri: Uri): ContentObserver {
|
||||
val observer = @UnstableApi object : ContentObserver(null) {
|
||||
override fun onChange(selfChange: Boolean) {
|
||||
super.onChange(selfChange)
|
||||
|
||||
// Debounce refreshes
|
||||
synchronized(this@TimelineQuery) {
|
||||
if (refreshPending) return
|
||||
refreshPending = true
|
||||
}
|
||||
|
||||
// Refresh after 750ms
|
||||
Thread {
|
||||
Thread.sleep(750)
|
||||
synchronized(this@TimelineQuery) {
|
||||
refreshPending = false
|
||||
}
|
||||
|
||||
// Check if anything to update
|
||||
if (syncDeltaDb() == 0 || mCtx.isDestroyed || mCtx.isFinishing) return@Thread
|
||||
|
||||
mCtx.refreshTimeline()
|
||||
}.start()
|
||||
}
|
||||
}
|
||||
|
||||
mCtx.contentResolver.registerContentObserver(uri, true, observer)
|
||||
return observer
|
||||
}
|
||||
|
||||
@Throws(JSONException::class)
|
||||
fun getByDayId(dayId: Long): JSONArray {
|
||||
// Filter for enabled buckets
|
||||
|
@ -230,7 +289,7 @@ class TimelineQuery(private val mCtx: AppCompatActivity) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun syncDb(startTime: Long) {
|
||||
private fun syncDb(startTime: Long): Int {
|
||||
// Date modified is in seconds, not millis
|
||||
val syncTime = Instant.now().toEpochMilli() / 1000;
|
||||
|
||||
|
@ -254,13 +313,16 @@ class TimelineQuery(private val mCtx: AppCompatActivity) {
|
|||
mCtx.getSharedPreferences(mCtx.getString(R.string.preferences_key), 0).edit()
|
||||
.putLong(mCtx.getString(R.string.preferences_last_sync_time), syncTime)
|
||||
.apply()
|
||||
|
||||
// Number of updated files
|
||||
return files.size
|
||||
}
|
||||
|
||||
fun syncDeltaDb() {
|
||||
fun syncDeltaDb(): Int {
|
||||
// Get last sync time
|
||||
val syncTime = mCtx.getSharedPreferences(mCtx.getString(R.string.preferences_key), 0)
|
||||
.getLong(mCtx.getString(R.string.preferences_last_sync_time), 0L)
|
||||
syncDb(syncTime)
|
||||
return syncDb(syncTime)
|
||||
}
|
||||
|
||||
fun syncFullDb() {
|
||||
|
|
Loading…
Reference in New Issue