[mob][photos] refactor home widget (#5389)

## Description

Refactor home widget and separate memory home widget services.

## Tests
This commit is contained in:
Prateek Sunal
2025-03-20 20:06:38 +05:30
committed by GitHub
51 changed files with 653 additions and 471 deletions

View File

@@ -143,12 +143,12 @@
android:value="https://2235e5c99219488ea93da34b9ac1cb68@sentry.ente.io/4" />
<meta-data android:name="firebase_analytics_collection_deactivated"
android:value="true" />
<receiver android:name="SlideshowWidgetProvider" android:label="Memories" android:exported="true">
<receiver android:name="EnteMemoryWidgetProvider" android:label="Memories" android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/slideshow_widget" />
android:resource="@xml/memory_widget" />
</receiver>
</application>

View File

@@ -0,0 +1,198 @@
package io.ente.photos
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.SharedPreferences
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.net.Uri
import android.util.Log
import android.view.View
import android.widget.RemoteViews
import androidx.core.content.ContextCompat
import es.antonborri.home_widget.HomeWidgetLaunchIntent
import es.antonborri.home_widget.HomeWidgetProvider
import java.io.File
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
@Serializable
data class MemoryFileData(val title: String?, val subText: String?, val generatedId: Int?)
class EnteMemoryWidgetProvider : HomeWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray,
widgetData: SharedPreferences
) {
appWidgetIds.forEach { widgetId ->
val views =
RemoteViews(context.packageName, R.layout.memory_widget_layout)
.apply {
val totalMemories =
widgetData.getInt("totalMemories", 0)
var randomNumber = -1
var imagePath: String? = null
if (totalMemories > 0) {
randomNumber =
(0 until totalMemories!!).random()
imagePath =
widgetData.getString(
"memory_widget_" +
randomNumber,
null
)
}
var imageExists: Boolean = false
if (imagePath != null) {
val imageFile = File(imagePath)
imageExists = imageFile.exists()
}
if (imageExists) {
val data =
widgetData.getString(
"memory_widget_${randomNumber}_data",
null
)
val decoded: MemoryFileData? =
data?.let {
Json.decodeFromString<
MemoryFileData>(it)
}
val title = decoded?.title
val subText = decoded?.subText
val generatedId = decoded?.generatedId
val deepLinkUri =
Uri.parse(
"memorywidget://message?generatedId=${generatedId}&homeWidget"
)
val pendingIntent =
HomeWidgetLaunchIntent.getActivity(
context,
MainActivity::class.java,
deepLinkUri
)
setOnClickPendingIntent(
R.id.widget_container,
pendingIntent
)
Log.d(
"EnteMemoryWidgetProvider",
"Image exists: $imagePath"
)
setViewVisibility(
R.id.widget_img,
View.VISIBLE
)
setViewVisibility(
R.id.widget_placeholder_container,
View.VISIBLE
)
setViewVisibility(
R.id.widget_subtitle,
View.VISIBLE
)
setViewVisibility(
R.id.widget_title,
View.VISIBLE
)
setViewVisibility(
R.id.widget_overlay,
View.VISIBLE
)
setViewVisibility(
R.id.widget_placeholder,
View.GONE
)
setViewVisibility(
R.id.widget_placeholder_text,
View.GONE
)
setViewVisibility(
R.id.widget_placeholder_container,
View.GONE
)
val bitmap: Bitmap =
BitmapFactory.decodeFile(imagePath)
setImageViewBitmap(R.id.widget_img, bitmap)
setTextViewText(R.id.widget_title, title)
setTextViewText(
R.id.widget_subtitle,
subText
)
} else {
// Open App on Widget Click
val pendingIntent =
HomeWidgetLaunchIntent.getActivity(
context,
MainActivity::class.java
)
setOnClickPendingIntent(
R.id.widget_container,
pendingIntent
)
Log.d(
"EnteMemoryWidgetProvider",
"Image doesn't exists"
)
setViewVisibility(
R.id.widget_img,
View.GONE
)
setViewVisibility(
R.id.widget_placeholder_container,
View.GONE
)
setViewVisibility(
R.id.widget_subtitle,
View.GONE
)
setViewVisibility(
R.id.widget_title,
View.GONE
)
setViewVisibility(
R.id.widget_overlay,
View.GONE
)
setViewVisibility(
R.id.widget_placeholder,
View.VISIBLE
)
setViewVisibility(
R.id.widget_placeholder_text,
View.VISIBLE
)
setViewVisibility(
R.id.widget_placeholder_container,
View.VISIBLE
)
val drawable =
ContextCompat.getDrawable(
context,
R.drawable
.ic_home_widget_default
)
val bitmap =
(drawable as BitmapDrawable).bitmap
setImageViewBitmap(
R.id.widget_placeholder,
bitmap
)
}
}
appWidgetManager.updateAppWidget(widgetId, views)
}
}
}

View File

@@ -1,114 +0,0 @@
package io.ente.photos
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.SharedPreferences
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.net.Uri
import android.util.Log
import android.view.View
import android.widget.RemoteViews
import androidx.core.content.ContextCompat
import es.antonborri.home_widget.HomeWidgetLaunchIntent
import es.antonborri.home_widget.HomeWidgetProvider
import java.io.File
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
@Serializable
data class MemoryFileData(val title: String?, val subText: String?, val generatedId: Int?)
class SlideshowWidgetProvider : HomeWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray,
widgetData: SharedPreferences
) {
appWidgetIds.forEach { widgetId ->
val views =
RemoteViews(context.packageName, R.layout.slideshow_layout).apply {
val totalSet = widgetData.getInt("totalSet", 0)
var randomNumber = -1
var imagePath: String? = null
if (totalSet > 0) {
randomNumber = (0 until totalSet!!).random()
imagePath = widgetData.getString("slideshow_" + randomNumber, null)
}
var imageExists: Boolean = false
if (imagePath != null) {
val imageFile = File(imagePath)
imageExists = imageFile.exists()
}
if (imageExists) {
val data = widgetData.getString("slideshow_${randomNumber}_data", null)
val decoded: MemoryFileData? =
data?.let { Json.decodeFromString<MemoryFileData>(it) }
val title = decoded?.title
val subText = decoded?.subText
val generatedId = decoded?.generatedId
val deepLinkUri =
Uri.parse(
"memoryWidget://message?generatedId=${generatedId}&homeWidget"
)
val pendingIntent =
HomeWidgetLaunchIntent.getActivity(
context,
MainActivity::class.java,
deepLinkUri
)
setOnClickPendingIntent(R.id.widget_container, pendingIntent)
Log.d("SlideshowWidgetProvider", "Image exists: $imagePath")
setViewVisibility(R.id.widget_img, View.VISIBLE)
setViewVisibility(R.id.widget_placeholder_container, View.VISIBLE)
setViewVisibility(R.id.widget_subtitle, View.VISIBLE)
setViewVisibility(R.id.widget_title, View.VISIBLE)
setViewVisibility(R.id.widget_overlay, View.VISIBLE)
setViewVisibility(R.id.widget_placeholder, View.GONE)
setViewVisibility(R.id.widget_placeholder_text, View.GONE)
setViewVisibility(R.id.widget_placeholder_container, View.GONE)
val bitmap: Bitmap = BitmapFactory.decodeFile(imagePath)
setImageViewBitmap(R.id.widget_img, bitmap)
setTextViewText(R.id.widget_title, title)
setTextViewText(R.id.widget_subtitle, subText)
} else {
// Open App on Widget Click
val pendingIntent =
HomeWidgetLaunchIntent.getActivity(
context,
MainActivity::class.java
)
setOnClickPendingIntent(R.id.widget_container, pendingIntent)
Log.d("SlideshowWidgetProvider", "Image doesn't exists")
setViewVisibility(R.id.widget_img, View.GONE)
setViewVisibility(R.id.widget_placeholder_container, View.GONE)
setViewVisibility(R.id.widget_subtitle, View.GONE)
setViewVisibility(R.id.widget_title, View.GONE)
setViewVisibility(R.id.widget_overlay, View.GONE)
setViewVisibility(R.id.widget_placeholder, View.VISIBLE)
setViewVisibility(R.id.widget_placeholder_text, View.VISIBLE)
setViewVisibility(R.id.widget_placeholder_container, View.VISIBLE)
val drawable =
ContextCompat.getDrawable(
context,
R.drawable.ic_home_widget_default
)
val bitmap = (drawable as BitmapDrawable).bitmap
setImageViewBitmap(R.id.widget_placeholder, bitmap)
}
}
appWidgetManager.updateAppWidget(widgetId, views)
}
}
}

View File

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 125 KiB

View File

@@ -3,8 +3,8 @@
android:minWidth="100dp"
android:minHeight="100dp"
android:updatePeriodMillis="900000"
android:initialLayout="@layout/slideshow_layout"
android:previewImage="@drawable/slideshow_preview"
android:initialLayout="@layout/memory_widget_layout"
android:previewImage="@drawable/memory_widget_preview"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen">
</appwidget-provider>

View File

@@ -1,17 +1,17 @@
//
// SlideshowWidgetBundle.swift
// SlideshowWidget
// EnteMemoryWidgetBundle.swift
// EnteMemoryWidget
//
// Created by Prateek Sunal on 3/7/25.
// Copyright © 2025 The Chromium Authors. All rights reserved.
//
import WidgetKit
import SwiftUI
import WidgetKit
@main
struct SlideshowWidgetBundle: WidgetBundle {
struct EnteMemoryWidgetBundle: WidgetBundle {
var body: some Widget {
SlideshowWidget()
EnteMemoryWidget()
}
}

View File

@@ -4,7 +4,7 @@
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.io.ente.frame.SlideshowWidget</string>
<string>group.io.ente.frame.EnteMemoryWidget</string>
</array>
</dict>
</plist>

View File

@@ -4,7 +4,7 @@
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.io.ente.frame.SlideshowWidget</string>
<string>group.io.ente.frame.EnteMemoryWidget</string>
</array>
</dict>
</plist>

View File

@@ -447,83 +447,83 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/wakelock_plus/ios"
SPEC CHECKSUMS:
background_fetch: 39f11371c0dce04b001c4bfd5e782bcccb0a85e2
battery_info: b6c551049266af31556b93c9d9b9452cfec0219f
connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d
cupertino_http: 947a233f40cfea55167a49f2facc18434ea117ba
dart_ui_isolate: d5bcda83ca4b04f129d70eb90110b7a567aece14
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
background_fetch: 94b36ee293e82972852dba8ede1fbcd3bd3d9d57
battery_info: 83f3aae7be2fccefab1d2bf06b8aa96f11c8bcdd
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
cupertino_http: 94ac07f5ff090b8effa6c5e2c47871d48ab7c86c
dart_ui_isolate: 46f6714abe6891313267153ef6f9748d8ecfcab1
device_info_plus: 335f3ce08d2e174b9fdc3db3db0f4e3b1f66bd89
ffmpeg-kit-ios-full-gpl: 80adc341962e55ef709e36baa8ed9a70cf4ea62b
ffmpeg_kit_flutter_full_gpl: 8d15c14c0c3aba616fac04fe44b3d27d02e3c330
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
ffmpeg_kit_flutter_full_gpl: ce18b888487c05c46ed252cd2e7956812f2e3bd1
file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6
Firebase: d80354ed7f6df5f9aca55e9eb47cc4b634735eaf
firebase_core: 6e223dfa350b2edceb729cea505eaaef59330682
firebase_messaging: 07fde77ae28c08616a1d4d870450efc2b38cf40d
firebase_core: 6cbed78b4f298ed103a9fd034e6dbc846320480f
firebase_messaging: 5e0adf2eb18b0ee59aa0c109314c091a0497ecac
FirebaseCore: 99fe0c4b44a39f37d99e6404e02009d2db5d718d
FirebaseCoreInternal: df24ce5af28864660ecbd13596fc8dd3a8c34629
FirebaseInstallations: 6c963bd2a86aca0481eef4f48f5a4df783ae5917
FirebaseMessaging: 487b634ccdf6f7b7ff180fdcb2a9935490f764e8
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_app_icon_changer: e633ce623d36db4dd63c01e4b63fc8a5d44ed48d
flutter_email_sender: e03bdda7637bcd3539bfe718fddd980e9508efaa
flutter_image_compress_common: ec1d45c362c9d30a3f6a0426c297f47c52007e3e
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
flutter_native_splash: f71420956eb811e6d310720fee915f1d42852e7a
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
flutter_sodium: a00383520fc689c688b66fd3092984174712493e
fluttertoast: 21eecd6935e7064cc1fcb733a4c5a428f3f24f0f
flutter_app_icon_changer: 7b04b45ddc58854f06df562f1e013c54ec40da6e
flutter_email_sender: aa1e9772696691d02cd91fea829856c11efb8e58
flutter_image_compress_common: 1697a328fd72bfb335507c6bca1a65fa5ad87df1
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
flutter_local_notifications: ad39620c743ea4c15127860f4b5641649a988100
flutter_native_splash: 6cad9122ea0fad137d23137dd14b937f3e90b145
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
flutter_sodium: 7e4621538491834eba53bd524547854bcbbd6987
fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57
image_editor_common: d6f6644ae4a6de80481e89fe6d0a8c49e30b4b43
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
in_app_purchase_storekit: a1ce04056e23eecc666b086040239da7619cd783
integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
launcher_icon_switcher: 8e0ad2131a20c51c1dd939896ee32e70cd845b37
home_widget: f169fc41fd807b4d46ab6615dc44d62adbf9f64f
image_editor_common: 3de87e7c4804f4ae24c8f8a998362b98c105cac1
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
in_app_purchase_storekit: d1a48cb0f8b29dbf5f85f782f5dd79b21b90a5e6
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
launcher_icon_switcher: 84c218d233505aa7d8655d8fa61a3ba802c022da
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3
local_auth_ios: 5046a18c018dd973247a0564496c8898dbb5adf9
local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391
local_auth_ios: f7a1841beef3151d140a967c2e46f30637cdf451
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
maps_launcher: 2e5b6a2d664ec6c27f82ffa81b74228d770ab203
media_extension: 6618f07abd762cdbfaadf1b0c56a287e820f0c84
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
motion_sensors: 03f55b7c637a7e365a0b5f9697a449f9059d5d91
motionphoto: 8b65ce50c7d7ff3c767534fc3768b2eed9ac24e4
move_to_background: cd3091014529ec7829e342ad2d75c0a11f4378a5
maps_launcher: edf829809ba9e894d70e569bab11c16352dedb45
media_extension: 671e2567880d96c95c65c9a82ccceed8f2e309fd
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
motion_sensors: 741e702c17467b9569a92165dda8d4d88c6167f1
motionphoto: 23e2aeb5c6380112f69468d71f970fa7438e5ed1
move_to_background: 7e3467dd2a1d1013e98c9c1cb93fd53cd7ef9d84
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
native_video_player: 5d36066807b680e181473e6890dde643ac85380d
objective_c: 77e887b5ba1827970907e10e832eec1683f3431d
onnxruntime: e7c2ae44385191eaad5ae64c935a72debaddc997
native_video_player: e363dd14f6a498ad8a8f7e6486a0db046ad19f13
objective_c: 89e720c30d716b036faf9c9684022048eee1eee2
onnxruntime: f9b296392c96c42882be020a59dbeac6310d81b2
onnxruntime-c: a909204639a1f035f575127ac406f781ac797c9c
onnxruntime-objc: b6fab0f1787aa6f7190c2013f03037df4718bd8b
open_mail_app: 70273c53f768beefdafbe310c3d9086e4da3cb02
open_mail_app: 7314a609e88eed22d53671279e189af7a0ab0f11
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
photo_manager: ff695c7a1dd5bc379974953a2b5c0a293f7c4c8a
privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
photo_manager: d2fbcc0f2d82458700ee6256a15018210a81d413
privacy_screen: 3159a541f5d3a31bea916cfd4e58f9dc722b3fd4
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
receive_sharing_intent: 79c848f5b045674ad60b9fea3bafea59962ad2c1
receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00
SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
Sentry: 0f9bc9adfc0b960e7f3bb5ec67e9a3d8193f3bdb
sentry_flutter: 64a43fb39ab4c7f67d8a4cad52b49e22439e58b7
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
sentry_flutter: f4a0466dc8855998ffd59378ec33507c7dc32d7b
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
sqlite3_flutter_libs: 069c435986dd4b63461aecd68f4b30be4a9e9daa
system_info_plus: 5393c8da281d899950d751713575fbf91c7709aa
ua_client_hints: aeabd123262c087f0ce151ef96fa3ab77bfc8b38
uni_links: 103d3319e3383ed8bce559b96b1e219fbf02ba96
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3
video_thumbnail: 94ba6705afbaa120b77287080424930f23ea0c40
volume_controller: 2e3de73d6e7e81a0067310d17fb70f2f86d71ac7
wakelock_plus: 373cfe59b235a6dd5837d0fb88791d2f13a90d56
sqlite3_flutter_libs: 3c323550ef3b928bc0aa9513c841e45a7d242832
system_info_plus: 555ce7047fbbf29154726db942ae785c29211740
ua_client_hints: 92fe0d139619b73ec9fcb46cc7e079a26178f586
uni_links: f191d616c4db8750f74c72c988e79a83dd297fac
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
video_thumbnail: 584ccfa55d8fd2f3d5507218b0a18d84c839c620
volume_controller: 3657a1f65bedb98fa41ff7dc5793537919f31b12
wakelock_plus: 04623e3f525556020ebd4034310f20fe7fda8b49
PODFILE CHECKSUM: 20e086e6008977d43a3d40260f3f9bffcac748dd

View File

@@ -18,7 +18,7 @@
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
CEE6BE702D7AE7FD00E4048B /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6DACD83C2B755B0600BA9516 /* WidgetKit.framework */; };
CEE6BE712D7AE7FD00E4048B /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6DACD83E2B755B0600BA9516 /* SwiftUI.framework */; };
CEE6BE7C2D7AE7FE00E4048B /* SlideshowWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = CEE6BE6F2D7AE7FD00E4048B /* SlideshowWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
CEE6BE7C2D7AE7FE00E4048B /* EnteMemoryWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = CEE6BE6F2D7AE7FD00E4048B /* EnteMemoryWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
DA6BE5E826B3BC8600656280 /* (null) in Resources */ = {isa = PBXBuildFile; };
/* End PBXBuildFile section */
@@ -28,7 +28,7 @@
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = CEE6BE6E2D7AE7FD00E4048B;
remoteInfo = SlideshowWidgetExtension;
remoteInfo = EnteMemoryWidgetExtension;
};
/* End PBXContainerItemProxy section */
@@ -39,7 +39,7 @@
dstPath = "";
dstSubfolderSpec = 13;
files = (
CEE6BE7C2D7AE7FE00E4048B /* SlideshowWidgetExtension.appex in Embed Foundation Extensions */,
CEE6BE7C2D7AE7FE00E4048B /* EnteMemoryWidgetExtension.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
@@ -78,9 +78,9 @@
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
A78E51A260432466D4C456A9 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
BB097BB5EB0EEB41344338D2 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
CE93A9062D808893005CD942 /* SlideshowWidgetExtensionDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SlideshowWidgetExtensionDebug.entitlements; sourceTree = "<group>"; };
CEE6BE6F2D7AE7FD00E4048B /* SlideshowWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SlideshowWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
CEE6BE822D7AE8C700E4048B /* SlideshowWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SlideshowWidgetExtension.entitlements; sourceTree = "<group>"; };
CE93A9062D808893005CD942 /* EnteMemoryWidgetExtensionDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = EnteMemoryWidgetExtensionDebug.entitlements; sourceTree = "<group>"; };
CEE6BE6F2D7AE7FD00E4048B /* EnteMemoryWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = EnteMemoryWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
CEE6BE822D7AE8C700E4048B /* EnteMemoryWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = EnteMemoryWidgetExtension.entitlements; sourceTree = "<group>"; };
DA8D6672273BBB59007651D4 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
F82DAEEB9A7D9FD00E0FFA1E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
@@ -91,12 +91,12 @@
membershipExceptions = (
Info.plist,
);
target = CEE6BE6E2D7AE7FD00E4048B /* SlideshowWidgetExtension */;
target = CEE6BE6E2D7AE7FD00E4048B /* EnteMemoryWidgetExtension */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
CEE6BE722D7AE7FD00E4048B /* SlideshowWidget */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (CEE6BE802D7AE7FE00E4048B /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = SlideshowWidget; sourceTree = "<group>"; };
CEE6BE722D7AE7FD00E4048B /* EnteMemoryWidget */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (CEE6BE802D7AE7FE00E4048B /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = EnteMemoryWidget; sourceTree = "<group>"; };
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
@@ -135,12 +135,12 @@
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
CE93A9062D808893005CD942 /* SlideshowWidgetExtensionDebug.entitlements */,
CEE6BE822D7AE8C700E4048B /* SlideshowWidgetExtension.entitlements */,
CE93A9062D808893005CD942 /* EnteMemoryWidgetExtensionDebug.entitlements */,
CEE6BE822D7AE8C700E4048B /* EnteMemoryWidgetExtension.entitlements */,
2772189F270F596900FFE3CC /* GoogleService-Info.plist */,
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
CEE6BE722D7AE7FD00E4048B /* SlideshowWidget */,
CEE6BE722D7AE7FD00E4048B /* EnteMemoryWidget */,
97C146EF1CF9000F007C117D /* Products */,
AC6CA265BB505D982CB00391 /* Pods */,
C6A22658E77FF012720BEDDA /* Frameworks */,
@@ -151,7 +151,7 @@
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
CEE6BE6F2D7AE7FD00E4048B /* SlideshowWidgetExtension.appex */,
CEE6BE6F2D7AE7FD00E4048B /* EnteMemoryWidgetExtension.appex */,
);
name = Products;
sourceTree = "<group>";
@@ -230,9 +230,9 @@
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
CEE6BE6E2D7AE7FD00E4048B /* SlideshowWidgetExtension */ = {
CEE6BE6E2D7AE7FD00E4048B /* EnteMemoryWidgetExtension */ = {
isa = PBXNativeTarget;
buildConfigurationList = CEE6BE812D7AE7FE00E4048B /* Build configuration list for PBXNativeTarget "SlideshowWidgetExtension" */;
buildConfigurationList = CEE6BE812D7AE7FE00E4048B /* Build configuration list for PBXNativeTarget "EnteMemoryWidgetExtension" */;
buildPhases = (
CEE6BE6B2D7AE7FD00E4048B /* Sources */,
CEE6BE6C2D7AE7FD00E4048B /* Frameworks */,
@@ -243,13 +243,13 @@
dependencies = (
);
fileSystemSynchronizedGroups = (
CEE6BE722D7AE7FD00E4048B /* SlideshowWidget */,
CEE6BE722D7AE7FD00E4048B /* EnteMemoryWidget */,
);
name = SlideshowWidgetExtension;
name = EnteMemoryWidgetExtension;
packageProductDependencies = (
);
productName = SlideshowWidgetExtension;
productReference = CEE6BE6F2D7AE7FD00E4048B /* SlideshowWidgetExtension.appex */;
productName = EnteMemoryWidgetExtension;
productReference = CEE6BE6F2D7AE7FD00E4048B /* EnteMemoryWidgetExtension.appex */;
productType = "com.apple.product-type.app-extension";
};
/* End PBXNativeTarget section */
@@ -286,7 +286,7 @@
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
CEE6BE6E2D7AE7FD00E4048B /* SlideshowWidgetExtension */,
CEE6BE6E2D7AE7FD00E4048B /* EnteMemoryWidgetExtension */,
);
};
/* End PBXProject section */
@@ -614,7 +614,7 @@
/* Begin PBXTargetDependency section */
CEE6BE7B2D7AE7FE00E4048B /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = CEE6BE6E2D7AE7FD00E4048B /* SlideshowWidgetExtension */;
target = CEE6BE6E2D7AE7FD00E4048B /* EnteMemoryWidgetExtension */;
targetProxy = CEE6BE7A2D7AE7FE00E4048B /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
@@ -955,7 +955,7 @@
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = SlideshowWidgetExtensionDebug.entitlements;
CODE_SIGN_ENTITLEMENTS = EnteMemoryWidgetExtensionDebug.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
@@ -963,8 +963,8 @@
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SlideshowWidget/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SlideshowWidget;
INFOPLIST_FILE = EnteMemoryWidget/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = EnteMemoryWidget;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 The Chromium Authors. All rights reserved.";
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
LD_RUNPATH_SEARCH_PATHS = (
@@ -976,7 +976,7 @@
MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.ente.frame.debug.SlideshowWidget;
PRODUCT_BUNDLE_IDENTIFIER = io.ente.frame.debug.EnteMemoryWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
@@ -997,7 +997,7 @@
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = SlideshowWidgetExtension.entitlements;
CODE_SIGN_ENTITLEMENTS = EnteMemoryWidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
@@ -1005,8 +1005,8 @@
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SlideshowWidget/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SlideshowWidget;
INFOPLIST_FILE = EnteMemoryWidget/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = EnteMemoryWidget;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 The Chromium Authors. All rights reserved.";
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
LD_RUNPATH_SEARCH_PATHS = (
@@ -1017,7 +1017,7 @@
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.ente.frame.SlideshowWidget;
PRODUCT_BUNDLE_IDENTIFIER = io.ente.frame.EnteMemoryWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -1037,7 +1037,7 @@
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = SlideshowWidgetExtension.entitlements;
CODE_SIGN_ENTITLEMENTS = EnteMemoryWidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
@@ -1045,8 +1045,8 @@
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SlideshowWidget/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = SlideshowWidget;
INFOPLIST_FILE = EnteMemoryWidget/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = EnteMemoryWidget;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 The Chromium Authors. All rights reserved.";
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
LD_RUNPATH_SEARCH_PATHS = (
@@ -1057,7 +1057,7 @@
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.ente.frame.SlideshowWidget;
PRODUCT_BUNDLE_IDENTIFIER = io.ente.frame.EnteMemoryWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -1089,7 +1089,7 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
CEE6BE812D7AE7FE00E4048B /* Build configuration list for PBXNativeTarget "SlideshowWidgetExtension" */ = {
CEE6BE812D7AE7FE00E4048B /* Build configuration list for PBXNativeTarget "EnteMemoryWidgetExtension" */ = {
isa = XCConfigurationList;
buildConfigurations = (
CEE6BE7D2D7AE7FE00E4048B /* Debug */,

View File

@@ -11,7 +11,7 @@
</array>
<key>com.apple.security.application-groups</key>
<array>
<string>group.io.ente.frame.SlideshowWidget</string>
<string>group.io.ente.frame.EnteMemoryWidget</string>
</array>
</dict>
</plist>

View File

@@ -1 +0,0 @@
{"images":[{"size":"20x20","idiom":"iphone","filename":"IconGreen-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"IconGreen-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"IconGreen-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"IconGreen-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"IconGreen-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"IconGreen-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"IconGreen-40x40@3x.png","scale":"3x"},{"size":"50x50","idiom":"ipad","filename":"IconGreen-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"IconGreen-50x50@2x.png","scale":"2x"},{"size":"57x57","idiom":"iphone","filename":"IconGreen-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"IconGreen-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"IconGreen-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"IconGreen-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"IconGreen-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"IconGreen-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"IconGreen-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"IconGreen-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"IconGreen-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"IconGreen-40x40@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"IconGreen-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"IconGreen-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"IconGreen-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"IconGreen-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"IconGreen-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"IconGreen-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

View File

@@ -18,6 +18,7 @@ import "package:photos/l10n/l10n.dart";
import "package:photos/service_locator.dart";
import 'package:photos/services/app_lifecycle_service.dart';
import "package:photos/services/home_widget_service.dart";
import "package:photos/services/memory_home_widget_service.dart";
import 'package:photos/services/sync/sync_service.dart';
import 'package:photos/ui/tabs/home_widget.dart';
import "package:photos/ui/viewer/actions/file_viewer.dart";
@@ -65,10 +66,7 @@ class _EnteAppState extends State<EnteApp> with WidgetsBindingObserver {
_memoriesChangedSubscription =
Bus.instance.on<MemoriesChangedEvent>().listen(
(event) async {
await HomeWidgetService.instance.updateMemoryChanged(true);
await HomeWidgetService.instance.initHomeWidget(
forceFetchNewMemories: true,
);
await MemoryHomeWidgetService.instance.memoryChanged();
},
);
}

View File

@@ -209,6 +209,7 @@ class Configuration {
await UploadLocksDB.instance.clearTable();
await IgnoredFilesService.instance.reset();
await TrashDB.instance.clearTable();
unawaited(HomeWidgetService.instance.clearWidget(autoLogout));
if (!autoLogout) {
// Following services won't be initialized if it's the case of autoLogout
FileUploader.instance.clearCachedUploadURLs();
@@ -216,7 +217,6 @@ class Configuration {
FavoritesService.instance.clearCache();
SearchService.instance.clearCache();
PersonService.instance.clearCache();
unawaited(HomeWidgetService.instance.clearHomeWidget());
Bus.instance.fire(UserLoggedOutEvent());
} else {
await _preferences.setBool("auto_logout", true);

View File

@@ -73,7 +73,7 @@ const kSearchSectionLimit = 9;
const maxPickAssetLimit = 50;
const iOSGroupID = "group.io.ente.frame.SlideshowWidget";
const iOSGroupID = "group.io.ente.frame.EnteMemoryWidget";
const blackThumbnailBase64 = '/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEB'
'AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQ'

View File

@@ -124,7 +124,7 @@ Future<void> _homeWidgetSync() async {
try {
await HomeWidgetService.instance.initHomeWidget();
} catch (e, s) {
_logger.severe("Error in initSlideshowWidget", e, s);
_logger.severe("Error in syncing home widget", e, s);
}
}

View File

@@ -1,18 +1,15 @@
import "dart:convert";
import "dart:io";
import "package:figma_squircle/figma_squircle.dart";
import "package:flutter/material.dart";
import "package:flutter/scheduler.dart";
import "package:fluttertoast/fluttertoast.dart";
import 'package:home_widget/home_widget.dart' as hw;
import "package:logging/logging.dart";
import "package:photos/core/configuration.dart";
import "package:path_provider/path_provider.dart";
import "package:path_provider_foundation/path_provider_foundation.dart";
import "package:photos/core/constants.dart";
import "package:photos/models/file/file.dart";
import "package:photos/service_locator.dart";
import "package:photos/services/memory_home_widget_service.dart";
import "package:photos/services/smart_memories_service.dart";
import "package:photos/utils/preload_util.dart";
import "package:photos/utils/thumbnail_util.dart";
import "package:shared_preferences/shared_preferences.dart";
@@ -25,83 +22,37 @@ class HomeWidgetService {
HomeWidgetService._privateConstructor();
init(SharedPreferences prefs) {
hw.HomeWidget.setAppGroupId(iOSGroupID).ignore();
_prefs = prefs;
setAppGroupID(iOSGroupID);
MemoryHomeWidgetService.instance.init(prefs);
}
static const memoryChangedKey = "memoryChanged.widget";
bool _hasSyncedMemory = false;
late final SharedPreferences _prefs;
Future<void> checkPendingMemorySync() async {
await Future.delayed(const Duration(seconds: 5), () {});
final memoryChanged = _prefs.getBool(memoryChangedKey);
final total = await _getTotal();
final forceFetchNewMemories =
memoryChanged == true || total == 0 || total == null;
if (_hasSyncedMemory && !forceFetchNewMemories) {
_logger.info(">>> Memory already synced");
return;
}
await initHomeWidget(forceFetchNewMemories: forceFetchNewMemories);
void setAppGroupID(String id) {
hw.HomeWidget.setAppGroupId(id).ignore();
}
Future<void> updateMemoryChanged(bool value) async {
await _prefs.setBool(memoryChangedKey, value);
Future<void> initHomeWidget() async {
await MemoryHomeWidgetService.instance.initMemoryHW(null);
}
Future<void> initHomeWidget({
bool forceFetchNewMemories = false,
Future<bool?> updateWidget({
required String androidClass,
required String iOSClass,
}) async {
final isLoggedIn = Configuration.instance.isLoggedIn();
if (!isLoggedIn) {
_logger.warning("user not logged in");
return;
}
final areMemoriesShown = memoriesCacheService.showAnyMemories;
if (!areMemoriesShown) {
_logger.warning("memories not enabled");
await clearHomeWidget();
return;
}
if (forceFetchNewMemories) {
await _forceMemoryUpdate();
} else {
final total = await _getTotal();
if (total == 0 || total == null) {
_logger.warning(
"sync stopped because no memory is cached yet, so nothing to sync",
);
return;
}
await _memorySync();
}
return await hw.HomeWidget.updateWidget(
name: androidClass,
androidName: androidClass,
qualifiedAndroidName: 'io.ente.photos.$androidClass',
iOSName: iOSClass,
);
}
Future<void> _forceMemoryUpdate() async {
await _lockAndLoadMemories();
await updateMemoryChanged(false);
}
Future<T?> getData<T>(String key) async =>
await hw.HomeWidget.getWidgetData<T>(key);
Future<void> _memorySync() async {
final homeWidgetCount = await HomeWidgetService.instance.countHomeWidgets();
if (homeWidgetCount == 0) {
_logger.warning("no home widget active");
return;
}
Future<bool?> setData<T>(String key, T? data) async =>
await hw.HomeWidget.saveWidgetData<T>(key, data);
await _updateWidget(text: "[i] refreshing from same set");
_logger.info(">>> Refreshing memory from same set");
}
Future<Size?> _renderFile(
Future<Size?> renderFile(
EnteFile randomFile,
String key,
String title,
@@ -121,19 +72,6 @@ class HomeWidgetService {
return (await hw.HomeWidget.getInstalledWidgets()).length;
}
Future<void> clearHomeWidget() async {
final total = await _getTotal();
if (total == 0 || total == null) return;
_logger.info("Clearing SlideshowWidget");
await _setTotal(0);
_hasSyncedMemory = false;
await _updateWidget(text: "[i] SlideshowWidget cleared & updated");
_logger.info(">>> SlideshowWidget cleared");
}
Future<bool> _captureFile(
EnteFile ogFile,
String key,
@@ -142,40 +80,26 @@ class HomeWidgetService {
try {
final thumbnail = await getThumbnail(ogFile);
final decoded = await decodeImageFromList(thumbnail!);
final double width = decoded.width.toDouble();
final double height = decoded.height.toDouble();
late final String? directory;
final Image img = Image.memory(
thumbnail,
fit: BoxFit.cover,
cacheWidth: width.toInt(),
cacheHeight: height.toInt(),
);
// coverage:ignore-start
if (Platform.isIOS) {
final PathProviderFoundation provider = PathProviderFoundation();
directory = await provider.getContainerPath(
appGroupIdentifier: iOSGroupID,
);
} else {
directory = (await getApplicationSupportDirectory()).path;
}
await PreloadImage.loadImage(img.image);
final String path = '$directory/home_widget/$key.png';
final File file = File(path);
if (!await file.exists()) {
await file.create(recursive: true);
}
await file.writeAsBytes(thumbnail!);
final platformBrightness =
SchedulerBinding.instance.platformDispatcher.platformBrightness;
final widget = ClipSmoothRect(
radius: SmoothBorderRadius(cornerRadius: 32, cornerSmoothing: 1),
child: Container(
width: width,
height: height,
decoration: BoxDecoration(
color: platformBrightness == Brightness.light
? const Color.fromRGBO(251, 251, 251, 1)
: const Color.fromRGBO(27, 27, 27, 1),
image: DecorationImage(image: img.image, fit: BoxFit.cover),
),
),
);
await hw.HomeWidget.renderFlutterWidget(
widget,
logicalSize: Size(width, height),
key: key,
);
await setData(key, path);
final data = {
"title": title,
@@ -202,115 +126,32 @@ class HomeWidgetService {
return true;
}
Future<void> clearWidget(bool autoLogout) async {
if (autoLogout) {
setAppGroupID(iOSGroupID);
}
await MemoryHomeWidgetService.instance.clearWidget();
}
Future<void> onLaunchFromWidget(Uri? uri, BuildContext context) async {
if (uri == null) {
_logger.warning("onLaunchFromWidget: uri is null");
return;
}
_hasSyncedMemory = true;
// sync the memories
initHomeWidget().ignore();
final generatedId = int.tryParse(uri.queryParameters["generatedId"] ?? "");
_logger.info("onLaunchFromWidget: $uri, $generatedId");
if (generatedId == null) {
_logger.warning("onLaunchFromWidget: generatedId is null");
return;
}
await memoriesCacheService.goToMemoryFromGeneratedFileID(
context,
generatedId,
);
}
Future<Map<String, Iterable<EnteFile>>> _getMemories() async {
// if (fetchMemory) {
final memories = await memoriesCacheService.getMemories();
if (memories.isEmpty) {
return {};
}
// flatten the list to list of ente files
final files = memories.asMap().map(
(k, v) => MapEntry(
v.title,
v.memories.map((e) => e.file),
),
);
return files;
}
Future<void> _updateWidget({String? text}) async {
await hw.HomeWidget.updateWidget(
name: 'SlideshowWidgetProvider',
androidName: 'SlideshowWidgetProvider',
qualifiedAndroidName: 'io.ente.photos.SlideshowWidgetProvider',
iOSName: 'SlideshowWidget',
);
if (flagService.internalUser) {
await Fluttertoast.showToast(
msg: text ?? "[i] SlideshowWidget updated",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
backgroundColor: Colors.black,
textColor: Colors.white,
fontSize: 16.0,
if (uri.scheme == "memorywidget") {
_logger.info("onLaunchFromWidget: redirecting to memory widget");
await MemoryHomeWidgetService.instance.onLaunchFromWidget(
generatedId,
context,
);
}
_logger.info(">>> Home Widget updated");
}
Future<int?> _getTotal() async {
return await hw.HomeWidget.getWidgetData<int>("totalSet");
}
Future<void> _setTotal(int total) async {
await hw.HomeWidget.saveWidgetData("totalSet", total);
}
Future<void> _lockAndLoadMemories() async {
final files = await _getMemories();
if (files.isEmpty) {
_logger.warning("No files found, clearing everything");
await clearHomeWidget();
return;
}
int index = 0;
for (final i in files.entries) {
for (final file in i.value) {
final value =
await _renderFile(file, "slideshow_$index", i.key).catchError(
(e, sT) {
_logger.severe("Error rendering widget", e, sT);
return null;
},
);
if (value != null) {
await _setTotal(index);
if (index == 1) {
await _updateWidget(
text: "[i] First memory fetched. updating widget",
);
}
index++;
}
}
}
if (index == 0) {
return;
}
await _updateWidget();
_logger.info(">>> Switching to next memory set");
}
}

View File

@@ -370,6 +370,10 @@ class MemoriesCacheService {
}
}
Future<List<SmartMemory>?> getCachedMemories() async {
return _cachedMemories;
}
Future<void> goToMemoryFromGeneratedFileID(
BuildContext context,
int generatedFileID,

View File

@@ -0,0 +1,253 @@
import "package:flutter/material.dart";
import "package:fluttertoast/fluttertoast.dart";
import "package:logging/logging.dart";
import "package:photos/models/file/file.dart";
import "package:photos/service_locator.dart";
import "package:photos/services/home_widget_service.dart";
import "package:photos/services/sync/local_sync_service.dart";
import "package:shared_preferences/shared_preferences.dart";
import "package:synchronized/synchronized.dart";
class MemoryHomeWidgetService {
final Logger _logger = Logger((MemoryHomeWidgetService).toString());
MemoryHomeWidgetService._privateConstructor();
static final MemoryHomeWidgetService instance =
MemoryHomeWidgetService._privateConstructor();
late final SharedPreferences _prefs;
final _memoryForceRefreshLock = Lock();
bool _hasSyncedMemory = false;
static const memoryChangedKey = "memoryChanged.widget";
static const totalMemories = "totalMemories";
init(SharedPreferences prefs) {
_prefs = prefs;
}
Future<void> _forceMemoryUpdate() async {
await _lockAndLoadMemories();
await updateMemoryChanged(false);
}
Future<void> _memorySync() async {
final homeWidgetCount = await HomeWidgetService.instance.countHomeWidgets();
if (homeWidgetCount == 0) {
_logger.warning("no home widget active");
return;
}
await _updateWidget(text: "refreshing from same set");
}
Future<bool> hasAnyBlockers() async {
final hasCompletedFirstImport =
LocalSyncService.instance.hasCompletedFirstImport();
if (!hasCompletedFirstImport) {
_logger.warning("first import not completed");
return true;
}
final areMemoriesShown = memoriesCacheService.showAnyMemories;
if (!areMemoriesShown) {
_logger.warning("memories not enabled");
return true;
}
return false;
}
Future<void> initMemoryHW(bool? forceFetchNewMemories) async {
final result = await hasAnyBlockers();
if (result) {
await clearWidget();
return;
}
await _memoryForceRefreshLock.synchronized(() async {
final isTotalEmpty = await _checkIfTotalEmpty();
forceFetchNewMemories ??= await getForceFetchCondition(isTotalEmpty);
_logger.warning(
"init memory hw: forceFetch: $forceFetchNewMemories, isTotalEmpty: $isTotalEmpty",
);
if (forceFetchNewMemories!) {
await _forceMemoryUpdate();
} else if (!isTotalEmpty) {
await _memorySync();
}
});
}
Future<void> clearWidget() async {
final isTotalEmpty = await _checkIfTotalEmpty();
if (isTotalEmpty) {
_logger.info(">>> Nothing to clear");
return;
}
_logger.info("Clearing MemoryHomeWidget");
await _setTotal(null);
_hasSyncedMemory = false;
await _updateWidget(text: "MemoryHomeWidget cleared & updated");
}
Future<void> updateMemoryChanged(bool value) async {
_logger.info("Updating memory changed to $value");
await _prefs.setBool(memoryChangedKey, value);
}
Future<bool> _checkIfTotalEmpty() async {
final total = await _getTotal();
return total == 0 || total == null;
}
Future<bool> getForceFetchCondition(bool isTotalEmpty) async {
final memoryChanged = _prefs.getBool(memoryChangedKey);
if (memoryChanged == true) return true;
final cachedMemories = await memoriesCacheService.getCachedMemories();
final forceFetchNewMemories =
isTotalEmpty && (cachedMemories?.isNotEmpty ?? false);
return forceFetchNewMemories;
}
Future<void> checkPendingMemorySync() async {
await Future.delayed(const Duration(seconds: 5), () {});
final isTotalEmpty = await _checkIfTotalEmpty();
final forceFetchNewMemories = await getForceFetchCondition(isTotalEmpty);
if (_hasSyncedMemory && !forceFetchNewMemories) {
_logger.info(">>> Memory already synced");
return;
}
await HomeWidgetService.instance.initHomeWidget();
}
Future<Map<String, Iterable<EnteFile>>> _getMemories() async {
final memories = await memoriesCacheService.getMemories();
if (memories.isEmpty) {
return {};
}
// flatten the memories to a list of files and take first 50
final files = memories.take(50).toList().asMap().map(
(k, v) => MapEntry(
v.title,
v.memories.map((e) => e.file),
),
);
return files;
}
Future<void> _updateWidget({String? text}) async {
await HomeWidgetService.instance.updateWidget(
androidClass: "EnteMemoryWidgetProvider",
iOSClass: "EnteMemoryWidget",
);
if (flagService.internalUser) {
await Fluttertoast.showToast(
msg: "[i] ${text ?? "MemoryHomeWidget updated"}",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
backgroundColor: Colors.black,
textColor: Colors.white,
fontSize: 16.0,
);
}
_logger.info(">>> Home Widget updated, type: ${text ?? "normal"}");
}
Future<void> memoryChanged() async {
final cachedMemories = await memoriesCacheService.getCachedMemories();
final currentTotal = cachedMemories?.length ?? 0;
final int total = await _getTotal() ?? 0;
if (total == currentTotal && total == 0) {
_logger.info(">>> Memories not changed, doing nothing");
return;
}
_logger.info(">>> Memories changed, updating widget");
await updateMemoryChanged(true);
await initMemoryHW(true);
}
Future<int?> _getTotal() async {
return HomeWidgetService.instance.getData<int>(totalMemories);
}
Future<void> _setTotal(int? total) async =>
await HomeWidgetService.instance.setData(totalMemories, total);
Future<void> _lockAndLoadMemories() async {
final files = await _getMemories();
if (files.isEmpty) {
_logger.warning("No files found, clearing everything");
await clearWidget();
return;
}
final total = await _getTotal();
_logger.info(">>> Total memories before: $total");
int index = 0;
for (final i in files.entries) {
for (final file in i.value) {
final value = await HomeWidgetService.instance
.renderFile(file, "memory_widget_$index", i.key)
.catchError(
(e, sT) {
_logger.severe("Error rendering widget", e, sT);
return null;
},
);
if (value != null) {
final result = await hasAnyBlockers();
if (result) {
return;
}
await _setTotal(index);
if (index == 1) {
await _updateWidget(
text: "First memory fetched. updating widget",
);
}
index++;
}
}
}
if (index == 0) {
return;
}
await _updateWidget(
text: ">>> Switching to next memory set, total: $index",
);
}
Future<void> onLaunchFromWidget(int generatedId, BuildContext context) async {
_hasSyncedMemory = true;
await _memorySync();
await memoriesCacheService.goToMemoryFromGeneratedFileID(
context,
generatedId,
);
}
}

View File

@@ -7,11 +7,11 @@ import "package:photos/db/ml/db.dart";
import "package:photos/events/people_changed_event.dart";
import "package:photos/models/ml/face/person.dart";
import "package:photos/service_locator.dart";
import "package:photos/services/home_widget_service.dart";
import "package:photos/services/machine_learning/face_ml/person/person_service.dart";
import "package:photos/services/machine_learning/ml_indexing_isolate.dart";
import 'package:photos/services/machine_learning/ml_service.dart';
import "package:photos/services/machine_learning/semantic_search/semantic_search_service.dart";
import "package:photos/services/memory_home_widget_service.dart";
import 'package:photos/theme/ente_theme.dart';
import 'package:photos/ui/components/captioned_text_widget.dart';
import 'package:photos/ui/components/expandable_menu_item_widget.dart';
@@ -322,13 +322,13 @@ class _MLDebugSectionWidgetState extends State<MLDebugSectionWidget> {
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Force sync memory widget",
title: "Force memory widget data refresh",
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async => await HomeWidgetService.instance
.initHomeWidget(forceFetchNewMemories: true),
onTap: () async =>
await MemoryHomeWidgetService.instance.initMemoryHW(true),
),
sectionOptionSpacing,
MenuItemWidget(
@@ -338,7 +338,8 @@ class _MLDebugSectionWidgetState extends State<MLDebugSectionWidget> {
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async => await HomeWidgetService.instance.initHomeWidget(),
onTap: () async =>
await MemoryHomeWidgetService.instance.initMemoryHW(false),
),
sectionOptionSpacing,
MenuItemWidget(

View File

@@ -5,7 +5,7 @@ import "package:photos/core/event_bus.dart";
import "package:photos/events/hide_shared_items_from_home_gallery_event.dart";
import "package:photos/generated/l10n.dart";
import "package:photos/service_locator.dart";
import "package:photos/services/home_widget_service.dart";
import "package:photos/services/memory_home_widget_service.dart";
import "package:photos/theme/ente_theme.dart";
import "package:photos/ui/components/buttons/icon_button_widget.dart";
import "package:photos/ui/components/captioned_text_widget.dart";
@@ -109,7 +109,8 @@ class _GallerySettingsScreenState extends State<GallerySettingsScreen> {
),
);
unawaited(
HomeWidgetService.instance.initHomeWidget(),
MemoryHomeWidgetService.instance
.initMemoryHW(true),
);
},
),

View File

@@ -37,8 +37,8 @@ import "package:photos/service_locator.dart";
import 'package:photos/services/account/user_service.dart';
import 'package:photos/services/app_lifecycle_service.dart';
import 'package:photos/services/collections_service.dart';
import "package:photos/services/home_widget_service.dart";
import "package:photos/services/machine_learning/face_ml/person/person_service.dart";
import "package:photos/services/memory_home_widget_service.dart";
import "package:photos/services/notification_service.dart";
import "package:photos/services/sync/diff_fetcher.dart";
import 'package:photos/services/sync/local_sync_service.dart';
@@ -124,7 +124,9 @@ class _HomeWidgetState extends State<HomeWidget> {
_logger.info("Building initstate");
super.initState();
HomeWidgetService.instance.checkPendingMemorySync();
if (LocalSyncService.instance.hasCompletedFirstImport()) {
MemoryHomeWidgetService.instance.checkPendingMemorySync();
}
_tabChangedEventSubscription =
Bus.instance.on<TabChangedEvent>().listen((event) {
_selectedTabIndex = event.selectedIndex;
@@ -184,13 +186,11 @@ class _HomeWidgetState extends State<HomeWidget> {
}
Future.delayed(
delayInRefresh,
() => {
if (mounted)
{
setState(
() {},
),
},
() {
if (mounted) {
setState(() {});
MemoryHomeWidgetService.instance.checkPendingMemorySync();
}
},
);
}

View File

@@ -12,7 +12,7 @@ description: ente photos application
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.9.118+1028
version: 0.9.119+1029
publish_to: none
environment:
@@ -189,7 +189,7 @@ dependencies:
styled_text: ^8.1.0
syncfusion_flutter_core: ^25.2.5
syncfusion_flutter_sliders: ^25.2.5
synchronized: ^3.1.0
synchronized: ^3.3.0+3
system_info_plus: ^0.0.6
tuple: ^2.0.0
ua_client_hints: ^1.4.0