Skip to content

Commit c0ab921

Browse files
feat(package_info_plus): add update time (#3466)
1 parent 1cbf2b5 commit c0ab921

File tree

11 files changed

+254
-69
lines changed

11 files changed

+254
-69
lines changed

packages/package_info_plus/package_info_plus/android/src/main/kotlin/dev/fluttercommunity/plus/packageinfo/PackageInfoPlugin.kt

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ class PackageInfoPlugin : MethodCallHandler, FlutterPlugin {
3939
val buildSignature = getBuildSignature(packageManager)
4040

4141
val installerPackage = getInstallerPackageName()
42-
val installTimeMillis = getInstallTimeMillis()
42+
43+
val installTimeMillis = info.firstInstallTime
44+
val updateTimeMillis = info.lastUpdateTime
4345

4446
val infoMap = HashMap<String, String>()
4547
infoMap.apply {
@@ -49,7 +51,8 @@ class PackageInfoPlugin : MethodCallHandler, FlutterPlugin {
4951
put("buildNumber", getLongVersionCode(info).toString())
5052
if (buildSignature != null) put("buildSignature", buildSignature)
5153
if (installerPackage != null) put("installerStore", installerPackage)
52-
if (installTimeMillis != null) put("installTime", installTimeMillis.toString())
54+
put("installTime", installTimeMillis.toString())
55+
put("updateTime", updateTimeMillis.toString())
5356
}.also { resultingMap ->
5457
result.success(resultingMap)
5558
}
@@ -76,22 +79,6 @@ class PackageInfoPlugin : MethodCallHandler, FlutterPlugin {
7679
}
7780
}
7881

79-
private fun getInstallTimeMillis(): Long? {
80-
return try {
81-
val packageManager = applicationContext!!.packageManager
82-
val packageName = applicationContext!!.packageName
83-
val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
84-
packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0))
85-
} else {
86-
packageManager.getPackageInfo(packageName, 0)
87-
}
88-
89-
packageInfo.firstInstallTime
90-
} catch (e: PackageManager.NameNotFoundException) {
91-
null
92-
}
93-
}
94-
9582
@Suppress("deprecation")
9683
private fun getLongVersionCode(info: PackageInfo): Long {
9784
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {

packages/package_info_plus/package_info_plus/example/integration_test/package_info_plus_test.dart

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ void main() {
2929
expect(info.version, '1.2.3');
3030
expect(info.installerStore, null);
3131
expect(info.installTime, null);
32+
expect(info.updateTime, null);
3233
} else {
3334
if (Platform.isAndroid) {
3435
final androidVersionInfo = await DeviceInfoPlugin().androidInfo;
@@ -52,6 +53,14 @@ void main() {
5253
lessThanOrEqualTo(1),
5354
),
5455
);
56+
expect(
57+
info.updateTime,
58+
isA<DateTime>().having(
59+
(d) => d.difference(DateTime.now()).inMinutes,
60+
'Was just updated',
61+
lessThanOrEqualTo(1),
62+
),
63+
);
5564
} else if (Platform.isIOS) {
5665
expect(info.appName, 'Package Info Plus Example');
5766
expect(info.buildNumber, '4');
@@ -67,6 +76,14 @@ void main() {
6776
lessThanOrEqualTo(1),
6877
),
6978
);
79+
expect(
80+
info.updateTime,
81+
isA<DateTime>().having(
82+
(d) => d.difference(DateTime.now()).inMinutes,
83+
'Was just updated',
84+
lessThanOrEqualTo(1),
85+
),
86+
);
7087
} else if (Platform.isMacOS) {
7188
expect(info.appName, 'Package Info Plus Example');
7289
expect(info.buildNumber, '4');
@@ -82,6 +99,14 @@ void main() {
8299
lessThanOrEqualTo(1),
83100
),
84101
);
102+
expect(
103+
info.updateTime,
104+
isA<DateTime>().having(
105+
(d) => d.difference(DateTime.now()).inMinutes,
106+
'Was just updated',
107+
lessThanOrEqualTo(1),
108+
),
109+
);
85110
} else if (Platform.isLinux) {
86111
expect(info.appName, 'package_info_plus_example');
87112
expect(info.buildNumber, '4');
@@ -96,6 +121,14 @@ void main() {
96121
lessThanOrEqualTo(1),
97122
),
98123
);
124+
expect(
125+
info.updateTime,
126+
isA<DateTime>().having(
127+
(d) => d.difference(DateTime.now()).inMinutes,
128+
'Was just updated',
129+
lessThanOrEqualTo(1),
130+
),
131+
);
99132
} else if (Platform.isWindows) {
100133
expect(info.appName, 'example');
101134
expect(info.buildNumber, '4');
@@ -111,6 +144,14 @@ void main() {
111144
lessThanOrEqualTo(1),
112145
),
113146
);
147+
expect(
148+
info.updateTime,
149+
isA<DateTime>().having(
150+
(d) => d.difference(DateTime.now()).inMinutes,
151+
'Was just updated',
152+
lessThanOrEqualTo(1),
153+
),
154+
);
114155
} else {
115156
throw (UnsupportedError('platform not supported'));
116157
}
@@ -127,6 +168,7 @@ void main() {
127168
expect(find.text('Not set'), findsOneWidget);
128169
expect(find.text('not available'), findsOneWidget);
129170
expect(find.text('Install time not available'), findsOneWidget);
171+
expect(find.text('Update time not available'), findsOneWidget);
130172
} else {
131173
final expectedInstallTimeIso = testStartTime.toIso8601String();
132174
final installTimeRegex = RegExp(
@@ -153,7 +195,7 @@ void main() {
153195
} else {
154196
expect(find.text('not available'), findsOneWidget);
155197
}
156-
expect(find.textContaining(installTimeRegex), findsOneWidget);
198+
expect(find.textContaining(installTimeRegex), findsNWidgets(2));
157199
} else if (Platform.isIOS) {
158200
expect(find.text('Package Info Plus Example'), findsOneWidget);
159201
expect(find.text('4'), findsOneWidget);
@@ -162,7 +204,7 @@ void main() {
162204
expect(find.text('1.2.3'), findsOneWidget);
163205
expect(find.text('Not set'), findsOneWidget);
164206
expect(find.text('com.apple.simulator'), findsOneWidget);
165-
expect(find.textContaining(installTimeRegex), findsOneWidget);
207+
expect(find.textContaining(installTimeRegex), findsNWidgets(2));
166208
} else if (Platform.isMacOS) {
167209
expect(find.text('Package Info Plus Example'), findsOneWidget);
168210
expect(find.text('4'), findsOneWidget);
@@ -171,20 +213,20 @@ void main() {
171213
expect(find.text('1.2.3'), findsOneWidget);
172214
expect(find.text('Not set'), findsOneWidget);
173215
expect(find.text('not available'), findsOneWidget);
174-
expect(find.textContaining(installTimeRegex), findsOneWidget);
216+
expect(find.textContaining(installTimeRegex), findsNWidgets(2));
175217
} else if (Platform.isLinux) {
176218
expect(find.text('package_info_plus_example'), findsNWidgets(2));
177219
expect(find.text('1.2.3'), findsOneWidget);
178220
expect(find.text('4'), findsOneWidget);
179221
expect(find.text('Not set'), findsOneWidget);
180-
expect(find.textContaining(installTimeRegex), findsOneWidget);
222+
expect(find.textContaining(installTimeRegex), findsNWidgets(2));
181223
} else if (Platform.isWindows) {
182224
expect(find.text('example'), findsNWidgets(2));
183225
expect(find.text('1.2.3'), findsOneWidget);
184226
expect(find.text('4'), findsOneWidget);
185227
expect(find.text('Not set'), findsOneWidget);
186228
expect(find.text('not available'), findsOneWidget);
187-
expect(find.textContaining(installTimeRegex), findsOneWidget);
229+
expect(find.textContaining(installTimeRegex), findsNWidgets(2));
188230
} else {
189231
throw (UnsupportedError('platform not supported'));
190232
}

packages/package_info_plus/package_info_plus/example/lib/main.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ class _MyHomePageState extends State<MyHomePage> {
8989
_packageInfo.installTime?.toIso8601String() ??
9090
'Install time not available',
9191
),
92+
_infoTile(
93+
'Update time',
94+
_packageInfo.updateTime?.toIso8601String() ??
95+
'Update time not available',
96+
),
9297
],
9398
),
9499
);

packages/package_info_plus/package_info_plus/ios/package_info_plus/Sources/package_info_plus/FPPPackageInfoPlusPlugin.m

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,8 @@ - (void)handleMethodCall:(FlutterMethodCall *)call
2626
? @"com.apple.testflight"
2727
: @"com.apple";
2828

29-
NSURL* urlToDocumentsFolder = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
30-
__autoreleasing NSError *error;
31-
NSDate *installDate = [[[NSFileManager defaultManager] attributesOfItemAtPath:urlToDocumentsFolder.path error:&error] objectForKey:NSFileCreationDate];
32-
NSNumber *installTimeMillis = installDate ? @((long long)([installDate timeIntervalSince1970] * 1000)) : [NSNull null];
33-
29+
NSDate *installDate = [self getInstallDate];
30+
NSDate *updateDate = [self getUpdateDate];
3431

3532
result(@{
3633
@"appName" : [[NSBundle mainBundle]
@@ -46,12 +43,46 @@ - (void)handleMethodCall:(FlutterMethodCall *)call
4643
objectForInfoDictionaryKey:@"CFBundleVersion"]
4744
?: [NSNull null],
4845
@"installerStore" : installerStore,
49-
@"installTime" : installTimeMillis ? [installTimeMillis stringValue] : [NSNull null]
46+
@"installTime" : [self getTimeMillisStringFromDate:installDate] ?: [NSNull null],
47+
@"updateTime" : [self getTimeMillisStringFromDate:updateDate] ?: [NSNull null]
5048
});
5149

5250
} else {
5351
result(FlutterMethodNotImplemented);
5452
}
5553
}
5654

55+
- (NSDate *)getInstallDate {
56+
NSURL* urlToDocumentsFolder = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
57+
__autoreleasing NSError *error;
58+
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:urlToDocumentsFolder.path error:&error];
59+
60+
if (error) {
61+
return nil;
62+
}
63+
64+
return [attributes objectForKey:NSFileCreationDate];
65+
}
66+
67+
- (NSDate *)getUpdateDate {
68+
__autoreleasing NSError *error;
69+
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[[NSBundle mainBundle] bundlePath] error:&error];
70+
NSDate *updateDate = [attributes fileModificationDate];
71+
72+
if (error) {
73+
return nil;
74+
}
75+
76+
return updateDate;
77+
}
78+
79+
- (NSString *)getTimeMillisStringFromDate:(NSDate *)date {
80+
if (!date) {
81+
return nil;
82+
}
83+
84+
NSNumber *timeMillis = @((long long)([date timeIntervalSince1970] * 1000));
85+
return [timeMillis stringValue];
86+
}
87+
5788
@end

packages/package_info_plus/package_info_plus/lib/package_info_plus.dart

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class PackageInfo {
2828
this.buildSignature = '',
2929
this.installerStore,
3030
this.installTime,
31+
this.updateTime,
3132
});
3233

3334
static PackageInfo? _fromPlatform;
@@ -90,6 +91,7 @@ class PackageInfo {
9091
buildSignature: platformData.buildSignature,
9192
installerStore: platformData.installerStore,
9293
installTime: platformData.installTime,
94+
updateTime: platformData.updateTime,
9395
);
9496
return _fromPlatform!;
9597
}
@@ -159,6 +161,15 @@ class PackageInfo {
159161
/// - On web, returns `null`.
160162
final DateTime? installTime;
161163

164+
/// The time when the application was last updated.
165+
///
166+
/// - On Android, returns `PackageManager.lastUpdateTime`
167+
/// - On iOS and macOS, return the last modified date of the app main bundle
168+
/// - On Windows and Linux, returns the last modified date of the app executable.
169+
/// If the last modified date is not available, returns `null`.
170+
/// - On web, returns `null`.
171+
final DateTime? updateTime;
172+
162173
/// Initializes the application metadata with mock values for testing.
163174
///
164175
/// If the singleton instance has been initialized already, it is overwritten.
@@ -171,6 +182,7 @@ class PackageInfo {
171182
required String buildSignature,
172183
String? installerStore,
173184
DateTime? installTime,
185+
DateTime? updateTime,
174186
}) {
175187
_fromPlatform = PackageInfo(
176188
appName: appName,
@@ -180,6 +192,7 @@ class PackageInfo {
180192
buildSignature: buildSignature,
181193
installerStore: installerStore,
182194
installTime: installTime,
195+
updateTime: updateTime,
183196
);
184197
}
185198

@@ -195,7 +208,8 @@ class PackageInfo {
195208
buildNumber == other.buildNumber &&
196209
buildSignature == other.buildSignature &&
197210
installerStore == other.installerStore &&
198-
installTime == other.installTime;
211+
installTime == other.installTime &&
212+
updateTime == other.updateTime;
199213

200214
/// Overwrite hashCode for value equality
201215
@override
@@ -206,11 +220,12 @@ class PackageInfo {
206220
buildNumber.hashCode ^
207221
buildSignature.hashCode ^
208222
installerStore.hashCode ^
209-
installTime.hashCode;
223+
installTime.hashCode ^
224+
updateTime.hashCode;
210225

211226
@override
212227
String toString() {
213-
return 'PackageInfo(appName: $appName, buildNumber: $buildNumber, packageName: $packageName, version: $version, buildSignature: $buildSignature, installerStore: $installerStore, installTime: $installTime)';
228+
return 'PackageInfo(appName: $appName, buildNumber: $buildNumber, packageName: $packageName, version: $version, buildSignature: $buildSignature, installerStore: $installerStore, installTime: $installTime, updateTime: $updateTime)';
214229
}
215230

216231
Map<String, dynamic> _toMap() {
@@ -222,6 +237,7 @@ class PackageInfo {
222237
if (buildSignature.isNotEmpty) 'buildSignature': buildSignature,
223238
if (installerStore?.isNotEmpty ?? false) 'installerStore': installerStore,
224239
if (installTime != null) 'installTime': installTime!.toIso8601String(),
240+
if (updateTime != null) 'updateTime': updateTime!.toIso8601String(),
225241
};
226242
}
227243

0 commit comments

Comments
 (0)