Skip to content

Commit 27cc2cc

Browse files
committed
Refactor MSStoreService
- Improved uwp packages version comparison logic - Allow Store apps to be installed using a product ID or a link from the search bar
1 parent 223cd2c commit 27cc2cc

File tree

5 files changed

+102
-81
lines changed

5 files changed

+102
-81
lines changed

lib/l10n/intl_ar.arb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
"miscHibernateModeLabel": "وضعية السبات",
7676
"miscHibernateModeDescription": "كامل - يدعم السبات وبدء التشغيل السريع. سيكون حجم ملف السبات 40% من الذاكرة العشوائية (RAM) المثبتة. يمكن إضافة السبات إلى قائمة الطاقة.\n\nمخفَّض - يدعم فقط بدء التشغيل السريع بدون السبات، حجم ملف السبات سيكون 20% من الذاكرة العشوائية الفعلية المثبتة ويقوم بإزالة السبات من قائمة الطاقة",
7777
"miscFastStartupLabel": "بدء التشغيل السريع",
78-
"miscFastStartupDescription": "حفظ الجلسة الحالية في C:\hiberfil.sys لبدء تشغيل أسرع، ولا يؤثر على إعادة التشغيل. يكون معطلًا افتراضيًا لتجنب عدم الاستقرار أثناء وجود نظامين أو ترقيات النظام",
78+
"miscFastStartupDescription": "حفظ الجلسة الحالية في C:\\hiberfil.sys لبدء تشغيل أسرع، ولا يؤثر على إعادة التشغيل. يكون معطلًا افتراضيًا لتجنب عدم الاستقرار أثناء وجود نظامين أو ترقيات النظام",
7979
"miscTMMonitoringLabel": "مراقبة الشبكة ووحدة معالجة الرسومات (GPU)",
8080
"miscTMMonitoringDescription": "تنشيط خدمات المراقبة لمدير المهام",
8181
"miscMpoLabel": "التراكب متعدد الأسطح (MPO)",

lib/l10n/untranslated.json

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
{
22
"zh": [
3-
"experimental",
4-
"warning",
5-
"install",
6-
"installing",
7-
"close",
8-
"search",
93
"usabilitySESLabel",
104
"usabilitySESDescription",
115
"perfBALabel",

lib/models/ms_store/packages_info.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ part 'packages_info.freezed.dart';
33
part 'packages_info.g.dart';
44

55
@freezed
6-
class PackagesInfo with _$PackagesInfo, Comparable<PackagesInfo> {
6+
class PackagesInfo with _$PackagesInfo implements Comparable<PackagesInfo>{
77
PackagesInfo._();
88

99
factory PackagesInfo(

lib/screens/pages/ms_store_page.dart

Lines changed: 58 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import 'package:fluent_ui/fluent_ui.dart';
2-
import '../../l10n/generated/localizations.dart';
3-
import '../../models/ms_store/search_response.dart';
4-
import '../../widgets/card_highlight.dart';
5-
import '../../models/ms_store/packages_info.dart';
6-
import '../../services/msstore_service.dart';
7-
import '../../widgets/dialogs/msstore_dialogs.dart';
8-
import '../../widgets/download_widget.dart';
2+
import 'package:revitool/l10n/generated/localizations.dart';
3+
import 'package:revitool/models/ms_store/packages_info.dart';
4+
import 'package:revitool/models/ms_store/search_response.dart';
5+
import 'package:revitool/services/msstore_service.dart';
6+
import 'package:revitool/widgets/card_highlight.dart';
7+
import 'package:revitool/widgets/dialogs/msstore_dialogs.dart';
8+
import 'package:revitool/widgets/download_widget.dart';
99

1010
class MSStorePage extends StatefulWidget {
1111
const MSStorePage({super.key});
@@ -16,9 +16,9 @@ class MSStorePage extends StatefulWidget {
1616

1717
class _MSStorePageState extends State<MSStorePage>
1818
with AutomaticKeepAliveClientMixin<MSStorePage> {
19-
final TextEditingController _textEditingController = TextEditingController();
19+
final _textEditingController = TextEditingController();
2020
List<ProductsList> _productsList = [];
21-
final MSStoreService _msStoreService = MSStoreService();
21+
final _msStoreService = MSStoreService();
2222
String _selectedRing = "Retail";
2323

2424
@override
@@ -27,13 +27,33 @@ class _MSStorePageState extends State<MSStorePage>
2727
super.dispose();
2828
}
2929

30-
void _onSearchButtonPressed() async {
30+
Future<void> _onSearchButtonPressed() async {
3131
final query = _textEditingController.text;
32-
final data = await _msStoreService.searchProducts(query);
3332

34-
setState(() {
35-
_productsList = data;
36-
});
33+
if (query.startsWith("9") && query.length == 12 ||
34+
query.startsWith("XP") && query.length == 14) {
35+
await showInstallDialog(
36+
context,
37+
ReviLocalizations.of(context).msstoreSearchingPackages,
38+
query,
39+
_selectedRing);
40+
} else if (query.startsWith('https://') &&
41+
query.contains('microsoft.com')) {
42+
final uri = Uri.parse(query);
43+
final productId = uri.pathSegments.last;
44+
debugPrint(productId);
45+
await showInstallDialog(
46+
context,
47+
ReviLocalizations.of(context).msstoreSearchingPackages,
48+
productId,
49+
_selectedRing);
50+
} else {
51+
final data = await _msStoreService.searchProducts(query, _selectedRing);
52+
53+
setState(() {
54+
_productsList = data;
55+
});
56+
}
3757
}
3858

3959
@override
@@ -56,7 +76,7 @@ class _MSStorePageState extends State<MSStorePage>
5676
controller: _textEditingController,
5777
placeholder: ReviLocalizations.of(context).search,
5878
expands: false,
59-
onSubmitted: (value) => _onSearchButtonPressed()),
79+
onSubmitted: (value) async => await _onSearchButtonPressed()),
6080
),
6181
const SizedBox(width: 10),
6282
ComboBox<String>(
@@ -88,7 +108,7 @@ class _MSStorePageState extends State<MSStorePage>
88108
],
89109
),
90110
const SizedBox(height: 10),
91-
for (var product in _productsList) ...[
111+
for (final product in _productsList) ...[
92112
if (product.displayPrice == "Free") ...[
93113
CardHighlight(
94114
label: product.title,
@@ -97,19 +117,11 @@ class _MSStorePageState extends State<MSStorePage>
97117
child: FilledButton(
98118
child: Text(ReviLocalizations.of(context).install),
99119
onPressed: () async {
100-
showLoadingDialog(context,
101-
ReviLocalizations.of(context).msstoreSearchingPackages);
102-
103-
final List<PackagesInfo> packages = await _msStoreService
104-
.startProcess(product.productId!, _selectedRing);
105-
106-
if (!mounted) return;
107-
Navigator.pop(context);
108-
if (packages.isNotEmpty) {
109-
showSelectPackages(product.productId!, packages);
110-
} else {
111-
showNotFound(context);
112-
}
120+
await showInstallDialog(
121+
context,
122+
ReviLocalizations.of(context).msstoreSearchingPackages,
123+
product.productId!,
124+
_selectedRing);
113125
},
114126
),
115127
)
@@ -119,6 +131,23 @@ class _MSStorePageState extends State<MSStorePage>
119131
);
120132
}
121133

134+
Future<void> showInstallDialog(BuildContext context, String loadingTitle,
135+
String productID, String ring) async {
136+
showLoadingDialog(
137+
context, ReviLocalizations.of(context).msstoreSearchingPackages);
138+
139+
final packages =
140+
await _msStoreService.startProcess(productID, _selectedRing);
141+
142+
if (!mounted) return;
143+
Navigator.pop(context);
144+
if (packages.isNotEmpty) {
145+
showSelectPackages(productID, packages);
146+
} else {
147+
showNotFound(context);
148+
}
149+
}
150+
122151
void showSelectPackages(String productId, List<PackagesInfo> packages) async {
123152
final List<TreeViewItem> items = List.generate(
124153
packages.length,

lib/services/msstore_service.dart

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import '../models/ms_store/search_response.dart';
99
import '../models/ms_store/packages_info.dart';
1010
import 'package:xml/xml.dart' as xml;
1111
import '../utils.dart';
12-
import 'registry_utils_service.dart';
12+
// import 'registry_utils_service.dart';
1313

1414
class MSStoreService {
1515
static final MSStoreService _instance = MSStoreService._private();
@@ -41,10 +41,10 @@ class MSStoreService {
4141
);
4242

4343
static final _regex = RegExp(r'"WuCategoryId":"([^"]+)"');
44-
static final _namePattern = RegExp(r'^([A-Za-z]+\.)+[A-Za-z]+');
45-
static final _versionPattern = RegExp(r'_(\d+\.\d+\.\d+\.\d+)_');
44+
static final _namePattern = RegExp(r'^[^_]+');
4645

47-
// static final _genPattern = RegExp(r'\.(\d\.\d)_');
46+
/// major.minor.build.revision
47+
static final _versionPattern = RegExp(r'_(\d+\.\d+\.\d+\.\d+)_');
4848

4949
static const _validUWPExtensions = {
5050
"appx",
@@ -60,7 +60,7 @@ class MSStoreService {
6060

6161
final _dio = Dio();
6262
final _cancelToken = CancelToken();
63-
final RegistryUtilsService _registryUtilsService = RegistryUtilsService();
63+
// final _registryUtilsService = RegistryUtilsService();
6464

6565
factory MSStoreService() {
6666
return _instance;
@@ -90,15 +90,15 @@ class MSStoreService {
9090
}
9191
}
9292

93-
// Non-UWP apps mostly start with "X"
94-
if (productId.startsWith("X")) {
93+
// Non-UWP apps mostly start with "XP"
94+
if (productId.startsWith("XP")) {
9595
return await _getNonAppxPackage(productId);
9696
}
9797

9898
return [];
9999
}
100100

101-
Future<List<ProductsList>> searchProducts(String query) async {
101+
Future<List<ProductsList>> searchProducts(String query, String ring) async {
102102
//"$_filteredSearchAPI?&Query=$query&FilteredCategories=AllProducts&hl=en-us${systemLanguage.toLowerCase()}&
103103
final response = await _dio.get(
104104
"$_searchAPI?gl=US&hl=en-us&query=$query&mediaType=all&age=all&price=all&category=all&subscription=all",
@@ -324,46 +324,44 @@ class MSStoreService {
324324
Map<String?, List<PackagesInfo>> groupedPackages) {
325325
if (groupedPackages.isEmpty) return [];
326326

327-
final latestGenPackages = groupedPackages.values
328-
.map(
329-
(group) => group.fold(
330-
<PackagesInfo>[],
331-
(acc, package) {
332-
final version = _parseVersion(package.name!);
333-
final maxVersion = acc.fold(
334-
-1,
335-
(accVersion, accPackage) =>
336-
_parseVersion(accPackage.name!) > accVersion
337-
? _parseVersion(accPackage.name!)
338-
: accVersion,
339-
);
340-
if (version > maxVersion) {
341-
return [package];
342-
} else if (version == maxVersion) {
343-
acc.add(package);
327+
final latestGenPackages = groupedPackages.values.map((group) {
328+
Map<String, PackagesInfo> versionMap = {};
329+
330+
for (final package in group) {
331+
final match = _versionPattern.firstMatch(package.name!);
332+
333+
if (match != null) {
334+
final ver = match.group(1)!;
335+
336+
if (!versionMap.containsKey(ver)) {
337+
versionMap[ver] = package;
338+
} else {
339+
final lastSavedVer =
340+
versionMap[ver]!.name!.split('.').map(int.parse).toList();
341+
final currentVer = package.name!.split('.').map(int.parse).toList();
342+
343+
final lastSavedVerLength = lastSavedVer.length;
344+
final currentverLength = currentVer.length;
345+
346+
for (int i = 0; i < currentverLength; i++) {
347+
if (i >= lastSavedVerLength || currentVer[i] > lastSavedVer[i]) {
348+
versionMap[ver] = package;
349+
break;
350+
} else if (currentVer[i] < lastSavedVer[i]) {
351+
break;
344352
}
345-
return acc;
346-
},
347-
),
348-
)
349-
.expand((i) => i)
350-
.toList();
353+
}
354+
}
355+
}
356+
}
351357

352-
return latestGenPackages;
353-
}
358+
final latestVersion = versionMap.keys
359+
.reduce((curr, next) => curr.compareTo(next) > 0 ? curr : next);
354360

355-
int _parseVersion(String name) {
356-
final match = _versionPattern.firstMatch(name);
357-
if (match == null) return -1;
361+
return versionMap[latestVersion]!;
362+
}).toList();
358363

359-
List<String> versionParts = match.group(1)!.replaceAll('_', '').split('.');
360-
if (versionParts.isNotEmpty) {
361-
final lastPart = versionParts.last;
362-
versionParts[versionParts.length - 1] = lastPart.characters.first == '0'
363-
? lastPart
364-
: lastPart.replaceAll(RegExp(r'0+$'), '');
365-
}
366-
return int.parse(versionParts.join(''));
364+
return latestGenPackages;
367365
}
368366

369367
Future<List<ProcessResult>> installUWPPackages(String path) async {

0 commit comments

Comments
 (0)