Merge branch 'develop'
This commit is contained in:
commit
c86f52b61a
37 changed files with 632 additions and 332 deletions
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
|
@ -16,7 +16,7 @@ jobs:
|
||||||
|
|
||||||
- uses: subosito/flutter-action@v1
|
- uses: subosito/flutter-action@v1
|
||||||
with:
|
with:
|
||||||
channel: 'stable'
|
flutter-version: '1.17.5'
|
||||||
|
|
||||||
# Workaround for this Android Gradle Plugin issue (supposedly fixed in AGP 4.1):
|
# Workaround for this Android Gradle Plugin issue (supposedly fixed in AGP 4.1):
|
||||||
# https://issuetracker.google.com/issues/144111441
|
# https://issuetracker.google.com/issues/144111441
|
||||||
|
@ -49,8 +49,8 @@ jobs:
|
||||||
echo "${{ secrets.KEY_JKS }}" > release.keystore.asc
|
echo "${{ secrets.KEY_JKS }}" > release.keystore.asc
|
||||||
gpg -d --passphrase "${{ secrets.KEY_JKS_PASSPHRASE }}" --batch release.keystore.asc > $AVES_STORE_FILE
|
gpg -d --passphrase "${{ secrets.KEY_JKS_PASSPHRASE }}" --batch release.keystore.asc > $AVES_STORE_FILE
|
||||||
rm release.keystore.asc
|
rm release.keystore.asc
|
||||||
flutter build apk --bundle-sksl-path shaders.sksl.json
|
flutter build apk --bundle-sksl-path shaders_1.17.5.sksl.json
|
||||||
flutter build appbundle --bundle-sksl-path shaders.sksl.json
|
flutter build appbundle --bundle-sksl-path shaders_1.17.5.sksl.json
|
||||||
rm $AVES_STORE_FILE
|
rm $AVES_STORE_FILE
|
||||||
env:
|
env:
|
||||||
AVES_STORE_FILE: ${{ github.workspace }}/key.jks
|
AVES_STORE_FILE: ${{ github.workspace }}/key.jks
|
||||||
|
|
|
@ -6,8 +6,11 @@ import android.content.Intent;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.ResolveInfo;
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.content.res.Resources;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
@ -27,6 +30,7 @@ import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -110,14 +114,41 @@ public class AppAdapterHandler implements MethodChannel.MethodCallHandler {
|
||||||
Intent intent = new Intent(Intent.ACTION_MAIN, null);
|
Intent intent = new Intent(Intent.ACTION_MAIN, null);
|
||||||
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
|
||||||
PackageManager packageManager = context.getPackageManager();
|
|
||||||
List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivities(intent, 0);
|
// apps tend to use their name in English when creating folders
|
||||||
|
// so we get their names in English as well as the current locale
|
||||||
|
Configuration config = new Configuration();
|
||||||
|
config.setLocale(Locale.ENGLISH);
|
||||||
|
|
||||||
|
PackageManager pm = context.getPackageManager();
|
||||||
|
List<ResolveInfo> resolveInfoList = pm.queryIntentActivities(intent, 0);
|
||||||
for (ResolveInfo resolveInfo : resolveInfoList) {
|
for (ResolveInfo resolveInfo : resolveInfoList) {
|
||||||
ApplicationInfo applicationInfo = resolveInfo.activityInfo.applicationInfo;
|
ApplicationInfo ai = resolveInfo.activityInfo.applicationInfo;
|
||||||
boolean isSystemPackage = (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
|
boolean isSystemPackage = (ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
|
||||||
if (!isSystemPackage) {
|
if (!isSystemPackage) {
|
||||||
String appName = String.valueOf(packageManager.getApplicationLabel(applicationInfo));
|
String packageName = ai.packageName;
|
||||||
nameMap.put(appName, applicationInfo.packageName);
|
|
||||||
|
String currentLabel = String.valueOf(pm.getApplicationLabel(ai));
|
||||||
|
nameMap.put(currentLabel, packageName);
|
||||||
|
|
||||||
|
int labelRes = ai.labelRes;
|
||||||
|
if (labelRes != 0) {
|
||||||
|
try {
|
||||||
|
Resources resources = pm.getResourcesForApplication(ai);
|
||||||
|
// `updateConfiguration` is deprecated but it seems to be the only way
|
||||||
|
// to query resources from another app with a specific locale.
|
||||||
|
// The following methods do not work:
|
||||||
|
// - `resources.getConfiguration().setLocale(...)`
|
||||||
|
// - getting a package manager from a custom context with `context.createConfigurationContext(config)`
|
||||||
|
resources.updateConfiguration(config, resources.getDisplayMetrics());
|
||||||
|
String englishLabel = resources.getString(labelRes);
|
||||||
|
if (!TextUtils.equals(englishLabel, currentLabel)) {
|
||||||
|
nameMap.put(englishLabel, packageName);
|
||||||
|
}
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
Log.w(LOG_TAG, "failed to get app englishLabel for packageName=" + packageName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nameMap;
|
return nameMap;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:aves/model/settings.dart';
|
import 'package:aves/model/settings.dart';
|
||||||
|
import 'package:aves/widgets/common/data_providers/settings_provider.dart';
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
import 'package:aves/widgets/home_page.dart';
|
import 'package:aves/widgets/home_page.dart';
|
||||||
import 'package:aves/widgets/welcome_page.dart';
|
import 'package:aves/widgets/welcome_page.dart';
|
||||||
|
@ -37,34 +38,38 @@ class _AvesAppState extends State<AvesApp> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
// place the settings provider above `MaterialApp`
|
||||||
title: 'Aves',
|
// so it can be used during navigation transitions
|
||||||
theme: ThemeData(
|
return SettingsProvider(
|
||||||
brightness: Brightness.dark,
|
child: MaterialApp(
|
||||||
accentColor: accentColor,
|
title: 'Aves',
|
||||||
scaffoldBackgroundColor: Colors.grey[900],
|
theme: ThemeData(
|
||||||
buttonColor: accentColor,
|
brightness: Brightness.dark,
|
||||||
toggleableActiveColor: accentColor,
|
accentColor: accentColor,
|
||||||
tooltipTheme: TooltipThemeData(
|
scaffoldBackgroundColor: Colors.grey[900],
|
||||||
verticalOffset: 32,
|
buttonColor: accentColor,
|
||||||
),
|
toggleableActiveColor: accentColor,
|
||||||
appBarTheme: AppBarTheme(
|
tooltipTheme: TooltipThemeData(
|
||||||
textTheme: TextTheme(
|
verticalOffset: 32,
|
||||||
headline6: TextStyle(
|
),
|
||||||
fontSize: 20,
|
appBarTheme: AppBarTheme(
|
||||||
fontWeight: FontWeight.bold,
|
textTheme: TextTheme(
|
||||||
fontFamily: 'Concourse Caps',
|
headline6: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontFamily: 'Concourse Caps',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
home: FutureBuilder<void>(
|
||||||
home: FutureBuilder<void>(
|
future: _appSetup,
|
||||||
future: _appSetup,
|
builder: (context, snapshot) {
|
||||||
builder: (context, snapshot) {
|
if (snapshot.hasError) return Icon(AIcons.error);
|
||||||
if (snapshot.hasError) return Icon(AIcons.error);
|
if (snapshot.connectionState != ConnectionState.done) return Scaffold();
|
||||||
if (snapshot.connectionState != ConnectionState.done) return Scaffold();
|
return settings.hasAcceptedTerms ? HomePage() : WelcomePage();
|
||||||
return settings.hasAcceptedTerms ? HomePage() : WelcomePage();
|
},
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,9 @@ final Settings settings = Settings._private();
|
||||||
|
|
||||||
typedef SettingsCallback = void Function(String key, dynamic oldValue, dynamic newValue);
|
typedef SettingsCallback = void Function(String key, dynamic oldValue, dynamic newValue);
|
||||||
|
|
||||||
class Settings {
|
class Settings extends ChangeNotifier {
|
||||||
static SharedPreferences _prefs;
|
static SharedPreferences _prefs;
|
||||||
|
|
||||||
final ObserverList<SettingsCallback> _listeners = ObserverList<SettingsCallback>();
|
|
||||||
|
|
||||||
Settings._private();
|
Settings._private();
|
||||||
|
|
||||||
// preferences
|
// preferences
|
||||||
|
@ -28,6 +26,8 @@ class Settings {
|
||||||
static const infoMapZoomKey = 'info_map_zoom';
|
static const infoMapZoomKey = 'info_map_zoom';
|
||||||
static const launchPageKey = 'launch_page';
|
static const launchPageKey = 'launch_page';
|
||||||
static const coordinateFormatKey = 'coordinates_format';
|
static const coordinateFormatKey = 'coordinates_format';
|
||||||
|
static const svgBackgroundKey = 'svg_background';
|
||||||
|
static const albumSortFactorKey = 'album_sort_factor';
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
_prefs = await SharedPreferences.getInstance();
|
_prefs = await SharedPreferences.getInstance();
|
||||||
|
@ -37,37 +37,17 @@ class Settings {
|
||||||
return _prefs.clear();
|
return _prefs.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void addListener(SettingsCallback listener) => _listeners.add(listener);
|
|
||||||
|
|
||||||
void removeListener(SettingsCallback listener) => _listeners.remove(listener);
|
|
||||||
|
|
||||||
void notifyListeners(String key, dynamic oldValue, dynamic newValue) {
|
|
||||||
debugPrint('$runtimeType notifyListeners key=$key, old=$oldValue, new=$newValue');
|
|
||||||
if (_listeners != null) {
|
|
||||||
final localListeners = _listeners.toList();
|
|
||||||
for (final listener in localListeners) {
|
|
||||||
try {
|
|
||||||
if (_listeners.contains(listener)) {
|
|
||||||
listener(key, oldValue, newValue);
|
|
||||||
}
|
|
||||||
} catch (exception, stack) {
|
|
||||||
debugPrint('$runtimeType failed to notify listeners with exception=$exception\n$stack');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String get catalogTimeZone => _prefs.getString(catalogTimeZoneKey) ?? '';
|
String get catalogTimeZone => _prefs.getString(catalogTimeZoneKey) ?? '';
|
||||||
|
|
||||||
set catalogTimeZone(String newValue) => setAndNotify(catalogTimeZoneKey, newValue);
|
set catalogTimeZone(String newValue) => setAndNotify(catalogTimeZoneKey, newValue);
|
||||||
|
|
||||||
GroupFactor get collectionGroupFactor => getEnumOrDefault(collectionGroupFactorKey, GroupFactor.month, GroupFactor.values);
|
EntryGroupFactor get collectionGroupFactor => getEnumOrDefault(collectionGroupFactorKey, EntryGroupFactor.month, EntryGroupFactor.values);
|
||||||
|
|
||||||
set collectionGroupFactor(GroupFactor newValue) => setAndNotify(collectionGroupFactorKey, newValue.toString());
|
set collectionGroupFactor(EntryGroupFactor newValue) => setAndNotify(collectionGroupFactorKey, newValue.toString());
|
||||||
|
|
||||||
SortFactor get collectionSortFactor => getEnumOrDefault(collectionSortFactorKey, SortFactor.date, SortFactor.values);
|
EntrySortFactor get collectionSortFactor => getEnumOrDefault(collectionSortFactorKey, EntrySortFactor.date, EntrySortFactor.values);
|
||||||
|
|
||||||
set collectionSortFactor(SortFactor newValue) => setAndNotify(collectionSortFactorKey, newValue.toString());
|
set collectionSortFactor(EntrySortFactor newValue) => setAndNotify(collectionSortFactorKey, newValue.toString());
|
||||||
|
|
||||||
double get collectionTileExtent => _prefs.getDouble(collectionTileExtentKey) ?? 0;
|
double get collectionTileExtent => _prefs.getDouble(collectionTileExtentKey) ?? 0;
|
||||||
|
|
||||||
|
@ -93,6 +73,14 @@ class Settings {
|
||||||
|
|
||||||
set coordinateFormat(CoordinateFormat newValue) => setAndNotify(coordinateFormatKey, newValue.toString());
|
set coordinateFormat(CoordinateFormat newValue) => setAndNotify(coordinateFormatKey, newValue.toString());
|
||||||
|
|
||||||
|
int get svgBackground => _prefs.getInt(svgBackgroundKey) ?? 0xFFFFFFFF;
|
||||||
|
|
||||||
|
set svgBackground(int newValue) => setAndNotify(svgBackgroundKey, newValue);
|
||||||
|
|
||||||
|
ChipSortFactor get albumSortFactor => getEnumOrDefault(albumSortFactorKey, ChipSortFactor.date, ChipSortFactor.values);
|
||||||
|
|
||||||
|
set albumSortFactor(ChipSortFactor newValue) => setAndNotify(albumSortFactorKey, newValue.toString());
|
||||||
|
|
||||||
// convenience methods
|
// convenience methods
|
||||||
|
|
||||||
// ignore: avoid_positional_boolean_parameters
|
// ignore: avoid_positional_boolean_parameters
|
||||||
|
@ -133,7 +121,7 @@ class Settings {
|
||||||
_prefs.setBool(key, newValue);
|
_prefs.setBool(key, newValue);
|
||||||
}
|
}
|
||||||
if (oldValue != newValue) {
|
if (oldValue != newValue) {
|
||||||
notifyListeners(key, oldValue, newValue);
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,8 @@ import 'enums.dart';
|
||||||
class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSelectionMixin {
|
class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSelectionMixin {
|
||||||
final CollectionSource source;
|
final CollectionSource source;
|
||||||
final Set<CollectionFilter> filters;
|
final Set<CollectionFilter> filters;
|
||||||
GroupFactor groupFactor;
|
EntryGroupFactor groupFactor;
|
||||||
SortFactor sortFactor;
|
EntrySortFactor sortFactor;
|
||||||
final AChangeNotifier filterChangeNotifier = AChangeNotifier();
|
final AChangeNotifier filterChangeNotifier = AChangeNotifier();
|
||||||
final StreamController<ImageEntry> _highlightController = StreamController.broadcast();
|
final StreamController<ImageEntry> _highlightController = StreamController.broadcast();
|
||||||
|
|
||||||
|
@ -30,11 +30,11 @@ class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSel
|
||||||
CollectionLens({
|
CollectionLens({
|
||||||
@required this.source,
|
@required this.source,
|
||||||
Iterable<CollectionFilter> filters,
|
Iterable<CollectionFilter> filters,
|
||||||
@required GroupFactor groupFactor,
|
@required EntryGroupFactor groupFactor,
|
||||||
@required SortFactor sortFactor,
|
@required EntrySortFactor sortFactor,
|
||||||
}) : filters = {if (filters != null) ...filters.where((f) => f != null)},
|
}) : filters = {if (filters != null) ...filters.where((f) => f != null)},
|
||||||
groupFactor = groupFactor ?? GroupFactor.month,
|
groupFactor = groupFactor ?? EntryGroupFactor.month,
|
||||||
sortFactor = sortFactor ?? SortFactor.date {
|
sortFactor = sortFactor ?? EntrySortFactor.date {
|
||||||
_subscriptions.add(source.eventBus.on<EntryAddedEvent>().listen((e) => _refresh()));
|
_subscriptions.add(source.eventBus.on<EntryAddedEvent>().listen((e) => _refresh()));
|
||||||
_subscriptions.add(source.eventBus.on<EntryRemovedEvent>().listen((e) => onEntryRemoved(e.entries)));
|
_subscriptions.add(source.eventBus.on<EntryRemovedEvent>().listen((e) => onEntryRemoved(e.entries)));
|
||||||
_subscriptions.add(source.eventBus.on<EntryMovedEvent>().listen((e) => _refresh()));
|
_subscriptions.add(source.eventBus.on<EntryMovedEvent>().listen((e) => _refresh()));
|
||||||
|
@ -85,11 +85,11 @@ class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSel
|
||||||
void highlight(ImageEntry entry) => _highlightController.add(entry);
|
void highlight(ImageEntry entry) => _highlightController.add(entry);
|
||||||
|
|
||||||
bool get showHeaders {
|
bool get showHeaders {
|
||||||
if (sortFactor == SortFactor.size) return false;
|
if (sortFactor == EntrySortFactor.size) return false;
|
||||||
|
|
||||||
if (sortFactor == SortFactor.date && groupFactor == GroupFactor.none) return false;
|
if (sortFactor == EntrySortFactor.date && groupFactor == EntryGroupFactor.none) return false;
|
||||||
|
|
||||||
final albumSections = sortFactor == SortFactor.name || (sortFactor == SortFactor.date && groupFactor == GroupFactor.album);
|
final albumSections = sortFactor == EntrySortFactor.name || (sortFactor == EntrySortFactor.date && groupFactor == EntryGroupFactor.album);
|
||||||
final filterByAlbum = filters.any((f) => f is AlbumFilter);
|
final filterByAlbum = filters.any((f) => f is AlbumFilter);
|
||||||
if (albumSections && filterByAlbum) return false;
|
if (albumSections && filterByAlbum) return false;
|
||||||
|
|
||||||
|
@ -118,13 +118,13 @@ class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSel
|
||||||
filterChangeNotifier.notifyListeners();
|
filterChangeNotifier.notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void sort(SortFactor sortFactor) {
|
void sort(EntrySortFactor sortFactor) {
|
||||||
this.sortFactor = sortFactor;
|
this.sortFactor = sortFactor;
|
||||||
_applySort();
|
_applySort();
|
||||||
_applyGroup();
|
_applyGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
void group(GroupFactor groupFactor) {
|
void group(EntryGroupFactor groupFactor) {
|
||||||
this.groupFactor = groupFactor;
|
this.groupFactor = groupFactor;
|
||||||
_applyGroup();
|
_applyGroup();
|
||||||
}
|
}
|
||||||
|
@ -136,16 +136,16 @@ class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSel
|
||||||
|
|
||||||
void _applySort() {
|
void _applySort() {
|
||||||
switch (sortFactor) {
|
switch (sortFactor) {
|
||||||
case SortFactor.date:
|
case EntrySortFactor.date:
|
||||||
_filteredEntries.sort((a, b) {
|
_filteredEntries.sort((a, b) {
|
||||||
final c = b.bestDate?.compareTo(a.bestDate) ?? -1;
|
final c = b.bestDate?.compareTo(a.bestDate) ?? -1;
|
||||||
return c != 0 ? c : compareAsciiUpperCase(a.bestTitle, b.bestTitle);
|
return c != 0 ? c : compareAsciiUpperCase(a.bestTitle, b.bestTitle);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case SortFactor.size:
|
case EntrySortFactor.size:
|
||||||
_filteredEntries.sort((a, b) => b.sizeBytes.compareTo(a.sizeBytes));
|
_filteredEntries.sort((a, b) => b.sizeBytes.compareTo(a.sizeBytes));
|
||||||
break;
|
break;
|
||||||
case SortFactor.name:
|
case EntrySortFactor.name:
|
||||||
_filteredEntries.sort((a, b) => compareAsciiUpperCase(a.bestTitle, b.bestTitle));
|
_filteredEntries.sort((a, b) => compareAsciiUpperCase(a.bestTitle, b.bestTitle));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -153,30 +153,30 @@ class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSel
|
||||||
|
|
||||||
void _applyGroup() {
|
void _applyGroup() {
|
||||||
switch (sortFactor) {
|
switch (sortFactor) {
|
||||||
case SortFactor.date:
|
case EntrySortFactor.date:
|
||||||
switch (groupFactor) {
|
switch (groupFactor) {
|
||||||
case GroupFactor.album:
|
case EntryGroupFactor.album:
|
||||||
sections = groupBy<ImageEntry, String>(_filteredEntries, (entry) => entry.directory);
|
sections = groupBy<ImageEntry, String>(_filteredEntries, (entry) => entry.directory);
|
||||||
break;
|
break;
|
||||||
case GroupFactor.month:
|
case EntryGroupFactor.month:
|
||||||
sections = groupBy<ImageEntry, DateTime>(_filteredEntries, (entry) => entry.monthTaken);
|
sections = groupBy<ImageEntry, DateTime>(_filteredEntries, (entry) => entry.monthTaken);
|
||||||
break;
|
break;
|
||||||
case GroupFactor.day:
|
case EntryGroupFactor.day:
|
||||||
sections = groupBy<ImageEntry, DateTime>(_filteredEntries, (entry) => entry.dayTaken);
|
sections = groupBy<ImageEntry, DateTime>(_filteredEntries, (entry) => entry.dayTaken);
|
||||||
break;
|
break;
|
||||||
case GroupFactor.none:
|
case EntryGroupFactor.none:
|
||||||
sections = Map.fromEntries([
|
sections = Map.fromEntries([
|
||||||
MapEntry(null, _filteredEntries),
|
MapEntry(null, _filteredEntries),
|
||||||
]);
|
]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SortFactor.size:
|
case EntrySortFactor.size:
|
||||||
sections = Map.fromEntries([
|
sections = Map.fromEntries([
|
||||||
MapEntry(null, _filteredEntries),
|
MapEntry(null, _filteredEntries),
|
||||||
]);
|
]);
|
||||||
break;
|
break;
|
||||||
case SortFactor.name:
|
case EntrySortFactor.name:
|
||||||
final byAlbum = groupBy<ImageEntry, String>(_filteredEntries, (entry) => entry.directory);
|
final byAlbum = groupBy<ImageEntry, String>(_filteredEntries, (entry) => entry.directory);
|
||||||
int compare(a, b) {
|
int compare(a, b) {
|
||||||
final ua = source.getUniqueAlbumName(a);
|
final ua = source.getUniqueAlbumName(a);
|
||||||
|
|
|
@ -39,8 +39,8 @@ class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagMixin {
|
||||||
@override
|
@override
|
||||||
List<ImageEntry> get sortedEntriesForFilterList => CollectionLens(
|
List<ImageEntry> get sortedEntriesForFilterList => CollectionLens(
|
||||||
source: this,
|
source: this,
|
||||||
groupFactor: GroupFactor.month,
|
groupFactor: EntryGroupFactor.none,
|
||||||
sortFactor: SortFactor.date,
|
sortFactor: EntrySortFactor.date,
|
||||||
).sortedEntries;
|
).sortedEntries;
|
||||||
|
|
||||||
ValueNotifier<SourceState> stateNotifier = ValueNotifier<SourceState>(SourceState.ready);
|
ValueNotifier<SourceState> stateNotifier = ValueNotifier<SourceState>(SourceState.ready);
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
enum SortFactor { date, size, name }
|
|
||||||
|
|
||||||
enum GroupFactor { none, album, month, day }
|
|
||||||
|
|
||||||
enum Activity { browse, select }
|
enum Activity { browse, select }
|
||||||
|
|
||||||
|
enum ChipSortFactor { date, name }
|
||||||
|
|
||||||
|
enum EntrySortFactor { date, size, name }
|
||||||
|
|
||||||
|
enum EntryGroupFactor { none, album, month, day }
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:aves/services/android_app_service.dart';
|
import 'package:aves/services/android_app_service.dart';
|
||||||
import 'package:aves/services/android_file_service.dart';
|
import 'package:aves/services/android_file_service.dart';
|
||||||
|
import 'package:aves/utils/change_notifier.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
|
|
||||||
final AndroidFileUtils androidFileUtils = AndroidFileUtils._private();
|
final AndroidFileUtils androidFileUtils = AndroidFileUtils._private();
|
||||||
|
@ -9,6 +10,8 @@ class AndroidFileUtils {
|
||||||
Set<StorageVolume> storageVolumes = {};
|
Set<StorageVolume> storageVolumes = {};
|
||||||
Map appNameMap = {};
|
Map appNameMap = {};
|
||||||
|
|
||||||
|
AChangeNotifier appNameChangeNotifier = AChangeNotifier();
|
||||||
|
|
||||||
AndroidFileUtils._private();
|
AndroidFileUtils._private();
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
|
@ -19,8 +22,12 @@ class AndroidFileUtils {
|
||||||
downloadPath = join(primaryStorage, 'Download');
|
downloadPath = join(primaryStorage, 'Download');
|
||||||
moviesPath = join(primaryStorage, 'Movies');
|
moviesPath = join(primaryStorage, 'Movies');
|
||||||
picturesPath = join(primaryStorage, 'Pictures');
|
picturesPath = join(primaryStorage, 'Pictures');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> initAppNames() async {
|
||||||
appNameMap = await AndroidAppService.getAppNames()
|
appNameMap = await AndroidAppService.getAppNames()
|
||||||
..addAll({'KakaoTalkDownload': 'com.kakao.talk'});
|
..addAll({'KakaoTalkDownload': 'com.kakao.talk'});
|
||||||
|
appNameChangeNotifier.notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isCameraPath(String path) => path != null && path.startsWith(dcimPath) && (path.endsWith('Camera') || path.endsWith('100ANDRO'));
|
bool isCameraPath(String path) => path != null && path.startsWith(dcimPath) && (path.endsWith('Camera') || path.endsWith('100ANDRO'));
|
||||||
|
|
|
@ -19,9 +19,6 @@ class Constants {
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
static const svgBackground = Colors.white;
|
|
||||||
static const svgColorFilter = ColorFilter.mode(svgBackground, BlendMode.dstOver);
|
|
||||||
|
|
||||||
static const List<Dependency> androidDependencies = [
|
static const List<Dependency> androidDependencies = [
|
||||||
Dependency(
|
Dependency(
|
||||||
name: 'CWAC-Document',
|
name: 'CWAC-Document',
|
||||||
|
|
|
@ -7,9 +7,9 @@ import 'package:aves/model/source/enums.dart';
|
||||||
import 'package:aves/utils/durations.dart';
|
import 'package:aves/utils/durations.dart';
|
||||||
import 'package:aves/widgets/album/filter_bar.dart';
|
import 'package:aves/widgets/album/filter_bar.dart';
|
||||||
import 'package:aves/widgets/album/search/search_delegate.dart';
|
import 'package:aves/widgets/album/search/search_delegate.dart';
|
||||||
import 'package:aves/widgets/common/action_delegates/group_collection_dialog.dart';
|
import 'package:aves/widgets/common/action_delegates/collection_group_dialog.dart';
|
||||||
|
import 'package:aves/widgets/common/action_delegates/collection_sort_dialog.dart';
|
||||||
import 'package:aves/widgets/common/action_delegates/selection_action_delegate.dart';
|
import 'package:aves/widgets/common/action_delegates/selection_action_delegate.dart';
|
||||||
import 'package:aves/widgets/common/action_delegates/sort_collection_dialog.dart';
|
|
||||||
import 'package:aves/widgets/common/app_bar_subtitle.dart';
|
import 'package:aves/widgets/common/app_bar_subtitle.dart';
|
||||||
import 'package:aves/widgets/common/data_providers/media_store_collection_provider.dart';
|
import 'package:aves/widgets/common/data_providers/media_store_collection_provider.dart';
|
||||||
import 'package:aves/widgets/common/entry_actions.dart';
|
import 'package:aves/widgets/common/entry_actions.dart';
|
||||||
|
@ -196,7 +196,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
value: CollectionAction.sort,
|
value: CollectionAction.sort,
|
||||||
child: MenuRow(text: 'Sort...', icon: AIcons.sort),
|
child: MenuRow(text: 'Sort...', icon: AIcons.sort),
|
||||||
),
|
),
|
||||||
if (collection.sortFactor == SortFactor.date)
|
if (collection.sortFactor == EntrySortFactor.date)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
key: Key('menu-group'),
|
key: Key('menu-group'),
|
||||||
value: CollectionAction.group,
|
value: CollectionAction.group,
|
||||||
|
@ -296,9 +296,9 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
unawaited(_goToStats());
|
unawaited(_goToStats());
|
||||||
break;
|
break;
|
||||||
case CollectionAction.group:
|
case CollectionAction.group:
|
||||||
final factor = await showDialog<GroupFactor>(
|
final factor = await showDialog<EntryGroupFactor>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => GroupCollectionDialog(),
|
builder: (context) => CollectionGroupDialog(),
|
||||||
);
|
);
|
||||||
if (factor != null) {
|
if (factor != null) {
|
||||||
settings.collectionGroupFactor = factor;
|
settings.collectionGroupFactor = factor;
|
||||||
|
@ -306,9 +306,9 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CollectionAction.sort:
|
case CollectionAction.sort:
|
||||||
final factor = await showDialog<SortFactor>(
|
final factor = await showDialog<EntrySortFactor>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => SortCollectionDialog(),
|
builder: (context) => CollectionSortDialog(initialValue: settings.collectionSortFactor),
|
||||||
);
|
);
|
||||||
if (factor != null) {
|
if (factor != null) {
|
||||||
settings.collectionSortFactor = factor;
|
settings.collectionSortFactor = factor;
|
||||||
|
|
|
@ -29,24 +29,24 @@ class SectionHeader extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget header;
|
Widget header;
|
||||||
switch (collection.sortFactor) {
|
switch (collection.sortFactor) {
|
||||||
case SortFactor.date:
|
case EntrySortFactor.date:
|
||||||
switch (collection.groupFactor) {
|
switch (collection.groupFactor) {
|
||||||
case GroupFactor.album:
|
case EntryGroupFactor.album:
|
||||||
header = _buildAlbumSectionHeader();
|
header = _buildAlbumSectionHeader();
|
||||||
break;
|
break;
|
||||||
case GroupFactor.month:
|
case EntryGroupFactor.month:
|
||||||
header = MonthSectionHeader(key: ValueKey(sectionKey), date: sectionKey as DateTime);
|
header = MonthSectionHeader(key: ValueKey(sectionKey), date: sectionKey as DateTime);
|
||||||
break;
|
break;
|
||||||
case GroupFactor.day:
|
case EntryGroupFactor.day:
|
||||||
header = DaySectionHeader(key: ValueKey(sectionKey), date: sectionKey as DateTime);
|
header = DaySectionHeader(key: ValueKey(sectionKey), date: sectionKey as DateTime);
|
||||||
break;
|
break;
|
||||||
case GroupFactor.none:
|
case EntryGroupFactor.none:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SortFactor.size:
|
case EntrySortFactor.size:
|
||||||
break;
|
break;
|
||||||
case SortFactor.name:
|
case EntrySortFactor.name:
|
||||||
header = _buildAlbumSectionHeader();
|
header = _buildAlbumSectionHeader();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,8 @@ class SectionHeader extends StatelessWidget {
|
||||||
// force a higher first line to match leading icon/selector dimension
|
// force a higher first line to match leading icon/selector dimension
|
||||||
style: TextStyle(height: 2.3 * textScaleFactor),
|
style: TextStyle(height: 2.3 * textScaleFactor),
|
||||||
), // 23 hair spaces match a width of 40.0
|
), // 23 hair spaces match a width of 40.0
|
||||||
if (hasTrailing) TextSpan(text: '\u200A' * 17),
|
if (hasTrailing)
|
||||||
|
TextSpan(text: '\u200A' * 17),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: text,
|
text: text,
|
||||||
style: Constants.titleTextStyle,
|
style: Constants.titleTextStyle,
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/model/settings.dart';
|
||||||
import 'package:aves/widgets/common/image_providers/uri_picture_provider.dart';
|
import 'package:aves/widgets/common/image_providers/uri_picture_provider.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class ThumbnailVectorImage extends StatelessWidget {
|
class ThumbnailVectorImage extends StatelessWidget {
|
||||||
final ImageEntry entry;
|
final ImageEntry entry;
|
||||||
|
@ -23,14 +24,20 @@ class ThumbnailVectorImage extends StatelessWidget {
|
||||||
// so that `SvgPicture` doesn't get aligned by the `Stack` like the overlay icons
|
// so that `SvgPicture` doesn't get aligned by the `Stack` like the overlay icons
|
||||||
width: extent,
|
width: extent,
|
||||||
height: extent,
|
height: extent,
|
||||||
child: SvgPicture(
|
child: Selector<Settings, int>(
|
||||||
UriPicture(
|
selector: (context, s) => s.svgBackground,
|
||||||
uri: entry.uri,
|
builder: (context, svgBackground, child) {
|
||||||
mimeType: entry.mimeType,
|
final colorFilter = ColorFilter.mode(Color(svgBackground), BlendMode.dstOver);
|
||||||
colorFilter: Constants.svgColorFilter,
|
return SvgPicture(
|
||||||
),
|
UriPicture(
|
||||||
width: extent,
|
uri: entry.uri,
|
||||||
height: extent,
|
mimeType: entry.mimeType,
|
||||||
|
colorFilter: colorFilter,
|
||||||
|
),
|
||||||
|
width: extent,
|
||||||
|
height: extent,
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return heroTag == null
|
return heroTag == null
|
||||||
|
|
|
@ -17,7 +17,9 @@ import 'package:aves/widgets/album/collection_page.dart';
|
||||||
import 'package:aves/widgets/common/aves_logo.dart';
|
import 'package:aves/widgets/common/aves_logo.dart';
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
import 'package:aves/widgets/debug_page.dart';
|
import 'package:aves/widgets/debug_page.dart';
|
||||||
import 'package:aves/widgets/filter_grid_page.dart';
|
import 'package:aves/widgets/filter_grids/albums_page.dart';
|
||||||
|
import 'package:aves/widgets/filter_grids/countries_page.dart';
|
||||||
|
import 'package:aves/widgets/filter_grids/tags_page.dart';
|
||||||
import 'package:aves/widgets/settings/settings_page.dart';
|
import 'package:aves/widgets/settings/settings_page.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
|
@ -1,22 +1,25 @@
|
||||||
import 'package:aves/model/settings.dart';
|
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
import '../dialog.dart';
|
import '../dialog.dart';
|
||||||
|
|
||||||
class SortCollectionDialog extends StatefulWidget {
|
class ChipSortDialog extends StatefulWidget {
|
||||||
|
final ChipSortFactor initialValue;
|
||||||
|
|
||||||
|
const ChipSortDialog({@required this.initialValue});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_SortCollectionDialogState createState() => _SortCollectionDialogState();
|
_ChipSortDialogState createState() => _ChipSortDialogState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SortCollectionDialogState extends State<SortCollectionDialog> {
|
class _ChipSortDialogState extends State<ChipSortDialog> {
|
||||||
SortFactor _selectedSort;
|
ChipSortFactor _selectedSort;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_selectedSort = settings.collectionSortFactor;
|
_selectedSort = widget.initialValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -24,9 +27,8 @@ class _SortCollectionDialogState extends State<SortCollectionDialog> {
|
||||||
return AvesDialog(
|
return AvesDialog(
|
||||||
title: 'Sort',
|
title: 'Sort',
|
||||||
scrollableContent: [
|
scrollableContent: [
|
||||||
_buildRadioListTile(SortFactor.date, 'By date'),
|
_buildRadioListTile(ChipSortFactor.date, 'By date'),
|
||||||
_buildRadioListTile(SortFactor.size, 'By size'),
|
_buildRadioListTile(ChipSortFactor.name, 'By name'),
|
||||||
_buildRadioListTile(SortFactor.name, 'By album & file name'),
|
|
||||||
],
|
],
|
||||||
actions: [
|
actions: [
|
||||||
FlatButton(
|
FlatButton(
|
||||||
|
@ -42,7 +44,7 @@ class _SortCollectionDialogState extends State<SortCollectionDialog> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildRadioListTile(SortFactor value, String title) => RadioListTile<SortFactor>(
|
Widget _buildRadioListTile(ChipSortFactor value, String title) => RadioListTile<ChipSortFactor>(
|
||||||
key: Key(value.toString()),
|
key: Key(value.toString()),
|
||||||
value: value,
|
value: value,
|
||||||
groupValue: _selectedSort,
|
groupValue: _selectedSort,
|
|
@ -5,13 +5,13 @@ import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
import '../dialog.dart';
|
import '../dialog.dart';
|
||||||
|
|
||||||
class GroupCollectionDialog extends StatefulWidget {
|
class CollectionGroupDialog extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
_GroupCollectionDialogState createState() => _GroupCollectionDialogState();
|
_CollectionGroupDialogState createState() => _CollectionGroupDialogState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GroupCollectionDialogState extends State<GroupCollectionDialog> {
|
class _CollectionGroupDialogState extends State<CollectionGroupDialog> {
|
||||||
GroupFactor _selectedGroup;
|
EntryGroupFactor _selectedGroup;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -24,10 +24,10 @@ class _GroupCollectionDialogState extends State<GroupCollectionDialog> {
|
||||||
return AvesDialog(
|
return AvesDialog(
|
||||||
title: 'Group',
|
title: 'Group',
|
||||||
scrollableContent: [
|
scrollableContent: [
|
||||||
_buildRadioListTile(GroupFactor.album, 'By album'),
|
_buildRadioListTile(EntryGroupFactor.album, 'By album'),
|
||||||
_buildRadioListTile(GroupFactor.month, 'By month'),
|
_buildRadioListTile(EntryGroupFactor.month, 'By month'),
|
||||||
_buildRadioListTile(GroupFactor.day, 'By day'),
|
_buildRadioListTile(EntryGroupFactor.day, 'By day'),
|
||||||
_buildRadioListTile(GroupFactor.none, 'Do not group'),
|
_buildRadioListTile(EntryGroupFactor.none, 'Do not group'),
|
||||||
],
|
],
|
||||||
actions: [
|
actions: [
|
||||||
FlatButton(
|
FlatButton(
|
||||||
|
@ -43,7 +43,7 @@ class _GroupCollectionDialogState extends State<GroupCollectionDialog> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildRadioListTile(GroupFactor value, String title) => RadioListTile<GroupFactor>(
|
Widget _buildRadioListTile(EntryGroupFactor value, String title) => RadioListTile<EntryGroupFactor>(
|
||||||
key: Key(value.toString()),
|
key: Key(value.toString()),
|
||||||
value: value,
|
value: value,
|
||||||
groupValue: _selectedGroup,
|
groupValue: _selectedGroup,
|
|
@ -0,0 +1,60 @@
|
||||||
|
import 'package:aves/model/source/enums.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import '../dialog.dart';
|
||||||
|
|
||||||
|
class CollectionSortDialog extends StatefulWidget {
|
||||||
|
final EntrySortFactor initialValue;
|
||||||
|
|
||||||
|
const CollectionSortDialog({@required this.initialValue});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_CollectionSortDialogState createState() => _CollectionSortDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CollectionSortDialogState extends State<CollectionSortDialog> {
|
||||||
|
EntrySortFactor _selectedSort;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_selectedSort = widget.initialValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AvesDialog(
|
||||||
|
title: 'Sort',
|
||||||
|
scrollableContent: [
|
||||||
|
_buildRadioListTile(EntrySortFactor.date, 'By date'),
|
||||||
|
_buildRadioListTile(EntrySortFactor.size, 'By size'),
|
||||||
|
_buildRadioListTile(EntrySortFactor.name, 'By album & file name'),
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
FlatButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: Text('Cancel'.toUpperCase()),
|
||||||
|
),
|
||||||
|
FlatButton(
|
||||||
|
key: Key('apply-button'),
|
||||||
|
onPressed: () => Navigator.pop(context, _selectedSort),
|
||||||
|
child: Text('Apply'.toUpperCase()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildRadioListTile(EntrySortFactor value, String title) => RadioListTile<EntrySortFactor>(
|
||||||
|
key: Key(value.toString()),
|
||||||
|
value: value,
|
||||||
|
groupValue: _selectedSort,
|
||||||
|
onChanged: (sort) => setState(() => _selectedSort = sort),
|
||||||
|
title: Text(
|
||||||
|
title,
|
||||||
|
softWrap: false,
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
|
maxLines: 1,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
|
@ -17,7 +17,7 @@ import 'package:aves/widgets/common/action_delegates/permission_aware.dart';
|
||||||
import 'package:aves/widgets/common/dialog.dart';
|
import 'package:aves/widgets/common/dialog.dart';
|
||||||
import 'package:aves/widgets/common/entry_actions.dart';
|
import 'package:aves/widgets/common/entry_actions.dart';
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
import 'package:aves/widgets/filter_grid_page.dart';
|
import 'package:aves/widgets/filter_grids/filter_grid_page.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
|
|
11
lib/widgets/common/borders.dart
Normal file
11
lib/widgets/common/borders.dart
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class AvesCircleBorder {
|
||||||
|
static BoxBorder build(BuildContext context) {
|
||||||
|
final subPixel = MediaQuery.of(context).devicePixelRatio > 2;
|
||||||
|
return Border.all(
|
||||||
|
color: Colors.white30,
|
||||||
|
width: subPixel ? 0.5 : 1.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
17
lib/widgets/common/data_providers/settings_provider.dart
Normal file
17
lib/widgets/common/data_providers/settings_provider.dart
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import 'package:aves/model/settings.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class SettingsProvider extends StatelessWidget {
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const SettingsProvider({@required this.child});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ChangeNotifierProvider<Settings>.value(
|
||||||
|
value: settings,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
128
lib/widgets/filter_grids/albums_page.dart
Normal file
128
lib/widgets/filter_grids/albums_page.dart
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
import 'package:aves/model/filters/album.dart';
|
||||||
|
import 'package:aves/model/image_entry.dart';
|
||||||
|
import 'package:aves/model/settings.dart';
|
||||||
|
import 'package:aves/model/source/album.dart';
|
||||||
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
|
import 'package:aves/model/source/enums.dart';
|
||||||
|
import 'package:aves/utils/android_file_utils.dart';
|
||||||
|
import 'package:aves/utils/durations.dart';
|
||||||
|
import 'package:aves/widgets/album/empty.dart';
|
||||||
|
import 'package:aves/widgets/common/action_delegates/chip_sort_dialog.dart';
|
||||||
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
|
import 'package:aves/widgets/common/menu_row.dart';
|
||||||
|
import 'package:aves/widgets/filter_grids/filter_grid_page.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class AlbumListPage extends StatelessWidget {
|
||||||
|
final CollectionSource source;
|
||||||
|
|
||||||
|
const AlbumListPage({@required this.source});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Selector<Settings, ChipSortFactor>(
|
||||||
|
selector: (context, s) => s.albumSortFactor,
|
||||||
|
builder: (context, albumSortFactor, child) {
|
||||||
|
return AnimatedBuilder(
|
||||||
|
animation: androidFileUtils.appNameChangeNotifier,
|
||||||
|
builder: (context, child) => StreamBuilder(
|
||||||
|
stream: source.eventBus.on<AlbumsChangedEvent>(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
return FilterNavigationPage(
|
||||||
|
source: source,
|
||||||
|
title: 'Albums',
|
||||||
|
actions: _buildActions(),
|
||||||
|
filterEntries: _getAlbumEntries(),
|
||||||
|
filterBuilder: (s) => AlbumFilter(s, source.getUniqueAlbumName(s)),
|
||||||
|
emptyBuilder: () => EmptyContent(
|
||||||
|
icon: AIcons.album,
|
||||||
|
text: 'No albums',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, ImageEntry> _getAlbumEntries() {
|
||||||
|
final entriesByDate = source.sortedEntriesForFilterList;
|
||||||
|
final albumEntries = source.sortedAlbums.map((album) {
|
||||||
|
return MapEntry(
|
||||||
|
album,
|
||||||
|
entriesByDate.firstWhere((entry) => entry.directory == album, orElse: () => null),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
switch (settings.albumSortFactor) {
|
||||||
|
case ChipSortFactor.date:
|
||||||
|
albumEntries.sort((a, b) {
|
||||||
|
final c = b.value.bestDate?.compareTo(a.value.bestDate) ?? -1;
|
||||||
|
return c != 0 ? c : compareAsciiUpperCase(a.key, b.key);
|
||||||
|
});
|
||||||
|
return Map.fromEntries(albumEntries);
|
||||||
|
case ChipSortFactor.name:
|
||||||
|
default:
|
||||||
|
final regularAlbums = <String>[], appAlbums = <String>[], specialAlbums = <String>[];
|
||||||
|
for (var album in source.sortedAlbums) {
|
||||||
|
switch (androidFileUtils.getAlbumType(album)) {
|
||||||
|
case AlbumType.regular:
|
||||||
|
regularAlbums.add(album);
|
||||||
|
break;
|
||||||
|
case AlbumType.app:
|
||||||
|
appAlbums.add(album);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
specialAlbums.add(album);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Map.fromEntries([...specialAlbums, ...appAlbums, ...regularAlbums].map((album) {
|
||||||
|
return MapEntry(
|
||||||
|
album,
|
||||||
|
entriesByDate.firstWhere((entry) => entry.directory == album, orElse: () => null),
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildActions() {
|
||||||
|
return [
|
||||||
|
Builder(
|
||||||
|
builder: (context) => PopupMenuButton<ChipAction>(
|
||||||
|
key: Key('appbar-menu-button'),
|
||||||
|
itemBuilder: (context) {
|
||||||
|
return [
|
||||||
|
PopupMenuItem(
|
||||||
|
key: Key('menu-sort'),
|
||||||
|
value: ChipAction.sort,
|
||||||
|
child: MenuRow(text: 'Sort...', icon: AIcons.sort),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
onSelected: (action) => _onChipActionSelected(context, action),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onChipActionSelected(BuildContext context, ChipAction action) async {
|
||||||
|
// wait for the popup menu to hide before proceeding with the action
|
||||||
|
await Future.delayed(Durations.popupMenuAnimation * timeDilation);
|
||||||
|
switch (action) {
|
||||||
|
case ChipAction.sort:
|
||||||
|
final factor = await showDialog<ChipSortFactor>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => ChipSortDialog(initialValue: settings.albumSortFactor),
|
||||||
|
);
|
||||||
|
if (factor != null) {
|
||||||
|
settings.albumSortFactor = factor;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
lib/widgets/filter_grids/countries_page.dart
Normal file
30
lib/widgets/filter_grids/countries_page.dart
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import 'package:aves/model/filters/location.dart';
|
||||||
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
|
import 'package:aves/model/source/location.dart';
|
||||||
|
import 'package:aves/widgets/album/empty.dart';
|
||||||
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
|
import 'package:aves/widgets/filter_grids/filter_grid_page.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class CountryListPage extends StatelessWidget {
|
||||||
|
final CollectionSource source;
|
||||||
|
|
||||||
|
const CountryListPage({@required this.source});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return StreamBuilder(
|
||||||
|
stream: source.eventBus.on<LocationsChangedEvent>(),
|
||||||
|
builder: (context, snapshot) => FilterNavigationPage(
|
||||||
|
source: source,
|
||||||
|
title: 'Countries',
|
||||||
|
filterEntries: source.getCountryEntries(),
|
||||||
|
filterBuilder: (s) => LocationFilter(LocationLevel.country, s),
|
||||||
|
emptyBuilder: () => EmptyContent(
|
||||||
|
icon: AIcons.location,
|
||||||
|
text: 'No countries',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
71
lib/widgets/filter_grids/decorated_filter_chip.dart
Normal file
71
lib/widgets/filter_grids/decorated_filter_chip.dart
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:aves/model/filters/album.dart';
|
||||||
|
import 'package:aves/model/filters/filters.dart';
|
||||||
|
import 'package:aves/model/image_entry.dart';
|
||||||
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
|
import 'package:aves/utils/android_file_utils.dart';
|
||||||
|
import 'package:aves/widgets/album/thumbnail/raster.dart';
|
||||||
|
import 'package:aves/widgets/album/thumbnail/vector.dart';
|
||||||
|
import 'package:aves/widgets/common/aves_filter_chip.dart';
|
||||||
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
|
import 'package:aves/widgets/filter_grids/filter_grid_page.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class DecoratedFilterChip extends StatelessWidget {
|
||||||
|
final CollectionSource source;
|
||||||
|
final CollectionFilter filter;
|
||||||
|
final ImageEntry entry;
|
||||||
|
final FilterCallback onPressed;
|
||||||
|
|
||||||
|
const DecoratedFilterChip({
|
||||||
|
@required this.source,
|
||||||
|
@required this.filter,
|
||||||
|
@required this.entry,
|
||||||
|
@required this.onPressed,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Widget backgroundImage;
|
||||||
|
if (entry != null) {
|
||||||
|
backgroundImage = entry.isSvg
|
||||||
|
? ThumbnailVectorImage(
|
||||||
|
entry: entry,
|
||||||
|
extent: FilterGridPage.maxCrossAxisExtent,
|
||||||
|
)
|
||||||
|
: ThumbnailRasterImage(
|
||||||
|
entry: entry,
|
||||||
|
extent: FilterGridPage.maxCrossAxisExtent,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return AvesFilterChip(
|
||||||
|
filter: filter,
|
||||||
|
showGenericIcon: false,
|
||||||
|
background: backgroundImage,
|
||||||
|
details: _buildDetails(filter),
|
||||||
|
onPressed: onPressed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDetails(CollectionFilter filter) {
|
||||||
|
final count = Text(
|
||||||
|
'${source.count(filter)}',
|
||||||
|
style: TextStyle(color: FilterGridPage.detailColor),
|
||||||
|
);
|
||||||
|
return filter is AlbumFilter && androidFileUtils.isOnRemovableStorage(filter.album)
|
||||||
|
? Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
AIcons.removableStorage,
|
||||||
|
size: 16,
|
||||||
|
color: FilterGridPage.detailColor,
|
||||||
|
),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
count,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: count;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,103 +1,25 @@
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:aves/model/filters/album.dart';
|
|
||||||
import 'package:aves/model/filters/filters.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/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/model/settings.dart';
|
import 'package:aves/model/settings.dart';
|
||||||
import 'package:aves/model/source/album.dart';
|
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/location.dart';
|
|
||||||
import 'package:aves/model/source/tag.dart';
|
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
|
||||||
import 'package:aves/utils/durations.dart';
|
import 'package:aves/utils/durations.dart';
|
||||||
import 'package:aves/widgets/album/collection_page.dart';
|
import 'package:aves/widgets/album/collection_page.dart';
|
||||||
import 'package:aves/widgets/album/empty.dart';
|
|
||||||
import 'package:aves/widgets/album/thumbnail/raster.dart';
|
|
||||||
import 'package:aves/widgets/album/thumbnail/vector.dart';
|
|
||||||
import 'package:aves/widgets/app_drawer.dart';
|
import 'package:aves/widgets/app_drawer.dart';
|
||||||
import 'package:aves/widgets/common/app_bar_subtitle.dart';
|
import 'package:aves/widgets/common/app_bar_subtitle.dart';
|
||||||
import 'package:aves/widgets/common/aves_filter_chip.dart';
|
import 'package:aves/widgets/common/aves_filter_chip.dart';
|
||||||
import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart';
|
import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart';
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
import 'package:aves/widgets/filter_grids/decorated_filter_chip.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class AlbumListPage extends StatelessWidget {
|
|
||||||
final CollectionSource source;
|
|
||||||
|
|
||||||
const AlbumListPage({@required this.source});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return StreamBuilder(
|
|
||||||
stream: source.eventBus.on<AlbumsChangedEvent>(),
|
|
||||||
builder: (context, snapshot) => FilterNavigationPage(
|
|
||||||
source: source,
|
|
||||||
title: 'Albums',
|
|
||||||
filterEntries: source.getAlbumEntries(),
|
|
||||||
filterBuilder: (s) => AlbumFilter(s, source.getUniqueAlbumName(s)),
|
|
||||||
emptyBuilder: () => EmptyContent(
|
|
||||||
icon: AIcons.album,
|
|
||||||
text: 'No albums',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CountryListPage extends StatelessWidget {
|
|
||||||
final CollectionSource source;
|
|
||||||
|
|
||||||
const CountryListPage({@required this.source});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return StreamBuilder(
|
|
||||||
stream: source.eventBus.on<LocationsChangedEvent>(),
|
|
||||||
builder: (context, snapshot) => FilterNavigationPage(
|
|
||||||
source: source,
|
|
||||||
title: 'Countries',
|
|
||||||
filterEntries: source.getCountryEntries(),
|
|
||||||
filterBuilder: (s) => LocationFilter(LocationLevel.country, s),
|
|
||||||
emptyBuilder: () => EmptyContent(
|
|
||||||
icon: AIcons.location,
|
|
||||||
text: 'No countries',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TagListPage extends StatelessWidget {
|
|
||||||
final CollectionSource source;
|
|
||||||
|
|
||||||
const TagListPage({@required this.source});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return StreamBuilder(
|
|
||||||
stream: source.eventBus.on<TagsChangedEvent>(),
|
|
||||||
builder: (context, snapshot) => FilterNavigationPage(
|
|
||||||
source: source,
|
|
||||||
title: 'Tags',
|
|
||||||
filterEntries: source.getTagEntries(),
|
|
||||||
filterBuilder: (s) => TagFilter(s),
|
|
||||||
emptyBuilder: () => EmptyContent(
|
|
||||||
icon: AIcons.tag,
|
|
||||||
text: 'No tags',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FilterNavigationPage extends StatelessWidget {
|
class FilterNavigationPage extends StatelessWidget {
|
||||||
final CollectionSource source;
|
final CollectionSource source;
|
||||||
final String title;
|
final String title;
|
||||||
|
final List<Widget> actions;
|
||||||
final Map<String, ImageEntry> filterEntries;
|
final Map<String, ImageEntry> filterEntries;
|
||||||
final CollectionFilter Function(String key) filterBuilder;
|
final CollectionFilter Function(String key) filterBuilder;
|
||||||
final Widget Function() emptyBuilder;
|
final Widget Function() emptyBuilder;
|
||||||
|
@ -105,6 +27,7 @@ class FilterNavigationPage extends StatelessWidget {
|
||||||
const FilterNavigationPage({
|
const FilterNavigationPage({
|
||||||
@required this.source,
|
@required this.source,
|
||||||
@required this.title,
|
@required this.title,
|
||||||
|
this.actions,
|
||||||
@required this.filterEntries,
|
@required this.filterEntries,
|
||||||
@required this.filterBuilder,
|
@required this.filterBuilder,
|
||||||
@required this.emptyBuilder,
|
@required this.emptyBuilder,
|
||||||
|
@ -119,6 +42,7 @@ class FilterNavigationPage extends StatelessWidget {
|
||||||
title: Text(title),
|
title: Text(title),
|
||||||
source: source,
|
source: source,
|
||||||
),
|
),
|
||||||
|
actions: actions,
|
||||||
floating: true,
|
floating: true,
|
||||||
),
|
),
|
||||||
filterEntries: filterEntries,
|
filterEntries: filterEntries,
|
||||||
|
@ -242,60 +166,6 @@ class FilterGridPage extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DecoratedFilterChip extends StatelessWidget {
|
enum ChipAction {
|
||||||
final CollectionSource source;
|
sort,
|
||||||
final CollectionFilter filter;
|
|
||||||
final ImageEntry entry;
|
|
||||||
final FilterCallback onPressed;
|
|
||||||
|
|
||||||
const DecoratedFilterChip({
|
|
||||||
@required this.source,
|
|
||||||
@required this.filter,
|
|
||||||
@required this.entry,
|
|
||||||
@required this.onPressed,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
Widget backgroundImage;
|
|
||||||
if (entry != null) {
|
|
||||||
backgroundImage = entry.isSvg
|
|
||||||
? ThumbnailVectorImage(
|
|
||||||
entry: entry,
|
|
||||||
extent: FilterGridPage.maxCrossAxisExtent,
|
|
||||||
)
|
|
||||||
: ThumbnailRasterImage(
|
|
||||||
entry: entry,
|
|
||||||
extent: FilterGridPage.maxCrossAxisExtent,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return AvesFilterChip(
|
|
||||||
filter: filter,
|
|
||||||
showGenericIcon: false,
|
|
||||||
background: backgroundImage,
|
|
||||||
details: _buildDetails(filter),
|
|
||||||
onPressed: onPressed,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildDetails(CollectionFilter filter) {
|
|
||||||
final count = Text(
|
|
||||||
'${source.count(filter)}',
|
|
||||||
style: TextStyle(color: FilterGridPage.detailColor),
|
|
||||||
);
|
|
||||||
return filter is AlbumFilter && androidFileUtils.isOnRemovableStorage(filter.album)
|
|
||||||
? Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
AIcons.removableStorage,
|
|
||||||
size: 16,
|
|
||||||
color: FilterGridPage.detailColor,
|
|
||||||
),
|
|
||||||
SizedBox(width: 8),
|
|
||||||
count,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: count;
|
|
||||||
}
|
|
||||||
}
|
}
|
30
lib/widgets/filter_grids/tags_page.dart
Normal file
30
lib/widgets/filter_grids/tags_page.dart
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import 'package:aves/model/filters/tag.dart';
|
||||||
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
|
import 'package:aves/model/source/tag.dart';
|
||||||
|
import 'package:aves/widgets/album/empty.dart';
|
||||||
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
|
import 'package:aves/widgets/filter_grids/filter_grid_page.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class TagListPage extends StatelessWidget {
|
||||||
|
final CollectionSource source;
|
||||||
|
|
||||||
|
const TagListPage({@required this.source});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return StreamBuilder(
|
||||||
|
stream: source.eventBus.on<TagsChangedEvent>(),
|
||||||
|
builder: (context, snapshot) => FilterNavigationPage(
|
||||||
|
source: source,
|
||||||
|
title: 'Tags',
|
||||||
|
filterEntries: source.getTagEntries(),
|
||||||
|
filterBuilder: (s) => TagFilter(s),
|
||||||
|
emptyBuilder: () => EmptyContent(
|
||||||
|
icon: AIcons.tag,
|
||||||
|
text: 'No tags',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/model/settings.dart';
|
||||||
import 'package:aves/widgets/album/empty.dart';
|
import 'package:aves/widgets/album/empty.dart';
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
import 'package:aves/widgets/common/image_providers/thumbnail_provider.dart';
|
import 'package:aves/widgets/common/image_providers/thumbnail_provider.dart';
|
||||||
|
@ -77,12 +77,13 @@ class ImageView extends StatelessWidget {
|
||||||
|
|
||||||
Widget child;
|
Widget child;
|
||||||
if (entry.isSvg) {
|
if (entry.isSvg) {
|
||||||
|
final colorFilter = ColorFilter.mode(Color(settings.svgBackground), BlendMode.dstOver);
|
||||||
child = PhotoView.customChild(
|
child = PhotoView.customChild(
|
||||||
child: SvgPicture(
|
child: SvgPicture(
|
||||||
UriPicture(
|
UriPicture(
|
||||||
uri: entry.uri,
|
uri: entry.uri,
|
||||||
mimeType: entry.mimeType,
|
mimeType: entry.mimeType,
|
||||||
colorFilter: Constants.svgColorFilter,
|
colorFilter: colorFilter,
|
||||||
),
|
),
|
||||||
placeholderBuilder: (context) => loadingBuilder(context, fastThumbnailProvider),
|
placeholderBuilder: (context) => loadingBuilder(context, fastThumbnailProvider),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:aves/model/settings.dart';
|
import 'package:aves/model/settings.dart';
|
||||||
import 'package:aves/services/android_app_service.dart';
|
import 'package:aves/services/android_app_service.dart';
|
||||||
import 'package:aves/widgets/common/action_delegates/map_style_dialog.dart';
|
import 'package:aves/widgets/common/action_delegates/map_style_dialog.dart';
|
||||||
|
import 'package:aves/widgets/common/borders.dart';
|
||||||
import 'package:aves/widgets/common/fx/blurred.dart';
|
import 'package:aves/widgets/common/fx/blurred.dart';
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
import 'package:aves/widgets/fullscreen/info/location_section.dart';
|
import 'package:aves/widgets/fullscreen/info/location_section.dart';
|
||||||
|
@ -115,10 +116,10 @@ class MapOverlayButton extends StatelessWidget {
|
||||||
return BlurredOval(
|
return BlurredOval(
|
||||||
child: Material(
|
child: Material(
|
||||||
type: MaterialType.circle,
|
type: MaterialType.circle,
|
||||||
color: FullscreenOverlay.backgroundColor,
|
color: kOverlayBackgroundColor,
|
||||||
child: Ink(
|
child: Ink(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: FullscreenOverlay.buildBorder(context),
|
border: AvesCircleBorder.build(context),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
|
|
|
@ -80,7 +80,7 @@ class _FullscreenBottomOverlayState extends State<FullscreenBottomOverlay> {
|
||||||
final overlayContentMaxWidth = mqWidth - viewPadding.horizontal - innerPadding.horizontal;
|
final overlayContentMaxWidth = mqWidth - viewPadding.horizontal - innerPadding.horizontal;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
color: FullscreenOverlay.backgroundColor,
|
color: kOverlayBackgroundColor,
|
||||||
padding: viewInsets + viewPadding.copyWith(top: 0),
|
padding: viewInsets + viewPadding.copyWith(top: 0),
|
||||||
child: FutureBuilder<OverlayMetadata>(
|
child: FutureBuilder<OverlayMetadata>(
|
||||||
future: _detailLoader,
|
future: _detailLoader,
|
||||||
|
|
|
@ -1,17 +1,8 @@
|
||||||
|
import 'package:aves/widgets/common/borders.dart';
|
||||||
import 'package:aves/widgets/common/fx/blurred.dart';
|
import 'package:aves/widgets/common/fx/blurred.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class FullscreenOverlay {
|
const kOverlayBackgroundColor = Colors.black26;
|
||||||
static const backgroundColor = Colors.black26;
|
|
||||||
|
|
||||||
static BoxBorder buildBorder(BuildContext context) {
|
|
||||||
final subPixel = MediaQuery.of(context).devicePixelRatio > 2;
|
|
||||||
return Border.all(
|
|
||||||
color: Colors.white30,
|
|
||||||
width: subPixel ? 0.5 : 1.0,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class OverlayButton extends StatelessWidget {
|
class OverlayButton extends StatelessWidget {
|
||||||
final Animation<double> scale;
|
final Animation<double> scale;
|
||||||
|
@ -26,10 +17,10 @@ class OverlayButton extends StatelessWidget {
|
||||||
child: BlurredOval(
|
child: BlurredOval(
|
||||||
child: Material(
|
child: Material(
|
||||||
type: MaterialType.circle,
|
type: MaterialType.circle,
|
||||||
color: FullscreenOverlay.backgroundColor,
|
color: kOverlayBackgroundColor,
|
||||||
child: Ink(
|
child: Ink(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: FullscreenOverlay.buildBorder(context),
|
border: AvesCircleBorder.build(context),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
child: child,
|
child: child,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/services/android_app_service.dart';
|
import 'package:aves/services/android_app_service.dart';
|
||||||
import 'package:aves/utils/durations.dart';
|
import 'package:aves/utils/durations.dart';
|
||||||
import 'package:aves/utils/time_utils.dart';
|
import 'package:aves/utils/time_utils.dart';
|
||||||
|
import 'package:aves/widgets/common/borders.dart';
|
||||||
import 'package:aves/widgets/common/fx/blurred.dart';
|
import 'package:aves/widgets/common/fx/blurred.dart';
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
import 'package:aves/widgets/fullscreen/overlay/common.dart';
|
import 'package:aves/widgets/fullscreen/overlay/common.dart';
|
||||||
|
@ -180,8 +181,8 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 16) + EdgeInsets.only(bottom: 16),
|
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 16) + EdgeInsets.only(bottom: 16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: FullscreenOverlay.backgroundColor,
|
color: kOverlayBackgroundColor,
|
||||||
border: FullscreenOverlay.buildBorder(context),
|
border: AvesCircleBorder.build(context),
|
||||||
borderRadius: BorderRadius.circular(progressBarBorderRadius),
|
borderRadius: BorderRadius.circular(progressBarBorderRadius),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
|
@ -8,7 +8,7 @@ import 'package:aves/utils/android_file_utils.dart';
|
||||||
import 'package:aves/widgets/album/collection_page.dart';
|
import 'package:aves/widgets/album/collection_page.dart';
|
||||||
import 'package:aves/widgets/common/data_providers/media_store_collection_provider.dart';
|
import 'package:aves/widgets/common/data_providers/media_store_collection_provider.dart';
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
import 'package:aves/widgets/filter_grid_page.dart';
|
import 'package:aves/widgets/filter_grids/albums_page.dart';
|
||||||
import 'package:aves/widgets/fullscreen/fullscreen_page.dart';
|
import 'package:aves/widgets/fullscreen/fullscreen_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
@ -47,7 +47,8 @@ class _HomePageState extends State<HomePage> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await androidFileUtils.init(); // 170ms
|
await androidFileUtils.init();
|
||||||
|
unawaited(androidFileUtils.initAppNames());
|
||||||
|
|
||||||
final intentData = await ViewerService.getIntentData();
|
final intentData = await ViewerService.getIntentData();
|
||||||
if (intentData != null) {
|
if (intentData != null) {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'package:aves/utils/constants.dart';
|
||||||
import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart';
|
import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart';
|
||||||
import 'package:aves/widgets/settings/coordinate_format.dart';
|
import 'package:aves/widgets/settings/coordinate_format.dart';
|
||||||
import 'package:aves/widgets/settings/launch_page.dart';
|
import 'package:aves/widgets/settings/launch_page.dart';
|
||||||
|
import 'package:aves/widgets/settings/svg_background.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class SettingsPage extends StatelessWidget {
|
class SettingsPage extends StatelessWidget {
|
||||||
|
@ -27,6 +28,14 @@ class SettingsPage extends StatelessWidget {
|
||||||
Flexible(child: LaunchPageSelector()),
|
Flexible(child: LaunchPageSelector()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text('SVG background:'),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Flexible(child: SvgBackgroundSelector()),
|
||||||
|
],
|
||||||
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
|
43
lib/widgets/settings/svg_background.dart
Normal file
43
lib/widgets/settings/svg_background.dart
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import 'package:aves/model/settings.dart';
|
||||||
|
import 'package:aves/widgets/common/borders.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class SvgBackgroundSelector extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_SvgBackgroundSelectorState createState() => _SvgBackgroundSelectorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SvgBackgroundSelectorState extends State<SvgBackgroundSelector> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
const radius = 24.0;
|
||||||
|
return DropdownButton<int>(
|
||||||
|
items: [0xFFFFFFFF, 0xFF000000, 0x00000000].map((selected) {
|
||||||
|
return DropdownMenuItem(
|
||||||
|
value: selected,
|
||||||
|
child: Container(
|
||||||
|
height: radius,
|
||||||
|
width: radius,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Color(selected),
|
||||||
|
border: AvesCircleBorder.build(context),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: selected == 0
|
||||||
|
? Icon(
|
||||||
|
Icons.clear,
|
||||||
|
size: 20,
|
||||||
|
color: Colors.white30,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
value: settings.svgBackground,
|
||||||
|
onChanged: (selected) {
|
||||||
|
settings.svgBackground = selected;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
61
pubspec.lock
61
pubspec.lock
|
@ -42,7 +42,7 @@ packages:
|
||||||
name: async
|
name: async
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.2"
|
version: "2.4.1"
|
||||||
barcode:
|
barcode:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -64,13 +64,6 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0+1"
|
version: "2.2.0+1"
|
||||||
characters:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: characters
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.0"
|
|
||||||
charcode:
|
charcode:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -112,7 +105,7 @@ packages:
|
||||||
name: collection
|
name: collection
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.14.13"
|
version: "1.14.12"
|
||||||
console_log_handler:
|
console_log_handler:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -133,14 +126,14 @@ packages:
|
||||||
name: coverage
|
name: coverage
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.14.0"
|
version: "0.13.11"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: crypto
|
name: crypto
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.5"
|
version: "2.1.4"
|
||||||
csslib:
|
csslib:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -173,20 +166,13 @@ packages:
|
||||||
url: "git://github.com/deckerst/expansion_tile_card.git"
|
url: "git://github.com/deckerst/expansion_tile_card.git"
|
||||||
source: git
|
source: git
|
||||||
version: "1.0.3"
|
version: "1.0.3"
|
||||||
fake_async:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: fake_async
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.1.0"
|
|
||||||
file:
|
file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file
|
name: file
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.2.1"
|
version: "5.1.0"
|
||||||
firebase_crashlytics:
|
firebase_crashlytics:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -275,7 +261,7 @@ packages:
|
||||||
name: flutter_svg
|
name: flutter_svg
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.18.0"
|
version: "0.17.4"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -353,7 +339,7 @@ packages:
|
||||||
name: image
|
name: image
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.14"
|
version: "2.1.12"
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -381,7 +367,7 @@ packages:
|
||||||
name: json_rpc_2
|
name: json_rpc_2
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.1.0"
|
||||||
latlong:
|
latlong:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -416,7 +402,7 @@ packages:
|
||||||
name: matcher
|
name: matcher
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.8"
|
version: "0.12.6"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -438,6 +424,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.7"
|
version: "0.9.7"
|
||||||
|
multi_server_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: multi_server_socket
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
nested:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -500,7 +493,7 @@ packages:
|
||||||
name: path
|
name: path
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.7.0"
|
version: "1.6.4"
|
||||||
path_drawing:
|
path_drawing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -584,7 +577,7 @@ packages:
|
||||||
name: petitparser
|
name: petitparser
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.4"
|
version: "2.4.0"
|
||||||
photo_view:
|
photo_view:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -642,7 +635,7 @@ packages:
|
||||||
name: process
|
name: process
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.13"
|
version: "3.0.12"
|
||||||
proj4dart:
|
proj4dart:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -801,7 +794,7 @@ packages:
|
||||||
name: stack_trace
|
name: stack_trace
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.5"
|
version: "1.9.3"
|
||||||
stream_channel:
|
stream_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -857,21 +850,21 @@ packages:
|
||||||
name: test
|
name: test
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.15.2"
|
version: "1.14.4"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.17"
|
version: "0.2.15"
|
||||||
test_core:
|
test_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_core
|
name: test_core
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.10"
|
version: "0.3.4"
|
||||||
transparent_image:
|
transparent_image:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -892,7 +885,7 @@ packages:
|
||||||
name: typed_data
|
name: typed_data
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.1.6"
|
||||||
unicode:
|
unicode:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1025,7 +1018,7 @@ packages:
|
||||||
name: xml
|
name: xml
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.0"
|
version: "3.6.1"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1034,5 +1027,5 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.1"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.9.0-14.0.dev <3.0.0"
|
dart: ">=2.8.0 <3.0.0"
|
||||||
flutter: ">=1.18.0-6.0.pre <2.0.0"
|
flutter: ">=1.17.0 <2.0.0"
|
||||||
|
|
|
@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 1.1.7+19
|
version: 1.1.8+20
|
||||||
|
|
||||||
# video_player (as of v0.10.8+2, backed by ExoPlayer):
|
# video_player (as of v0.10.8+2, backed by ExoPlayer):
|
||||||
# - does not support content URIs (by default, but trivial by fork)
|
# - does not support content URIs (by default, but trivial by fork)
|
||||||
|
|
1
shaders_1.17.5.sksl.json
Normal file
1
shaders_1.17.5.sksl.json
Normal file
File diff suppressed because one or more lines are too long
|
@ -68,7 +68,7 @@ void groupCollection() {
|
||||||
await driver.tap(find.byValueKey('menu-group'));
|
await driver.tap(find.byValueKey('menu-group'));
|
||||||
await driver.waitUntilNoTransientCallbacks();
|
await driver.waitUntilNoTransientCallbacks();
|
||||||
|
|
||||||
await driver.tap(find.byValueKey(GroupFactor.album.toString()));
|
await driver.tap(find.byValueKey(EntryGroupFactor.album.toString()));
|
||||||
await driver.tap(find.byValueKey('apply-button'));
|
await driver.tap(find.byValueKey('apply-button'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ void sortCollection() {
|
||||||
await driver.tap(find.byValueKey('menu-sort'));
|
await driver.tap(find.byValueKey('menu-sort'));
|
||||||
await driver.waitUntilNoTransientCallbacks();
|
await driver.waitUntilNoTransientCallbacks();
|
||||||
|
|
||||||
await driver.tap(find.byValueKey(SortFactor.date.toString()));
|
await driver.tap(find.byValueKey(EntrySortFactor.date.toString()));
|
||||||
await driver.tap(find.byValueKey('apply-button'));
|
await driver.tap(find.byValueKey('apply-button'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue