refactor & fix lag when installing extension

This commit is contained in:
Moustapha Kodjo Amadou 2025-02-27 09:05:16 +01:00
parent 7a003d7fde
commit 34c2ce3c37
3 changed files with 435 additions and 493 deletions

View file

@ -27,79 +27,99 @@ class ExtensionScreen extends ConsumerStatefulWidget {
}
class _ExtensionScreenState extends ConsumerState<ExtensionScreen> {
final controller = ScrollController();
final ScrollController controller = ScrollController();
bool isUpdating = false;
Future<void> _refreshSources() {
return switch (widget.itemType) {
ItemType.manga => ref.refresh(
fetchMangaSourcesListProvider(id: null, reFresh: true).future,
),
ItemType.anime => ref.refresh(
fetchAnimeSourcesListProvider(id: null, reFresh: true).future,
),
_ => ref.refresh(
fetchNovelSourcesListProvider(id: null, reFresh: true).future,
),
};
}
Future<void> _updateSource(Source source) {
switch (source.itemType) {
case ItemType.manga:
return ref.read(
fetchMangaSourcesListProvider(id: source.id, reFresh: true).future,
);
case ItemType.anime:
return ref.read(
fetchAnimeSourcesListProvider(id: source.id, reFresh: true).future,
);
default:
return ref.read(
fetchNovelSourcesListProvider(id: source.id, reFresh: true).future,
);
}
}
@override
Widget build(BuildContext context) {
switch (widget.itemType) {
case ItemType.manga:
ref.read(fetchMangaSourcesListProvider(id: null, reFresh: false));
break;
case ItemType.anime:
ref.read(fetchAnimeSourcesListProvider(id: null, reFresh: false));
break;
default:
ref.read(fetchNovelSourcesListProvider(id: null, reFresh: false));
}
final streamExtensions = ref.watch(
getExtensionsStreamProvider(widget.itemType),
);
if (widget.itemType == ItemType.manga) {
ref.watch(fetchMangaSourcesListProvider(id: null, reFresh: false));
} else if (widget.itemType == ItemType.anime) {
ref.watch(fetchAnimeSourcesListProvider(id: null, reFresh: false));
} else {
ref.watch(fetchNovelSourcesListProvider(id: null, reFresh: false));
}
final l10n = l10nLocalizations(context)!;
return RefreshIndicator(
onRefresh:
() =>
widget.itemType == ItemType.manga
? ref.refresh(
fetchMangaSourcesListProvider(
id: null,
reFresh: true,
).future,
)
: widget.itemType == ItemType.anime
? ref.refresh(
fetchAnimeSourcesListProvider(
id: null,
reFresh: true,
).future,
)
: ref.refresh(
fetchNovelSourcesListProvider(
id: null,
reFresh: true,
).future,
),
onRefresh: _refreshSources,
child: Padding(
padding: const EdgeInsets.only(top: 10),
child: streamExtensions.when(
data: (data) {
data =
final filteredData =
widget.query.isEmpty
? data
: data
.where(
(element) => element.name!.toLowerCase().contains(
widget.query.toLowerCase(),
),
(element) =>
element.name?.toLowerCase().contains(
widget.query.toLowerCase(),
) ??
false,
)
.toList();
final notInstalledEntries =
data
.where((element) => element.version == element.versionLast!)
.where((element) => !element.isAdded!)
.toList();
final installedEntries =
data
.where((element) => element.version == element.versionLast!)
.where((element) => element.isAdded!)
.toList();
final updateEntries =
data
.where(
(element) =>
compareVersions(
element.version!,
element.versionLast!,
) <
0,
)
.toList();
final updateEntries = <Source>[];
final installedEntries = <Source>[];
final notInstalledEntries = <Source>[];
for (var element in filteredData) {
final isLatestVersion = element.version == element.versionLast;
if (compareVersions(
element.version ?? '',
element.versionLast ?? '',
) <
0) {
updateEntries.add(element);
} else if (isLatestVersion) {
if (element.isAdded ?? false) {
installedEntries.add(element);
} else {
notInstalledEntries.add(element);
}
}
}
return Scrollbar(
interactive: true,
controller: controller,
@ -108,113 +128,12 @@ class _ExtensionScreenState extends ConsumerState<ExtensionScreen> {
child: CustomScrollView(
controller: controller,
slivers: [
SliverGroupedListView<Source, String>(
elements: updateEntries,
groupBy: (element) => "",
groupSeparatorBuilder:
(_) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
l10n.update_pending,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 13,
),
),
ElevatedButton(
onPressed: () async {
for (var source in updateEntries) {
source.itemType == ItemType.manga
? await ref.watch(
fetchMangaSourcesListProvider(
id: source.id,
reFresh: true,
).future,
)
: source.itemType == ItemType.anime
? await ref.watch(
fetchAnimeSourcesListProvider(
id: source.id,
reFresh: true,
).future,
)
: await ref.watch(
fetchNovelSourcesListProvider(
id: source.id,
reFresh: true,
).future,
);
}
},
child: Text(l10n.update_all),
),
],
),
),
itemBuilder: (context, Source element) {
return ExtensionListTileWidget(source: element);
},
groupComparator:
(group1, group2) => group1.compareTo(group2),
itemComparator:
(item1, item2) => item1.name!.compareTo(item2.name!),
order: GroupedListOrder.ASC,
),
SliverGroupedListView<Source, String>(
elements: installedEntries,
groupBy: (element) => "",
groupSeparatorBuilder:
(_) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Text(
l10n.installed,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 13,
),
),
),
itemBuilder: (context, Source element) {
return ExtensionListTileWidget(source: element);
},
groupComparator:
(group1, group2) => group1.compareTo(group2),
itemComparator:
(item1, item2) => item1.name!.compareTo(item2.name!),
order: GroupedListOrder.ASC,
),
SliverGroupedListView<Source, String>(
elements: notInstalledEntries,
groupBy:
(element) =>
completeLanguageName(element.lang!.toLowerCase()),
groupSeparatorBuilder:
(String groupByValue) => Padding(
padding: const EdgeInsets.only(left: 12),
child: Row(
children: [
Text(
groupByValue,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 13,
),
),
],
),
),
itemBuilder: (context, Source element) {
return ExtensionListTileWidget(source: element);
},
groupComparator:
(group1, group2) => group1.compareTo(group2),
itemComparator:
(item1, item2) => item1.name!.compareTo(item2.name!),
order: GroupedListOrder.ASC,
),
if (updateEntries.isNotEmpty)
_buildUpdateSection(updateEntries, l10n),
if (installedEntries.isNotEmpty)
_buildInstalledSection(installedEntries, l10n),
if (notInstalledEntries.isNotEmpty)
_buildNotInstalledSection(notInstalledEntries),
],
),
);
@ -222,21 +141,7 @@ class _ExtensionScreenState extends ConsumerState<ExtensionScreen> {
error:
(error, _) => Center(
child: ElevatedButton(
onPressed: () {
if (widget.itemType == ItemType.manga) {
ref.invalidate(
fetchMangaSourcesListProvider(id: null, reFresh: true),
);
} else if (widget.itemType == ItemType.anime) {
ref.invalidate(
fetchAnimeSourcesListProvider(id: null, reFresh: true),
);
} else {
ref.invalidate(
fetchNovelSourcesListProvider(id: null, reFresh: true),
);
}
},
onPressed: _refreshSources,
child: Text(context.l10n.refresh),
),
),
@ -245,4 +150,108 @@ class _ExtensionScreenState extends ConsumerState<ExtensionScreen> {
),
);
}
Widget _buildUpdateSection(List<Source> updateEntries, dynamic l10n) {
return SliverGroupedListView<Source, String>(
elements: updateEntries,
groupBy: (_) => "",
groupSeparatorBuilder:
(_) => StatefulBuilder(
builder: (context, setState) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
l10n.update_pending,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 13,
),
),
ElevatedButton(
onPressed:
isUpdating
? null
: () async {
setState(() => isUpdating = true);
try {
for (var source in updateEntries) {
await _updateSource(source);
}
} finally {
setState(() => isUpdating = false);
}
},
child:
isUpdating
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
: Text(l10n.update_all),
),
],
),
);
},
),
itemBuilder:
(context, Source element) =>
ref.watch(extensionListTileWidget(element)),
groupComparator: (group1, group2) => group1.compareTo(group2),
itemComparator:
(item1, item2) => item1.name?.compareTo(item2.name ?? '') ?? 0,
order: GroupedListOrder.ASC,
);
}
Widget _buildInstalledSection(List<Source> installedEntries, dynamic l10n) {
return SliverGroupedListView<Source, String>(
elements: installedEntries,
groupBy: (_) => "",
groupSeparatorBuilder:
(_) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Text(
l10n.installed,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 13),
),
),
itemBuilder:
(context, Source element) =>
ref.watch(extensionListTileWidget(element)),
groupComparator: (group1, group2) => group1.compareTo(group2),
itemComparator:
(item1, item2) => item1.name?.compareTo(item2.name ?? '') ?? 0,
order: GroupedListOrder.ASC,
);
}
Widget _buildNotInstalledSection(List<Source> notInstalledEntries) {
return SliverGroupedListView<Source, String>(
elements: notInstalledEntries,
groupBy:
(element) => completeLanguageName(element.lang?.toLowerCase() ?? ''),
groupSeparatorBuilder:
(String groupByValue) => Padding(
padding: const EdgeInsets.only(left: 12),
child: Text(
groupByValue,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 13),
),
),
itemBuilder:
(context, Source element) =>
ref.watch(extensionListTileWidget(element)),
groupComparator: (group1, group2) => group1.compareTo(group2),
itemComparator:
(item1, item2) => item1.name?.compareTo(item2.name ?? '') ?? 0,
order: GroupedListOrder.ASC,
);
}
}

View file

@ -1,11 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/changed.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart';
import 'package:mangayomi/services/fetch_anime_sources.dart';
import 'package:mangayomi/services/fetch_manga_sources.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
@ -15,14 +12,13 @@ import 'package:mangayomi/utils/cached_network.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:mangayomi/utils/language.dart';
final extensionListTileWidget = Provider.family<Widget, Source>((ref, source) {
return ExtensionListTileWidget(source: source);
});
class ExtensionListTileWidget extends ConsumerStatefulWidget {
final Source source;
final bool isTestSource;
const ExtensionListTileWidget({
super.key,
required this.source,
this.isTestSource = false,
});
const ExtensionListTileWidget({super.key, required this.source});
@override
ConsumerState<ExtensionListTileWidget> createState() =>
@ -32,69 +28,94 @@ class ExtensionListTileWidget extends ConsumerStatefulWidget {
class _ExtensionListTileWidgetState
extends ConsumerState<ExtensionListTileWidget> {
bool _isLoading = false;
late final bool _updateAvailable;
late final bool _sourceNotEmpty;
@override
void initState() {
super.initState();
_updateAvailable =
compareVersions(widget.source.version!, widget.source.versionLast!) < 0;
_sourceNotEmpty =
widget.source.sourceCode != null &&
widget.source.sourceCode!.isNotEmpty;
}
Future<void> _handleSourceFetch() async {
setState(() => _isLoading = true);
try {
final future = switch (widget.source.itemType) {
ItemType.manga => ref.watch(
fetchMangaSourcesListProvider(
id: widget.source.id,
reFresh: true,
).future,
),
ItemType.anime => ref.watch(
fetchAnimeSourcesListProvider(
id: widget.source.id,
reFresh: true,
).future,
),
_ => ref.watch(
fetchNovelSourcesListProvider(
id: widget.source.id,
reFresh: true,
).future,
),
};
await future;
} finally {
if (mounted) setState(() => _isLoading = false);
}
}
Widget _buildTrailingButton(BuildContext context, String label) {
return TextButton(
onPressed:
_isLoading
? null
: () {
if (!_updateAvailable && _sourceNotEmpty) {
context.push('/extension_detail', extra: widget.source);
} else {
_handleSourceFetch();
}
},
child:
_isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(strokeWidth: 2.0),
)
: Text(label),
);
}
@override
Widget build(BuildContext context) {
final l10n = l10nLocalizations(context)!;
final updateAivalable =
widget.isTestSource
? false
: compareVersions(
widget.source.version!,
widget.source.versionLast!,
) <
0;
final sourceNotEmpty =
widget.source.sourceCode != null &&
widget.source.sourceCode!.isNotEmpty;
final buttonLabel =
!_sourceNotEmpty
? l10n.install
: _updateAvailable
? l10n.update
: l10n.settings;
return ListTile(
onTap: () async {
if (sourceNotEmpty || widget.isTestSource) {
if (widget.isTestSource) {
isar.writeTxnSync(() {
isar.sources.putSync(widget.source);
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(
ActionType.updateExtension,
widget.source.id,
widget.source.toJson(),
false,
);
});
}
context.push('/extension_detail', extra: widget.source);
} else {
setState(() {
_isLoading = true;
});
widget.source.itemType == ItemType.manga
? await ref.watch(
fetchMangaSourcesListProvider(
id: widget.source.id,
reFresh: true,
).future,
)
: widget.source.itemType == ItemType.anime
? await ref.watch(
fetchAnimeSourcesListProvider(
id: widget.source.id,
reFresh: true,
).future,
)
: await ref.watch(
fetchNovelSourcesListProvider(
id: widget.source.id,
reFresh: true,
).future,
);
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
},
onTap:
_isLoading
? null
: () {
if (_sourceNotEmpty) {
context.push('/extension_detail', extra: widget.source);
} else {
_handleSourceFetch();
}
},
leading: Container(
height: 37,
width: 37,
@ -145,59 +166,7 @@ class _ExtensionListTileWidgetState
),
],
),
trailing: TextButton(
onPressed:
widget.isTestSource || !updateAivalable && sourceNotEmpty
? () {
context.push('/extension_detail', extra: widget.source);
}
: () async {
setState(() {
_isLoading = true;
});
widget.source.itemType == ItemType.manga
? await ref.watch(
fetchMangaSourcesListProvider(
id: widget.source.id,
reFresh: true,
).future,
)
: widget.source.itemType == ItemType.anime
? await ref.watch(
fetchAnimeSourcesListProvider(
id: widget.source.id,
reFresh: true,
).future,
)
: await ref.watch(
fetchNovelSourcesListProvider(
id: widget.source.id,
reFresh: true,
).future,
);
if (mounted) {
setState(() {
_isLoading = false;
});
}
},
child:
_isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(strokeWidth: 2.0),
)
: Text(
widget.isTestSource
? l10n.settings
: !sourceNotEmpty
? l10n.install
: updateAivalable
? l10n.update
: l10n.settings,
),
),
trailing: _buildTrailingButton(context, buttonLabel),
);
}
}

View file

@ -24,232 +24,196 @@ Future<void> fetchSourcesList({
if (url == null) return;
final req = await http.get(Uri.parse(url));
final info = await PackageInfo.fromPlatform();
final sourceList =
(jsonDecode(req.body) as List).map((e) => Source.fromJson(e)).toList();
(jsonDecode(req.body) as List)
.map((e) => Source.fromJson(e))
.where(
(source) =>
source.itemType == itemType &&
source.appMinVerReq != null &&
compareVersions(info.version, source.appMinVerReq!) > -1,
)
.toList();
final info = await PackageInfo.fromPlatform();
isar.writeTxnSync(() async {
for (var source in sourceList) {
if (source.appMinVerReq != null) {
if (compareVersions(info.version, source.appMinVerReq!) > -1) {
if (source.itemType == itemType) {
if (id != null) {
if (id == source.id) {
final sourc = isar.sources.getSync(id)!;
final req = await http.get(Uri.parse(source.sourceCodeUrl!));
final headers =
getExtensionService(
source..sourceCode = req.body,
).getHeaders();
isar.writeTxnSync(() {
isar.sources.putSync(
sourc
..headers = jsonEncode(headers)
..isAdded = true
..sourceCode = req.body
..sourceCodeUrl = source.sourceCodeUrl
..id = id
..apiUrl = source.apiUrl
..baseUrl = source.baseUrl
..dateFormat = source.dateFormat
..dateFormatLocale = source.dateFormatLocale
..hasCloudflare = source.hasCloudflare
..iconUrl = source.iconUrl
..typeSource = source.typeSource
..lang = source.lang
..isNsfw = source.isNsfw
..name = source.name
..version = source.version
..versionLast = source.version
..itemType = itemType
..isFullData = source.isFullData ?? false
..appMinVerReq = source.appMinVerReq
..sourceCodeLanguage = source.sourceCodeLanguage
..additionalParams = source.additionalParams ?? ""
..isObsolete = false
..repo = repo,
);
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(
ActionType.updateExtension,
sourc.id,
sourc.toJson(),
false,
);
});
// log("successfully installed or updated");
}
} else if (isar.sources.getSync(source.id!) != null) {
// log("exist");
final sourc = isar.sources.getSync(source.id!)!;
if (sourc.isAdded!) {
if (compareVersions(sourc.version!, source.version!) < 0) {
// log("update available auto update");
if (ref.watch(autoUpdateExtensionsStateProvider)) {
final req = await http.get(
Uri.parse(source.sourceCodeUrl!),
);
final headers =
getExtensionService(
source..sourceCode = req.body,
).getHeaders();
isar.writeTxnSync(() {
isar.sources.putSync(
sourc
..headers = jsonEncode(headers)
..isAdded = true
..sourceCode = req.body
..sourceCodeUrl = source.sourceCodeUrl
..id = source.id
..apiUrl = source.apiUrl
..baseUrl = source.baseUrl
..dateFormat = source.dateFormat
..dateFormatLocale = source.dateFormatLocale
..hasCloudflare = source.hasCloudflare
..iconUrl = source.iconUrl
..typeSource = source.typeSource
..lang = source.lang
..isNsfw = source.isNsfw
..name = source.name
..version = source.version
..versionLast = source.version
..itemType = itemType
..isFullData = source.isFullData ?? false
..appMinVerReq = source.appMinVerReq
..sourceCodeLanguage = source.sourceCodeLanguage
..additionalParams = source.additionalParams ?? ""
..isObsolete = false
..repo = repo,
);
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(
ActionType.updateExtension,
sourc.id,
sourc.toJson(),
false,
);
});
} else {
// log("update aivalable");
isar.sources.putSync(sourc..versionLast = source.version);
}
}
}
if (id != null) {
final matchingSource = sourceList.firstWhere(
(source) => source.id == id,
orElse: () => Source(),
);
if (matchingSource.id != null) {
await _updateSource(matchingSource, ref, repo, itemType);
}
} else {
for (var source in sourceList) {
final existingSource = isar.sources.getSync(source.id!);
if (existingSource != null) {
if (existingSource.isAdded! &&
compareVersions(existingSource.version!, source.version!) < 0) {
if (ref.watch(autoUpdateExtensionsStateProvider)) {
await _updateSource(source, ref, repo, itemType);
} else {
final newSource =
Source()
..sourceCodeUrl = source.sourceCodeUrl
..id = source.id
..sourceCode = source.sourceCode
..apiUrl = source.apiUrl
..baseUrl = source.baseUrl
..dateFormat = source.dateFormat
..dateFormatLocale = source.dateFormatLocale
..hasCloudflare = source.hasCloudflare
..iconUrl = source.iconUrl
..typeSource = source.typeSource
..lang = source.lang
..isNsfw = source.isNsfw
..name = source.name
..version = source.version
..versionLast = source.version
..itemType = itemType
..sourceCodeLanguage = source.sourceCodeLanguage
..isFullData = source.isFullData ?? false
..appMinVerReq = source.appMinVerReq
..isObsolete = false
..repo = repo;
isar.sources.putSync(newSource);
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(
ActionType.addExtension,
null,
newSource.toJson(),
false,
);
// log("new source");
isar.sources.putSync(
existingSource..versionLast = source.version,
);
}
}
} else {
_addNewSource(source, ref, repo, itemType);
}
}
}
});
checkIfSourceIsObsolete(sourceList, repo!, itemType, ref);
}
Future<void> _updateSource(
Source source,
Ref ref,
Repo? repo,
ItemType itemType,
) async {
final http = MClient.init(reqcopyWith: {'useDartHttpClient': true});
final req = await http.get(Uri.parse(source.sourceCodeUrl!));
final headers =
getExtensionService(source..sourceCode = req.body).getHeaders();
final updatedSource =
Source()
..headers = jsonEncode(headers)
..isAdded = true
..sourceCode = req.body
..sourceCodeUrl = source.sourceCodeUrl
..id = source.id
..apiUrl = source.apiUrl
..baseUrl = source.baseUrl
..dateFormat = source.dateFormat
..dateFormatLocale = source.dateFormatLocale
..hasCloudflare = source.hasCloudflare
..iconUrl = source.iconUrl
..typeSource = source.typeSource
..lang = source.lang
..isNsfw = source.isNsfw
..name = source.name
..version = source.version
..versionLast = source.version
..itemType = itemType
..isFullData = source.isFullData ?? false
..appMinVerReq = source.appMinVerReq
..sourceCodeLanguage = source.sourceCodeLanguage
..additionalParams = source.additionalParams ?? ""
..isObsolete = false
..repo = repo;
isar.writeTxnSync(() {
isar.sources.putSync(updatedSource);
});
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(
ActionType.updateExtension,
updatedSource.id,
updatedSource.toJson(),
false,
);
}
void _addNewSource(Source source, Ref ref, Repo? repo, ItemType itemType) {
final newSource =
Source()
..sourceCodeUrl = source.sourceCodeUrl
..id = source.id
..sourceCode = source.sourceCode
..apiUrl = source.apiUrl
..baseUrl = source.baseUrl
..dateFormat = source.dateFormat
..dateFormatLocale = source.dateFormatLocale
..hasCloudflare = source.hasCloudflare
..iconUrl = source.iconUrl
..typeSource = source.typeSource
..lang = source.lang
..isNsfw = source.isNsfw
..name = source.name
..version = source.version
..versionLast = source.version
..itemType = itemType
..sourceCodeLanguage = source.sourceCodeLanguage
..isFullData = source.isFullData ?? false
..appMinVerReq = source.appMinVerReq
..isObsolete = false
..repo = repo;
isar.writeTxnSync(() {
isar.sources.putSync(newSource);
});
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(ActionType.addExtension, null, newSource.toJson(), false);
}
void checkIfSourceIsObsolete(
List<Source> sourceList,
Repo repo,
ItemType itemType,
Ref ref,
) {
for (var source
in isar.sources
if (sourceList.isEmpty) return;
final sources =
isar.sources
.filter()
.idIsNotNull()
.itemTypeEqualTo(itemType)
.findAllSync()) {
if (sourceList.isNotEmpty && !(source.isLocal ?? false)) {
final ids =
sourceList.where((e) => e.id != null).map((e) => e.id).toList();
if (ids.isNotEmpty) {
isar.writeTxnSync(() {
if (source.isObsolete !=
(!ids.contains(source.id) &&
source.repo?.jsonUrl == repo.jsonUrl)) {
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(
ActionType.updateExtension,
source.id,
source.toJson(),
false,
);
}
isar.sources.putSync(
source
..isObsolete =
!ids.contains(source.id) &&
source.repo?.jsonUrl == repo.jsonUrl,
);
});
.and()
.isLocalEqualTo(false)
.findAllSync();
if (sources.isEmpty) return;
final sourceIds =
sourceList.where((e) => e.id != null).map((e) => e.id!).toSet();
if (sourceIds.isEmpty) return;
isar.writeTxnSync(() {
for (var source in sources) {
final isNowObsolete =
!sourceIds.contains(source.id) &&
source.repo?.jsonUrl == repo.jsonUrl;
if (source.isObsolete != isNowObsolete) {
source.isObsolete = isNowObsolete;
isar.sources.putSync(source);
ref
.read(synchingProvider(syncId: 1).notifier)
.addChangedPart(
ActionType.updateExtension,
source.id,
source.toJson(),
false,
);
}
}
}
});
}
int compareVersions(String version1, String version2) {
List<String> v1Components = version1.split('.');
List<String> v2Components = version2.split('.');
final v1Parts = version1.split('.');
final v2Parts = version2.split('.');
final minLength =
v1Parts.length < v2Parts.length ? v1Parts.length : v2Parts.length;
for (int i = 0; i < v1Components.length && i < v2Components.length; i++) {
int v1Value = int.parse(
v1Components.length == i + 1 && v1Components[i].length == 1
? "${v1Components[i]}0"
: v1Components[i],
);
int v2Value = int.parse(
v2Components.length == i + 1 && v2Components[i].length == 1
? "${v2Components[i]}0"
: v2Components[i],
);
for (var i = 0; i < minLength; i++) {
final v1Value = int.parse(v1Parts[i].padRight(2, '0'));
final v2Value = int.parse(v2Parts[i].padRight(2, '0'));
if (v1Value < v2Value) {
return -1;
} else if (v1Value > v2Value) {
return 1;
}
final comparison = v1Value.compareTo(v2Value);
if (comparison != 0) return comparison;
}
if (v1Components.length < v2Components.length) {
return -1;
} else if (v1Components.length > v2Components.length) {
return 1;
}
return 0;
return v1Parts.length.compareTo(v2Parts.length);
}