#170 perf: collection sort/group by name, save/load top entries

This commit is contained in:
Thibault Deckers 2022-02-10 14:26:48 +09:00
parent 6c145d5bb5
commit 8b1180684c
67 changed files with 544 additions and 318 deletions

View file

@ -141,7 +141,7 @@ repositories {
}
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.exifinterface:exifinterface:1.3.3'
implementation 'androidx.multidex:multidex:2.0.1'
@ -152,10 +152,10 @@ dependencies {
implementation 'com.github.deckerst:Android-TiffBitmapFactory:876e53870a'
// forked, built by JitPack, cf https://jitpack.io/p/deckerst/pixymeta-android
implementation 'com.github.deckerst:pixymeta-android:706bd73d6e'
implementation 'com.github.bumptech.glide:glide:4.12.0'
implementation 'com.github.bumptech.glide:glide:4.13.0'
kapt 'androidx.annotation:annotation:1.3.0'
kapt 'com.github.bumptech.glide:compiler:4.12.0'
kapt 'com.github.bumptech.glide:compiler:4.13.0'
compileOnly rootProject.findProject(':streams_channel')
}

View file

@ -32,12 +32,12 @@ import deckers.thibault.aves.utils.UriUtils.tryParseId
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.util.PathUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.beyka.tiffbitmapfactory.TiffBitmapFactory
import java.io.IOException
import java.util.*
class DebugHandler(private val context: Context) : MethodCallHandler {
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
@ -81,7 +81,16 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
put("dataDir", context.dataDir)
}
}.mapValues { it.value?.path }
}.mapValues { it.value?.path }.toMutableMap()
// used by flutter plugin `path_provider`
dirs.putAll(
hashMapOf(
"flutter / cacheDir" to PathUtils.getCacheDirectory(context),
"flutter / dataDir" to PathUtils.getDataDirectory(context),
"flutter / filesDir" to PathUtils.getFilesDir(context),
)
)
result.success(dirs)
}

View file

@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.0'
classpath 'com.android.tools.build:gradle:7.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// GMS & Firebase Crashlytics are not actually used by all flavors
classpath 'com.google.gms:google-services:4.3.10'

View file

@ -0,0 +1,95 @@
import 'package:aves/model/covers.dart';
import 'package:aves/model/entry.dart';
import 'package:aves/model/favourites.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/metadata/address.dart';
import 'package:aves/model/metadata/catalog.dart';
import 'package:aves/model/video_playback.dart';
abstract class MetadataDb {
Future<void> init();
Future<int> dbFileSize();
Future<void> reset();
Future<void> removeIds(Set<int> contentIds, {Set<EntryDataType>? dataTypes});
// entries
Future<void> clearEntries();
Future<Set<AvesEntry>> loadAllEntries();
Future<void> saveEntries(Iterable<AvesEntry> entries);
Future<void> updateEntryId(int oldId, AvesEntry entry);
Future<Set<AvesEntry>> searchEntries(String query, {int? limit});
Future<Set<AvesEntry>> loadEntries(List<int> ids);
// date taken
Future<void> clearDates();
Future<Map<int?, int?>> loadDates();
// catalog metadata
Future<void> clearMetadataEntries();
Future<List<CatalogMetadata>> loadAllMetadataEntries();
Future<void> saveMetadata(Set<CatalogMetadata> metadataEntries);
Future<void> updateMetadataId(int oldId, CatalogMetadata? metadata);
// address
Future<void> clearAddresses();
Future<List<AddressDetails>> loadAllAddresses();
Future<void> saveAddresses(Set<AddressDetails> addresses);
Future<void> updateAddressId(int oldId, AddressDetails? address);
// favourites
Future<void> clearFavourites();
Future<Set<FavouriteRow>> loadAllFavourites();
Future<void> addFavourites(Iterable<FavouriteRow> rows);
Future<void> updateFavouriteId(int oldId, FavouriteRow row);
Future<void> removeFavourites(Iterable<FavouriteRow> rows);
// covers
Future<void> clearCovers();
Future<Set<CoverRow>> loadAllCovers();
Future<void> addCovers(Iterable<CoverRow> rows);
Future<void> updateCoverEntryId(int oldId, CoverRow row);
Future<void> removeCovers(Set<CollectionFilter> filters);
// video playback
Future<void> clearVideoPlayback();
Future<Set<VideoPlaybackRow>> loadAllVideoPlayback();
Future<VideoPlaybackRow?> loadVideoPlayback(int? contentId);
Future<void> addVideoPlayback(Set<VideoPlaybackRow> rows);
Future<void> updateVideoPlaybackId(int oldId, int? newId);
Future<void> removeVideoPlayback(Set<int> contentIds);
}

View file

@ -1,104 +1,19 @@
import 'dart:io';
import 'package:aves/model/covers.dart';
import 'package:aves/model/db/db_metadata.dart';
import 'package:aves/model/db/db_metadata_sqflite_upgrade.dart';
import 'package:aves/model/entry.dart';
import 'package:aves/model/favourites.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/metadata/address.dart';
import 'package:aves/model/metadata/catalog.dart';
import 'package:aves/model/metadata_db_upgrade.dart';
import 'package:aves/model/video_playback.dart';
import 'package:aves/services/common/services.dart';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:sqflite/sqflite.dart';
abstract class MetadataDb {
Future<void> init();
Future<int> dbFileSize();
Future<void> reset();
Future<void> removeIds(Set<int> contentIds, {Set<EntryDataType>? dataTypes});
// entries
Future<void> clearEntries();
Future<Set<AvesEntry>> loadAllEntries();
Future<void> saveEntries(Iterable<AvesEntry> entries);
Future<void> updateEntryId(int oldId, AvesEntry entry);
Future<Set<AvesEntry>> searchEntries(String query, {int? limit});
// date taken
Future<void> clearDates();
Future<Map<int?, int?>> loadDates();
// catalog metadata
Future<void> clearMetadataEntries();
Future<List<CatalogMetadata>> loadAllMetadataEntries();
Future<void> saveMetadata(Set<CatalogMetadata> metadataEntries);
Future<void> updateMetadataId(int oldId, CatalogMetadata? metadata);
// address
Future<void> clearAddresses();
Future<List<AddressDetails>> loadAllAddresses();
Future<void> saveAddresses(Set<AddressDetails> addresses);
Future<void> updateAddressId(int oldId, AddressDetails? address);
// favourites
Future<void> clearFavourites();
Future<Set<FavouriteRow>> loadAllFavourites();
Future<void> addFavourites(Iterable<FavouriteRow> rows);
Future<void> updateFavouriteId(int oldId, FavouriteRow row);
Future<void> removeFavourites(Iterable<FavouriteRow> rows);
// covers
Future<void> clearCovers();
Future<Set<CoverRow>> loadAllCovers();
Future<void> addCovers(Iterable<CoverRow> rows);
Future<void> updateCoverEntryId(int oldId, CoverRow row);
Future<void> removeCovers(Set<CollectionFilter> filters);
// video playback
Future<void> clearVideoPlayback();
Future<Set<VideoPlaybackRow>> loadAllVideoPlayback();
Future<VideoPlaybackRow?> loadVideoPlayback(int? contentId);
Future<void> addVideoPlayback(Set<VideoPlaybackRow> rows);
Future<void> updateVideoPlaybackId(int oldId, int? newId);
Future<void> removeVideoPlayback(Set<int> contentIds);
}
class SqfliteMetadataDb implements MetadataDb {
late Future<Database> _database;
@ -235,6 +150,24 @@ class SqfliteMetadataDb implements MetadataDb {
return entries;
}
@override
Future<Set<AvesEntry>> loadEntries(List<int> ids) async {
if (ids.isEmpty) return {};
final db = await _database;
final entries = <AvesEntry>{};
await Future.forEach(ids, (id) async {
final maps = await db.query(
entryTable,
where: 'contentId = ?',
whereArgs: [id],
);
if (maps.isNotEmpty) {
entries.add(AvesEntry.fromMap(maps.first));
}
});
return entries;
}
@override
Future<void> saveEntries(Iterable<AvesEntry> entries) async {
if (entries.isEmpty) return;

View file

@ -1,4 +1,4 @@
import 'package:aves/model/metadata_db.dart';
import 'package:aves/model/db/db_metadata_sqflite.dart';
import 'package:flutter/foundation.dart';
import 'package:sqflite/sqflite.dart';

View file

@ -1,7 +1,7 @@
import 'package:aves/l10n/l10n.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/settings/coordinate_format.dart';
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/enums/coordinate_format.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/utils/geo_utils.dart';

View file

@ -3,7 +3,7 @@ import 'package:aves/model/actions/entry_set_actions.dart';
import 'package:aves/model/actions/video_actions.dart';
import 'package:aves/model/filters/favourite.dart';
import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/source/enums.dart';
import 'package:aves/widgets/filter_grids/albums_page.dart';
import 'package:aves/widgets/filter_grids/countries_page.dart';
@ -106,4 +106,8 @@ class SettingsDefaults {
// file picker
static const filePickerShowHiddenFiles = false;
// platform settings
static const isRotationLocked = false;
static const areAnimationsRemoved = false;
}

View file

@ -7,15 +7,14 @@ import 'package:aves/model/actions/entry_set_actions.dart';
import 'package:aves/model/actions/video_actions.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/settings/defaults.dart';
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/map_style.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/map_style.dart';
import 'package:aves/model/source/enums.dart';
import 'package:aves/services/accessibility_service.dart';
import 'package:aves/services/common/services.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';
final Settings settings = Settings._private();
@ -25,8 +24,6 @@ class Settings extends ChangeNotifier {
Stream<String> get updateStream => _updateStreamController.stream;
static SharedPreferences? _prefs;
Settings._private();
static const Set<String> internalKeys = {
@ -34,6 +31,9 @@ class Settings extends ChangeNotifier {
catalogTimeZoneKey,
videoShowRawTimedTextKey,
searchHistoryKey,
platformAccelerometerRotationKey,
platformTransitionAnimationScaleKey,
topEntryIdsKey,
};
// app
@ -48,6 +48,7 @@ class Settings extends ChangeNotifier {
static const catalogTimeZoneKey = 'catalog_time_zone';
static const tileExtentPrefixKey = 'tile_extent_';
static const tileLayoutPrefixKey = 'tile_layout_';
static const topEntryIdsKey = 'top_entry_ids';
// drawer
static const drawerTypeBookmarksKey = 'drawer_type_bookmarks';
@ -124,16 +125,10 @@ class Settings extends ChangeNotifier {
// cf Android `Settings.Global.TRANSITION_ANIMATION_SCALE`
static const platformTransitionAnimationScaleKey = 'transition_animation_scale';
bool get initialized => _prefs != null;
bool get initialized => settingsStore.initialized;
Future<void> init({
required bool monitorPlatformSettings,
bool isRotationLocked = false,
bool areAnimationsRemoved = false,
}) async {
_prefs = await SharedPreferences.getInstance();
_isRotationLocked = isRotationLocked;
_areAnimationsRemoved = areAnimationsRemoved;
Future<void> init({required bool monitorPlatformSettings}) async {
await settingsStore.init();
if (monitorPlatformSettings) {
_platformSettingsChangeChannel.receiveBroadcastStream().listen((event) => _onPlatformSettingsChange(event as Map?));
}
@ -141,9 +136,9 @@ class Settings extends ChangeNotifier {
Future<void> reset({required bool includeInternalKeys}) async {
if (includeInternalKeys) {
await _prefs!.clear();
await settingsStore.clear();
} else {
await Future.forEach<String>(_prefs!.getKeys().whereNot(internalKeys.contains), _prefs!.remove);
await Future.forEach<String>(settingsStore.getKeys().whereNot(Settings.internalKeys.contains), settingsStore.remove);
}
}
@ -189,7 +184,7 @@ class Settings extends ChangeNotifier {
Locale? get locale {
// exceptionally allow getting locale before settings are initialized
final tag = _prefs?.getString(localeKey);
final tag = initialized ? getString(localeKey) : null;
if (tag != null) {
final codes = tag.split(localeSeparator);
return Locale.fromSubtags(
@ -250,11 +245,11 @@ class Settings extends ChangeNotifier {
set homePage(HomePageSetting newValue) => setAndNotify(homePageKey, newValue.toString());
String get catalogTimeZone => _prefs!.getString(catalogTimeZoneKey) ?? '';
String get catalogTimeZone => getString(catalogTimeZoneKey) ?? '';
set catalogTimeZone(String newValue) => setAndNotify(catalogTimeZoneKey, newValue);
double getTileExtent(String routeName) => _prefs!.getDouble(tileExtentPrefixKey + routeName) ?? 0;
double getTileExtent(String routeName) => getDouble(tileExtentPrefixKey + routeName) ?? 0;
void setTileExtent(String routeName, double newValue) => setAndNotify(tileExtentPrefixKey + routeName, newValue);
@ -262,10 +257,14 @@ class Settings extends ChangeNotifier {
void setTileLayout(String routeName, TileLayout newValue) => setAndNotify(tileLayoutPrefixKey + routeName, newValue.toString());
List<int>? get topEntryIds => getStringList(topEntryIdsKey)?.map(int.tryParse).whereNotNull().toList();
set topEntryIds(List<int>? newValue) => setAndNotify(topEntryIdsKey, newValue?.map((id) => id.toString()).whereNotNull().toList());
// drawer
List<CollectionFilter?> get drawerTypeBookmarks =>
(_prefs!.getStringList(drawerTypeBookmarksKey))?.map((v) {
(getStringList(drawerTypeBookmarksKey))?.map((v) {
if (v.isEmpty) return null;
return CollectionFilter.fromJson(v);
}).toList() ??
@ -273,11 +272,11 @@ class Settings extends ChangeNotifier {
set drawerTypeBookmarks(List<CollectionFilter?> newValue) => setAndNotify(drawerTypeBookmarksKey, newValue.map((filter) => filter?.toJson() ?? '').toList());
List<String>? get drawerAlbumBookmarks => _prefs!.getStringList(drawerAlbumBookmarksKey);
List<String>? get drawerAlbumBookmarks => getStringList(drawerAlbumBookmarksKey);
set drawerAlbumBookmarks(List<String>? newValue) => setAndNotify(drawerAlbumBookmarksKey, newValue);
List<String> get drawerPageBookmarks => _prefs!.getStringList(drawerPageBookmarksKey) ?? SettingsDefaults.drawerPageBookmarks;
List<String> get drawerPageBookmarks => getStringList(drawerPageBookmarksKey) ?? SettingsDefaults.drawerPageBookmarks;
set drawerPageBookmarks(List<String> newValue) => setAndNotify(drawerPageBookmarksKey, newValue);
@ -341,11 +340,11 @@ class Settings extends ChangeNotifier {
set tagSortFactor(ChipSortFactor newValue) => setAndNotify(tagSortFactorKey, newValue.toString());
Set<CollectionFilter> get pinnedFilters => (_prefs!.getStringList(pinnedFiltersKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet();
Set<CollectionFilter> get pinnedFilters => (getStringList(pinnedFiltersKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet();
set pinnedFilters(Set<CollectionFilter> newValue) => setAndNotify(pinnedFiltersKey, newValue.map((filter) => filter.toJson()).toList());
Set<CollectionFilter> get hiddenFilters => (_prefs!.getStringList(hiddenFiltersKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet();
Set<CollectionFilter> get hiddenFilters => (getStringList(hiddenFiltersKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet();
set hiddenFilters(Set<CollectionFilter> newValue) => setAndNotify(hiddenFiltersKey, newValue.map((filter) => filter.toJson()).toList());
@ -415,7 +414,7 @@ class Settings extends ChangeNotifier {
// subtitles
double get subtitleFontSize => _prefs!.getDouble(subtitleFontSizeKey) ?? SettingsDefaults.subtitleFontSize;
double get subtitleFontSize => getDouble(subtitleFontSizeKey) ?? SettingsDefaults.subtitleFontSize;
set subtitleFontSize(double newValue) => setAndNotify(subtitleFontSizeKey, newValue);
@ -427,11 +426,11 @@ class Settings extends ChangeNotifier {
set subtitleShowOutline(bool newValue) => setAndNotify(subtitleShowOutlineKey, newValue);
Color get subtitleTextColor => Color(_prefs!.getInt(subtitleTextColorKey) ?? SettingsDefaults.subtitleTextColor.value);
Color get subtitleTextColor => Color(getInt(subtitleTextColorKey) ?? SettingsDefaults.subtitleTextColor.value);
set subtitleTextColor(Color newValue) => setAndNotify(subtitleTextColorKey, newValue.value);
Color get subtitleBackgroundColor => Color(_prefs!.getInt(subtitleBackgroundColorKey) ?? SettingsDefaults.subtitleBackgroundColor.value);
Color get subtitleBackgroundColor => Color(getInt(subtitleBackgroundColorKey) ?? SettingsDefaults.subtitleBackgroundColor.value);
set subtitleBackgroundColor(Color newValue) => setAndNotify(subtitleBackgroundColorKey, newValue.value);
@ -441,7 +440,7 @@ class Settings extends ChangeNotifier {
set infoMapStyle(EntryMapStyle newValue) => setAndNotify(infoMapStyleKey, newValue.toString());
double get infoMapZoom => _prefs!.getDouble(infoMapZoomKey) ?? SettingsDefaults.infoMapZoom;
double get infoMapZoom => getDouble(infoMapZoomKey) ?? SettingsDefaults.infoMapZoom;
set infoMapZoom(double newValue) => setAndNotify(infoMapZoomKey, newValue);
@ -459,7 +458,7 @@ class Settings extends ChangeNotifier {
set saveSearchHistory(bool newValue) => setAndNotify(saveSearchHistoryKey, newValue);
List<CollectionFilter> get searchHistory => (_prefs!.getStringList(searchHistoryKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toList();
List<CollectionFilter> get searchHistory => (getStringList(searchHistoryKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toList();
set searchHistory(List<CollectionFilter> newValue) => setAndNotify(searchHistoryKey, newValue.map((filter) => filter.toJson()).toList());
@ -481,11 +480,19 @@ class Settings extends ChangeNotifier {
// convenience methods
int? getInt(String key) => settingsStore.getInt(key);
double? getDouble(String key) => settingsStore.getDouble(key);
String? getString(String key) => settingsStore.getString(key);
List<String>? getStringList(String key) => settingsStore.getStringList(key);
// ignore: avoid_positional_boolean_parameters
bool getBoolOrDefault(String key, bool defaultValue) => _prefs!.getBool(key) ?? defaultValue;
bool getBoolOrDefault(String key, bool defaultValue) => settingsStore.getBool(key) ?? defaultValue;
T getEnumOrDefault<T>(String key, T defaultValue, Iterable<T> values) {
final valueString = _prefs!.getString(key);
final valueString = settingsStore.getString(key);
for (final v in values) {
if (v.toString() == valueString) {
return v;
@ -495,28 +502,28 @@ class Settings extends ChangeNotifier {
}
List<T> getEnumListOrDefault<T extends Object>(String key, List<T> defaultValue, Iterable<T> values) {
return _prefs!.getStringList(key)?.map((s) => values.firstWhereOrNull((v) => v.toString() == s)).whereNotNull().toList() ?? defaultValue;
return settingsStore.getStringList(key)?.map((s) => values.firstWhereOrNull((v) => v.toString() == s)).whereNotNull().toList() ?? defaultValue;
}
void setAndNotify(String key, dynamic newValue) {
var oldValue = _prefs!.get(key);
var oldValue = settingsStore.get(key);
if (newValue == null) {
_prefs!.remove(key);
settingsStore.remove(key);
} else if (newValue is String) {
oldValue = _prefs!.getString(key);
_prefs!.setString(key, newValue);
oldValue = settingsStore.getString(key);
settingsStore.setString(key, newValue);
} else if (newValue is List<String>) {
oldValue = _prefs!.getStringList(key);
_prefs!.setStringList(key, newValue);
oldValue = settingsStore.getStringList(key);
settingsStore.setStringList(key, newValue);
} else if (newValue is int) {
oldValue = _prefs!.getInt(key);
_prefs!.setInt(key, newValue);
oldValue = settingsStore.getInt(key);
settingsStore.setInt(key, newValue);
} else if (newValue is double) {
oldValue = _prefs!.getDouble(key);
_prefs!.setDouble(key, newValue);
oldValue = settingsStore.getDouble(key);
settingsStore.setDouble(key, newValue);
} else if (newValue is bool) {
oldValue = _prefs!.getBool(key);
_prefs!.setBool(key, newValue);
oldValue = settingsStore.getBool(key);
settingsStore.setBool(key, newValue);
}
if (oldValue != newValue) {
_updateStreamController.add(key);
@ -527,50 +534,33 @@ class Settings extends ChangeNotifier {
// platform settings
void _onPlatformSettingsChange(Map? fields) {
var changed = false;
fields?.forEach((key, value) {
switch (key) {
case platformAccelerometerRotationKey:
if (value is num) {
final newValue = value == 0;
if (_isRotationLocked != newValue) {
_isRotationLocked = newValue;
if (!_isRotationLocked) {
windowService.requestOrientation();
}
_updateStreamController.add(key);
changed = true;
}
isRotationLocked = value == 0;
}
break;
case platformTransitionAnimationScaleKey:
if (value is num) {
final newValue = value == 0;
if (_areAnimationsRemoved != newValue) {
_areAnimationsRemoved = newValue;
_updateStreamController.add(key);
changed = true;
}
areAnimationsRemoved = value == 0;
}
}
});
if (changed) {
notifyListeners();
}
}
bool _isRotationLocked = false;
bool get isRotationLocked => getBoolOrDefault(platformAccelerometerRotationKey, SettingsDefaults.isRotationLocked);
bool get isRotationLocked => _isRotationLocked;
set isRotationLocked(bool newValue) => setAndNotify(platformAccelerometerRotationKey, newValue);
bool _areAnimationsRemoved = false;
bool get areAnimationsRemoved => getBoolOrDefault(platformTransitionAnimationScaleKey, SettingsDefaults.areAnimationsRemoved);
bool get areAnimationsRemoved => _areAnimationsRemoved;
set areAnimationsRemoved(bool newValue) => setAndNotify(platformTransitionAnimationScaleKey, newValue);
// import/export
Map<String, dynamic> export() => Map.fromEntries(
_prefs!.getKeys().whereNot(internalKeys.contains).map((k) => MapEntry(k, _prefs!.get(k))),
settingsStore.getKeys().whereNot(internalKeys.contains).map((k) => MapEntry(k, settingsStore.get(k))),
);
Future<void> import(dynamic jsonMap) async {
@ -581,16 +571,16 @@ class Settings extends ChangeNotifier {
// apply user modifications
jsonMap.forEach((key, value) {
if (value == null) {
_prefs!.remove(key);
settingsStore.remove(key);
} else if (key.startsWith(tileExtentPrefixKey)) {
if (value is double) {
_prefs!.setDouble(key, value);
settingsStore.setDouble(key, value);
} else {
debugPrint('failed to import key=$key, value=$value is not a double');
}
} else if (key.startsWith(tileLayoutPrefixKey)) {
if (value is String) {
_prefs!.setString(key, value);
settingsStore.setString(key, value);
} else {
debugPrint('failed to import key=$key, value=$value is not a string');
}
@ -599,7 +589,7 @@ class Settings extends ChangeNotifier {
case subtitleTextColorKey:
case subtitleBackgroundColorKey:
if (value is int) {
_prefs!.setInt(key, value);
settingsStore.setInt(key, value);
} else {
debugPrint('failed to import key=$key, value=$value is not an int');
}
@ -607,7 +597,7 @@ class Settings extends ChangeNotifier {
case subtitleFontSizeKey:
case infoMapZoomKey:
if (value is double) {
_prefs!.setDouble(key, value);
settingsStore.setDouble(key, value);
} else {
debugPrint('failed to import key=$key, value=$value is not a double');
}
@ -635,7 +625,7 @@ class Settings extends ChangeNotifier {
case saveSearchHistoryKey:
case filePickerShowHiddenFilesKey:
if (value is bool) {
_prefs!.setBool(key, value);
settingsStore.setBool(key, value);
} else {
debugPrint('failed to import key=$key, value=$value is not a bool');
}
@ -658,7 +648,7 @@ class Settings extends ChangeNotifier {
case accessibilityAnimationsKey:
case timeToTakeActionKey:
if (value is String) {
_prefs!.setString(key, value);
settingsStore.setString(key, value);
} else {
debugPrint('failed to import key=$key, value=$value is not a string');
}
@ -673,7 +663,7 @@ class Settings extends ChangeNotifier {
case viewerQuickActionsKey:
case videoQuickActionsKey:
if (value is List) {
_prefs!.setStringList(key, value.cast<String>());
settingsStore.setStringList(key, value.cast<String>());
} else {
debugPrint('failed to import key=$key, value=$value is not a list');
}

View file

@ -0,0 +1,37 @@
abstract class SettingsStore {
bool get initialized;
Future<void> init();
Future<bool> clear();
Future<bool> remove(String key);
// get
Set<String> getKeys();
Object? get(String key);
bool? getBool(String key);
int? getInt(String key);
double? getDouble(String key);
String? getString(String key);
List<String>? getStringList(String key);
// set
Future<bool> setBool(String key, bool value);
Future<bool> setInt(String key, int value);
Future<bool> setDouble(String key, double value);
Future<bool> setString(String key, String value);
Future<bool> setStringList(String key, List<String> value);
}

View file

@ -0,0 +1,60 @@
import 'package:aves/model/settings/store/store.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SharedPrefSettingsStore implements SettingsStore {
static SharedPreferences? _prefs;
@override
bool get initialized => _prefs != null;
@override
Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
}
@override
Future<bool> clear() => _prefs!.clear();
@override
Future<bool> remove(String key) => _prefs!.remove(key);
// get
@override
Set<String> getKeys() => _prefs!.getKeys();
@override
Object? get(String key) => _prefs!.get(key);
@override
bool? getBool(String key) => _prefs!.getBool(key);
@override
int? getInt(String key) => _prefs!.getInt(key);
@override
double? getDouble(String key) => _prefs!.getDouble(key);
@override
String? getString(String key) => _prefs!.getString(key);
@override
List<String>? getStringList(String key) => _prefs!.getStringList(key);
// set
@override
Future<bool> setBool(String key, bool value) => _prefs!.setBool(key, value);
@override
Future<bool> setInt(String key, int value) => _prefs!.setInt(key, value);
@override
Future<bool> setDouble(String key, double value) => _prefs!.setDouble(key, value);
@override
Future<bool> setString(String key, String value) => _prefs!.setString(key, value);
@override
Future<bool> setStringList(String key, List<String> value) => _prefs!.setStringList(key, value);
}

View file

@ -26,56 +26,9 @@ mixin AlbumMixin on SourceBase {
return compareAsciiUpperCase(va, vb);
}
void _notifyAlbumChange() => eventBus.fire(AlbumsChangedEvent());
String getAlbumDisplayName(BuildContext? context, String dirPath) {
final separator = pContext.separator;
assert(!dirPath.endsWith(separator));
if (context != null) {
final type = androidFileUtils.getAlbumType(dirPath);
if (type == AlbumType.camera) return context.l10n.albumCamera;
if (type == AlbumType.download) return context.l10n.albumDownload;
if (type == AlbumType.screenshots) return context.l10n.albumScreenshots;
if (type == AlbumType.screenRecordings) return context.l10n.albumScreenRecordings;
if (type == AlbumType.videoCaptures) return context.l10n.albumVideoCaptures;
}
final dir = VolumeRelativeDirectory.fromPath(dirPath);
if (dir == null) return dirPath;
final relativeDir = dir.relativeDir;
if (relativeDir.isEmpty) {
final volume = androidFileUtils.getStorageVolume(dirPath)!;
return volume.getDescription(context);
}
String unique(String dirPath, Set<String?> others) {
final parts = pContext.split(dirPath);
for (var i = parts.length - 1; i > 0; i--) {
final name = pContext.joinAll(['', ...parts.skip(i)]);
final testName = '$separator$name';
if (others.every((item) => !item!.endsWith(testName))) return name;
}
return dirPath;
}
final otherAlbumsOnDevice = _directories.where((item) => item != dirPath).toSet();
final uniqueNameInDevice = unique(dirPath, otherAlbumsOnDevice);
if (uniqueNameInDevice.length <= relativeDir.length) {
return uniqueNameInDevice;
}
final volumePath = dir.volumePath;
String trimVolumePath(String? path) => path!.substring(dir.volumePath.length);
final otherAlbumsOnVolume = otherAlbumsOnDevice.where((path) => path!.startsWith(volumePath)).map(trimVolumePath).toSet();
final uniqueNameInVolume = unique(trimVolumePath(dirPath), otherAlbumsOnVolume);
final volume = androidFileUtils.getStorageVolume(dirPath)!;
if (volume.isPrimary) {
return uniqueNameInVolume;
} else {
return '$uniqueNameInVolume (${volume.getDescription(context)})';
}
void _onAlbumChanged() {
invalidateAlbumDisplayNames();
eventBus.fire(AlbumsChangedEvent());
}
Map<String, AvesEntry?> getAlbumEntries() {
@ -109,7 +62,7 @@ mixin AlbumMixin on SourceBase {
void addDirectories(Set<String?> albums) {
if (!_directories.containsAll(albums)) {
_directories.addAll(albums);
_notifyAlbumChange();
_onAlbumChanged();
}
}
@ -117,7 +70,7 @@ mixin AlbumMixin on SourceBase {
final emptyAlbums = (albums ?? _directories).where((v) => _isEmptyAlbum(v) && !_newAlbums.contains(v)).toSet();
if (emptyAlbums.isNotEmpty) {
_directories.removeAll(emptyAlbums);
_notifyAlbumChange();
_onAlbumChanged();
invalidateAlbumFilterSummary(directories: emptyAlbums);
final bookmarks = settings.drawerAlbumBookmarks;
@ -165,6 +118,8 @@ mixin AlbumMixin on SourceBase {
AvesEntry? albumRecentEntry(AlbumFilter filter) {
return _filterRecentEntryMap.putIfAbsent(filter.album, () => sortedEntriesByDate.firstWhereOrNull(filter.test));
}
// new albums
void createAlbum(String directory) {
_newAlbums.add(directory);
@ -181,6 +136,80 @@ mixin AlbumMixin on SourceBase {
void forgetNewAlbums(Set<String> directories) {
_newAlbums.removeAll(directories);
}
// display names
final Map<String, String> _albumDisplayNamesWithContext = {}, _albumDisplayNamesWithoutContext = {};
void invalidateAlbumDisplayNames() {
_albumDisplayNamesWithContext.clear();
_albumDisplayNamesWithoutContext.clear();
}
String _computeDisplayName(BuildContext? context, String dirPath) {
final separator = pContext.separator;
assert(!dirPath.endsWith(separator));
if (context != null) {
final type = androidFileUtils.getAlbumType(dirPath);
switch (type) {
case AlbumType.camera:
return context.l10n.albumCamera;
case AlbumType.download:
return context.l10n.albumDownload;
case AlbumType.screenshots:
return context.l10n.albumScreenshots;
case AlbumType.screenRecordings:
return context.l10n.albumScreenRecordings;
case AlbumType.videoCaptures:
return context.l10n.albumVideoCaptures;
case AlbumType.regular:
case AlbumType.app:
break;
}
}
final dir = VolumeRelativeDirectory.fromPath(dirPath);
if (dir == null) return dirPath;
final relativeDir = dir.relativeDir;
if (relativeDir.isEmpty) {
final volume = androidFileUtils.getStorageVolume(dirPath)!;
return volume.getDescription(context);
}
String unique(String dirPath, Set<String?> others) {
final parts = pContext.split(dirPath);
for (var i = parts.length - 1; i > 0; i--) {
final name = pContext.joinAll(['', ...parts.skip(i)]);
final testName = '$separator$name';
if (others.every((item) => !item!.endsWith(testName))) return name;
}
return dirPath;
}
final otherAlbumsOnDevice = _directories.where((item) => item != dirPath).toSet();
final uniqueNameInDevice = unique(dirPath, otherAlbumsOnDevice);
if (uniqueNameInDevice.length <= relativeDir.length) {
return uniqueNameInDevice;
}
final volumePath = dir.volumePath;
String trimVolumePath(String? path) => path!.substring(dir.volumePath.length);
final otherAlbumsOnVolume = otherAlbumsOnDevice.where((path) => path!.startsWith(volumePath)).map(trimVolumePath).toSet();
final uniqueNameInVolume = unique(trimVolumePath(dirPath), otherAlbumsOnVolume);
final volume = androidFileUtils.getStorageVolume(dirPath)!;
if (volume.isPrimary) {
return uniqueNameInVolume;
} else {
return '$uniqueNameInVolume (${volume.getDescription(context)})';
}
}
String getAlbumDisplayName(BuildContext? context, String dirPath) {
final names = (context != null ? _albumDisplayNamesWithContext : _albumDisplayNamesWithoutContext);
return names.putIfAbsent(dirPath, () => _computeDisplayName(context, dirPath));
}
}
class AlbumsChangedEvent {}

View file

@ -68,7 +68,12 @@ class CollectionLens with ChangeNotifier {
}));
favourites.addListener(_onFavouritesChanged);
}
settings.addListener(_onSettingsChanged);
_subscriptions.add(settings.updateStream
.where([
Settings.collectionSortFactorKey,
Settings.collectionGroupFactorKey,
].contains)
.listen((_) => _onSettingsChanged()));
_refresh();
}
@ -78,7 +83,6 @@ class CollectionLens with ChangeNotifier {
..forEach((sub) => sub.cancel())
..clear();
favourites.removeListener(_onFavouritesChanged);
settings.removeListener(_onSettingsChanged);
super.dispose();
}

View file

@ -39,6 +39,10 @@ mixin SourceBase {
}
abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagMixin {
CollectionSource() {
settings.updateStream.where((key) => key == Settings.localeKey).listen((_) => invalidateAlbumDisplayNames());
}
final EventBus _eventBus = EventBus();
@override

View file

@ -50,6 +50,16 @@ class MediaStoreSource extends CollectionSource {
stateNotifier.value = SourceState.loading;
clearEntries();
final topIds = settings.topEntryIds;
late final Set<AvesEntry> topEntries;
if (topIds != null) {
debugPrint('$runtimeType refresh ${stopwatch.elapsed} load ${topIds.length} top entries');
topEntries = await metadataDb.loadEntries(topIds);
addEntries(topEntries);
} else {
topEntries = {};
}
debugPrint('$runtimeType refresh ${stopwatch.elapsed} fetch known entries');
final oldEntries = await metadataDb.loadAllEntries();
debugPrint('$runtimeType refresh ${stopwatch.elapsed} check obsolete entries');
@ -57,6 +67,11 @@ class MediaStoreSource extends CollectionSource {
final obsoleteContentIds = (await mediaStoreService.checkObsoleteContentIds(knownDateById.keys.toList())).toSet();
oldEntries.removeWhere((entry) => obsoleteContentIds.contains(entry.contentId));
if (topEntries.isNotEmpty) {
final obsoleteTopEntries = topEntries.where((entry) => obsoleteContentIds.contains(entry.contentId));
await removeEntries(obsoleteTopEntries.map((entry) => entry.uri).toSet());
}
// show known entries
debugPrint('$runtimeType refresh ${stopwatch.elapsed} add known entries');
addEntries(oldEntries);

View file

@ -1,5 +1,8 @@
import 'package:aves/model/availability.dart';
import 'package:aves/model/metadata_db.dart';
import 'package:aves/model/db/db_metadata.dart';
import 'package:aves/model/db/db_metadata_sqflite.dart';
import 'package:aves/model/settings/store/store.dart';
import 'package:aves/model/settings/store/store_shared_pref.dart';
import 'package:aves/services/android_app_service.dart';
import 'package:aves/services/device_service.dart';
import 'package:aves/services/media/embedded_data_service.dart';
@ -19,6 +22,7 @@ final getIt = GetIt.instance;
final p.Context pContext = getIt<p.Context>();
final AvesAvailability availability = getIt<AvesAvailability>();
final MetadataDb metadataDb = getIt<MetadataDb>();
final SettingsStore settingsStore = getIt<SettingsStore>();
final AndroidAppService androidAppService = getIt<AndroidAppService>();
final DeviceService deviceService = getIt<DeviceService>();
@ -35,6 +39,7 @@ void initPlatformServices() {
getIt.registerLazySingleton<p.Context>(p.Context.new);
getIt.registerLazySingleton<AvesAvailability>(LiveAvesAvailability.new);
getIt.registerLazySingleton<MetadataDb>(SqfliteMetadataDb.new);
getIt.registerLazySingleton<SettingsStore>(SharedPrefSettingsStore.new);
getIt.registerLazySingleton<AndroidAppService>(PlatformAndroidAppService.new);
getIt.registerLazySingleton<DeviceService>(PlatformDeviceService.new);

View file

@ -1,4 +1,4 @@
import 'package:aves/model/settings/accessibility_animations.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';

View file

@ -1,12 +1,14 @@
import 'dart:async';
import 'dart:ui';
import 'package:aves/app_flavor.dart';
import 'package:aves/app_mode.dart';
import 'package:aves/l10n/l10n.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/settings/accessibility_animations.dart';
import 'package:aves/model/settings/screen_on.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/enums/screen_on.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/collection_source.dart';
import 'package:aves/model/source/media_store_source.dart';
import 'package:aves/services/accessibility_service.dart';
@ -16,12 +18,15 @@ import 'package:aves/theme/icons.dart';
import 'package:aves/theme/themes.dart';
import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/utils/debouncer.dart';
import 'package:aves/widgets/collection/collection_grid.dart';
import 'package:aves/widgets/collection/collection_page.dart';
import 'package:aves/widgets/common/behaviour/route_tracker.dart';
import 'package:aves/widgets/common/behaviour/routes.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/providers/highlight_info_provider.dart';
import 'package:aves/widgets/home_page.dart';
import 'package:aves/widgets/welcome_page.dart';
import 'package:collection/collection.dart';
import 'package:equatable/equatable.dart';
import 'package:fijkplayer/fijkplayer.dart';
import 'package:flutter/foundation.dart';
@ -43,7 +48,7 @@ class AvesApp extends StatefulWidget {
_AvesAppState createState() => _AvesAppState();
}
class _AvesAppState extends State<AvesApp> {
class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
final ValueNotifier<AppMode> appModeNotifier = ValueNotifier(AppMode.main);
late Future<void> _appSetup;
final _mediaStoreSource = MediaStoreSource();
@ -70,6 +75,7 @@ class _AvesAppState extends State<AvesApp> {
_newIntentChannel.receiveBroadcastStream().listen((event) => _onNewIntent(event as Map?));
_analysisCompletionChannel.receiveBroadcastStream().listen((event) => _onAnalysisCompletion());
_errorChannel.receiveBroadcastStream().listen((event) => _onError(event as String?));
WidgetsBinding.instance!.addObserver(this);
}
@override
@ -158,26 +164,45 @@ class _AvesAppState extends State<AvesApp> {
);
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
debugPrint('$runtimeType lifecycle ${state.name}');
switch (state) {
case AppLifecycleState.inactive:
_saveTopEntries();
break;
case AppLifecycleState.paused:
case AppLifecycleState.detached:
case AppLifecycleState.resumed:
break;
}
}
// save IDs of entries visible at the top of the collection page with current layout settings
void _saveTopEntries() {
final stopwatch = Stopwatch()..start();
final screenSize = window.physicalSize / window.devicePixelRatio;
var tileExtent = settings.getTileExtent(CollectionPage.routeName);
if (tileExtent == 0) {
tileExtent = screenSize.shortestSide / CollectionGrid.columnCountDefault;
}
final rows = (screenSize.height / tileExtent).ceil();
final columns = (screenSize.width / tileExtent).ceil();
final count = rows * columns;
final collection = CollectionLens(source: _mediaStoreSource, listenToSource: false);
settings.topEntryIds = collection.sortedEntries.take(count).map((entry) => entry.contentId).whereNotNull().toList();
collection.dispose();
debugPrint('Saved $count top entries in ${stopwatch.elapsed.inMilliseconds}ms');
}
// setup before the first page is displayed. keep it short
Future<void> _setup() async {
final stopwatch = Stopwatch()..start();
// TODO TLAD [init] init settings/device w/o platform calls (first platform channel call takes ~800ms):
// 1) use cached values if any,
// 2a) call platform w/ delay if cached
// 2b) call platform w/o delay if not cached
// 3) cache platform call results across app restarts
await device.init();
final isRotationLocked = await windowService.isRotationLocked();
final areAnimationsRemoved = await AccessibilityService.areAnimationsRemoved();
// TODO TLAD [init] migrate settings away from `shared_preferences` to a platform-free solution
await settings.init(
monitorPlatformSettings: true,
isRotationLocked: isRotationLocked,
areAnimationsRemoved: areAnimationsRemoved,
);
await settings.init(monitorPlatformSettings: true);
settings.isRotationLocked = await windowService.isRotationLocked();
settings.areAnimationsRemoved = await AccessibilityService.areAnimationsRemoved();
_monitorSettings();
FijkLog.setLevel(FijkLogLevel.Warn);
@ -187,22 +212,30 @@ class _AvesAppState extends State<AvesApp> {
}
void _monitorSettings() {
// keep screen on
settings.updateStream.where((key) => key == Settings.keepScreenOnKey).listen(
(_) => settings.keepScreenOn.apply(),
);
settings.keepScreenOn.apply();
void applyIsInstalledAppAccessAllowed() {
if (settings.isInstalledAppAccessAllowed) {
androidFileUtils.initAppNames();
} else {
androidFileUtils.resetAppNames();
}
}
// installed app access
settings.updateStream.where((key) => key == Settings.isInstalledAppAccessAllowedKey).listen(
(_) {
if (settings.isInstalledAppAccessAllowed) {
androidFileUtils.initAppNames();
} else {
androidFileUtils.resetAppNames();
}
},
);
void applyKeepScreenOn() {
settings.keepScreenOn.apply();
}
void applyIsRotationLocked() {
if (!settings.isRotationLocked) {
windowService.requestOrientation();
}
}
settings.updateStream.where((key) => key == Settings.isInstalledAppAccessAllowedKey).listen((_) => applyIsInstalledAppAccessAllowed());
settings.updateStream.where((key) => key == Settings.keepScreenOnKey).listen((_) => applyKeepScreenOn());
settings.updateStream.where((key) => key == Settings.platformAccelerometerRotationKey).listen((_) => applyIsRotationLocked());
applyKeepScreenOn();
applyIsRotationLocked();
}
Future<void> _setupErrorReporting() async {

View file

@ -39,6 +39,11 @@ import 'package:tuple/tuple.dart';
class CollectionGrid extends StatefulWidget {
final String? settingsRouteKey;
static const int columnCountDefault = 4;
static const double extentMin = 46;
static const double extentMax = 300;
static const double spacing = 2;
const CollectionGrid({
Key? key,
this.settingsRouteKey,
@ -61,9 +66,10 @@ class _CollectionGridState extends State<CollectionGrid> {
Widget build(BuildContext context) {
_tileExtentController ??= TileExtentController(
settingsRouteKey: widget.settingsRouteKey ?? context.currentRouteName!,
columnCountDefault: 4,
extentMin: 46,
spacing: 2,
columnCountDefault: CollectionGrid.columnCountDefault,
extentMin: CollectionGrid.extentMin,
extentMax: CollectionGrid.extentMax,
spacing: CollectionGrid.spacing,
);
return TileExtentControllerProvider(
controller: _tileExtentController!,

View file

@ -1,5 +1,5 @@
import 'package:aves/model/entry.dart';
import 'package:aves/model/settings/coordinate_format.dart';
import 'package:aves/model/settings/enums/coordinate_format.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/format.dart';
import 'package:aves/theme/icons.dart';

View file

@ -1,8 +1,8 @@
import 'dart:async';
import 'dart:math';
import 'package:aves/model/settings/accessibility_animations.dart';
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/accessibility_service.dart';
import 'package:aves/theme/durations.dart';

View file

@ -4,7 +4,7 @@ import 'package:aves/model/filters/album.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/filters/location.dart';
import 'package:aves/model/filters/tag.dart';
import 'package:aves/model/settings/accessibility_animations.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';

View file

@ -1,4 +1,4 @@
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/viewer/info/common.dart';
import 'package:flutter/material.dart';

View file

@ -1,6 +1,6 @@
import 'package:aves/model/filters/coordinate.dart';
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/map_style.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/map_style.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/theme/durations.dart';

View file

@ -2,8 +2,8 @@ import 'dart:async';
import 'dart:math';
import 'package:aves/model/entry.dart';
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/map_style.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/map_style.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/utils/change_notifier.dart';

View file

@ -2,7 +2,7 @@ import 'dart:async';
import 'dart:typed_data';
import 'package:aves/model/entry_images.dart';
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/utils/change_notifier.dart';
import 'package:aves/widgets/common/map/buttons.dart';
import 'package:aves/widgets/common/map/controller.dart';

View file

@ -1,6 +1,6 @@
import 'dart:async';
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/utils/debouncer.dart';

View file

@ -1,4 +1,4 @@
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/widgets/common/basic/outlined_text.dart';
import 'package:aves/widgets/common/map/leaflet/scalebar_utils.dart';
import 'package:flutter/material.dart';

View file

@ -4,9 +4,9 @@ import 'dart:ui';
import 'package:aves/image_providers/thumbnail_provider.dart';
import 'package:aves/model/entry.dart';
import 'package:aves/model/entry_images.dart';
import 'package:aves/model/settings/accessibility_animations.dart';
import 'package:aves/model/settings/entry_background.dart';
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/enums/entry_background.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/widgets/common/fx/checkered_decoration.dart';

View file

@ -20,7 +20,7 @@ class TileExtentController {
this.columnCountMin = 2,
required this.columnCountDefault,
required this.extentMin,
this.extentMax = 300,
required this.extentMax,
required this.spacing,
}) {
userPreferredExtent = settings.getTileExtent(settingsRouteKey);

View file

@ -68,6 +68,7 @@ class DebugSettingsSection extends StatelessWidget {
'searchHistory': toMultiline(settings.searchHistory),
'locale': '${settings.locale}',
'systemLocales': '${WidgetsBinding.instance!.window.locales}',
'topEntryIds': '${settings.topEntryIds}',
},
),
),

View file

@ -1,7 +1,7 @@
import 'dart:async';
import 'package:aves/model/settings/coordinate_format.dart';
import 'package:aves/model/settings/map_style.dart';
import 'package:aves/model/settings/enums/coordinate_format.dart';
import 'package:aves/model/settings/enums/map_style.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/services/common/services.dart';

View file

@ -164,6 +164,7 @@ class _FilterGridState<T extends CollectionFilter> extends State<FilterGrid<T>>
settingsRouteKey: widget.settingsRouteKey ?? context.currentRouteName!,
columnCountDefault: 3,
extentMin: 60,
extentMax: 300,
spacing: 8,
);
return TileExtentControllerProvider(

View file

@ -4,7 +4,7 @@ import 'package:aves/app_mode.dart';
import 'package:aves/model/entry.dart';
import 'package:aves/model/filters/album.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/settings/home_page.dart';
import 'package:aves/model/settings/enums/home_page.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/collection_source.dart';

View file

@ -1,5 +1,5 @@
import 'package:aves/model/entry.dart';
import 'package:aves/model/settings/coordinate_format.dart';
import 'package:aves/model/settings/enums/coordinate_format.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/services/geocoding_service.dart';

View file

@ -4,8 +4,8 @@ import 'package:aves/model/entry.dart';
import 'package:aves/model/filters/coordinate.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/highlight.dart';
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/map_style.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/map_style.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/theme/durations.dart';

View file

@ -1,5 +1,5 @@
import 'package:aves/model/settings/accessibility_animations.dart';
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';

View file

@ -1,5 +1,5 @@
import 'package:aves/model/settings/accessibility_timeout.dart';
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/enums/accessibility_timeout.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/accessibility_service.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';

View file

@ -1,7 +1,7 @@
import 'package:aves/model/settings/coordinate_format.dart';
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/enums/coordinate_format.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/settings/unit_system.dart';
import 'package:aves/model/settings/enums/unit_system.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/utils/color_utils.dart';
import 'package:aves/utils/constants.dart';

View file

@ -1,6 +1,6 @@
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/home_page.dart';
import 'package:aves/model/settings/screen_on.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/enums/home_page.dart';
import 'package:aves/model/settings/enums/screen_on.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/utils/color_utils.dart';

View file

@ -1,7 +1,7 @@
import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/settings/video_loop_mode.dart';
import 'package:aves/model/settings/enums/video_loop_mode.dart';
import 'package:aves/model/source/collection_source.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/utils/color_utils.dart';

View file

@ -1,5 +1,5 @@
import 'package:aves/model/settings/entry_background.dart';
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/enums/entry_background.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/widgets/common/fx/borders.dart';
import 'package:aves/widgets/common/fx/checkered_decoration.dart';
import 'package:flutter/material.dart';

View file

@ -1,4 +1,4 @@
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/theme/icons.dart';

View file

@ -6,7 +6,7 @@ import 'package:aves/model/filters/location.dart';
import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/filters/rating.dart';
import 'package:aves/model/filters/tag.dart';
import 'package:aves/model/settings/accessibility_animations.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/model/source/collection_source.dart';

View file

@ -1,5 +1,5 @@
import 'package:aves/model/entry.dart';
import 'package:aves/model/settings/accessibility_animations.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/widgets/common/magnifier/pan/gesture_detector_scope.dart';

View file

@ -4,7 +4,7 @@ import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/highlight.dart';
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/services/common/services.dart';

View file

@ -1,6 +1,6 @@
import 'package:aves/model/entry.dart';
import 'package:aves/model/filters/location.dart';
import 'package:aves/model/settings/coordinate_format.dart';
import 'package:aves/model/settings/enums/coordinate_format.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/services/common/services.dart';

View file

@ -3,7 +3,7 @@ import 'dart:math';
import 'package:aves/model/entry.dart';
import 'package:aves/model/metadata/overlay.dart';
import 'package:aves/model/multipage.dart';
import 'package:aves/model/settings/coordinate_format.dart';
import 'package:aves/model/settings/enums/coordinate_format.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/theme/durations.dart';

View file

@ -4,7 +4,7 @@ import 'dart:typed_data';
import 'package:aves/model/entry.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/settings/video_loop_mode.dart';
import 'package:aves/model/settings/enums/video_loop_mode.dart';
import 'package:aves/model/video/keys.dart';
import 'package:aves/model/video/metadata.dart';
import 'package:aves/utils/change_notifier.dart';

View file

@ -2,7 +2,7 @@ import 'dart:async';
import 'package:aves/model/entry.dart';
import 'package:aves/model/entry_images.dart';
import 'package:aves/model/settings/accessibility_animations.dart';
import 'package:aves/model/settings/enums/accessibility_animations.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/common/magnifier/controller/controller.dart';

View file

@ -3,8 +3,8 @@ import 'dart:math';
import 'package:aves/image_providers/region_provider.dart';
import 'package:aves/model/entry.dart';
import 'package:aves/model/entry_images.dart';
import 'package:aves/model/settings/entry_background.dart';
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/enums/entry_background.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/utils/math_utils.dart';
import 'package:aves/widgets/common/fx/checkered_decoration.dart';

View file

@ -4,8 +4,8 @@ import 'dart:ui';
import 'package:aves/image_providers/region_provider.dart';
import 'package:aves/model/entry.dart';
import 'package:aves/model/entry_images.dart';
import 'package:aves/model/settings/entry_background.dart';
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/enums/entry_background.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/utils/math_utils.dart';
import 'package:aves/widgets/common/fx/checkered_decoration.dart';

View file

@ -4,7 +4,7 @@ import 'package:aves/model/favourites.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/metadata/address.dart';
import 'package:aves/model/metadata/catalog.dart';
import 'package:aves/model/metadata_db.dart';
import 'package:aves/model/db/db_metadata.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';

View file

@ -7,7 +7,7 @@ import 'package:aves/model/filters/album.dart';
import 'package:aves/model/filters/tag.dart';
import 'package:aves/model/metadata/address.dart';
import 'package:aves/model/metadata/catalog.dart';
import 'package:aves/model/metadata_db.dart';
import 'package:aves/model/db/db_metadata.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/enums.dart';
import 'package:aves/model/source/media_store_source.dart';

View file

@ -1,5 +1,5 @@
import 'package:aves/l10n/l10n.dart';
import 'package:aves/model/settings/coordinate_format.dart';
import 'package:aves/model/settings/enums/coordinate_format.dart';
import 'package:aves/utils/geo_utils.dart';
import 'package:latlong2/latlong.dart';
import 'package:test/test.dart';

View file

@ -1,6 +1,6 @@
import 'package:aves/main_play.dart' as app;
import 'package:aves/model/settings/defaults.dart';
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/enums.dart';
import 'package:aves/widgets/filter_grids/countries_page.dart';

View file

@ -1,7 +1,7 @@
import 'dart:ui';
import 'package:aves/main_play.dart' as app;
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:flutter_driver/driver_extension.dart';
import 'package:flutter_test/flutter_test.dart';