cities -> places

This commit is contained in:
Thibault Deckers 2020-04-13 13:17:13 +09:00
parent fd5bb222d7
commit 48133d0bb8
9 changed files with 249 additions and 41 deletions

View file

@ -0,0 +1,208 @@
package deckers.thibault.aves.channelhandlers;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.core.content.FileProvider;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.request.FutureTarget;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.signature.ObjectKey;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import static com.bumptech.glide.request.RequestOptions.centerCropTransform;
public class AppAdapterHandler implements MethodChannel.MethodCallHandler {
public static final String CHANNEL = "deckers.thibault/aves/app";
private Context context;
public AppAdapterHandler(Context context) {
this.context = context;
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
switch (call.method) {
case "getAppIcon": {
new Thread(() -> getAppIcon(call, new MethodResultWrapper(result))).start();
break;
}
case "getAppNames": {
result.success(getAppNames());
break;
}
case "edit": {
String title = call.argument("title");
Uri uri = Uri.parse(call.argument("uri"));
String mimeType = call.argument("mimeType");
edit(title, uri, mimeType);
result.success(null);
break;
}
case "open": {
String title = call.argument("title");
Uri uri = Uri.parse(call.argument("uri"));
String mimeType = call.argument("mimeType");
open(title, uri, mimeType);
result.success(null);
break;
}
case "openMap": {
Uri geoUri = Uri.parse(call.argument("geoUri"));
openMap(geoUri);
result.success(null);
break;
}
case "setAs": {
String title = call.argument("title");
Uri uri = Uri.parse(call.argument("uri"));
String mimeType = call.argument("mimeType");
setAs(title, uri, mimeType);
result.success(null);
break;
}
case "share": {
String title = call.argument("title");
Uri uri = Uri.parse(call.argument("uri"));
String mimeType = call.argument("mimeType");
share(title, uri, mimeType);
result.success(null);
break;
}
default:
result.notImplemented();
break;
}
}
private Map<String, String> getAppNames() {
Map<String, String> nameMap = new HashMap<>();
Intent intent = new Intent(Intent.ACTION_MAIN, null);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
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);
for (ResolveInfo resolveInfo : resolveInfoList) {
ApplicationInfo applicationInfo = resolveInfo.activityInfo.applicationInfo;
boolean isSystemPackage = (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
if (!isSystemPackage) {
String appName = String.valueOf(packageManager.getApplicationLabel(applicationInfo));
nameMap.put(appName, applicationInfo.packageName);
}
}
return nameMap;
}
private void getAppIcon(MethodCall call, MethodChannel.Result result) {
String packageName = call.argument("packageName");
Integer size = call.argument("size");
if (packageName == null || size == null) {
result.error("getAppIcon-args", "failed because of missing arguments", null);
return;
}
byte[] data = null;
try {
int iconResourceId = context.getPackageManager().getApplicationInfo(packageName, 0).icon;
Uri uri = new Uri.Builder()
.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
.authority(packageName)
.path(String.valueOf(iconResourceId))
.build();
// add signature to ignore cache for images which got modified but kept the same URI
Key signature = new ObjectKey(packageName + size);
RequestOptions options = new RequestOptions()
.signature(signature)
.override(size, size);
FutureTarget<Bitmap> target = Glide.with(context)
.asBitmap()
.apply(options)
.apply(centerCropTransform())
.load(uri)
.signature(signature)
.submit(size, size);
try {
Bitmap bmp = target.get();
if (bmp != null) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bmp.compress(Bitmap.CompressFormat.PNG, 100, stream);
data = stream.toByteArray();
}
} catch (Exception e) {
e.printStackTrace();
}
Glide.with(context).clear(target);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return;
}
if (data != null) {
result.success(data);
} else {
result.error("getAppIcon-null", "failed to get icon for packageName=" + packageName, null);
}
}
private void edit(String title, Uri uri, String mimeType) {
Intent intent = new Intent(Intent.ACTION_EDIT);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.setDataAndType(uri, mimeType);
context.startActivity(Intent.createChooser(intent, title));
}
private void open(String title, Uri uri, String mimeType) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri, mimeType);
context.startActivity(Intent.createChooser(intent, title));
}
private void openMap(Uri geoUri) {
Intent intent = new Intent(Intent.ACTION_VIEW, geoUri);
if (intent.resolveActivity(context.getPackageManager()) != null) {
context.startActivity(intent);
}
}
private void setAs(String title, Uri uri, String mimeType) {
Intent intent = new Intent(Intent.ACTION_ATTACH_DATA);
intent.setDataAndType(uri, mimeType);
context.startActivity(Intent.createChooser(intent, title));
}
private void share(String title, Uri uri, String mimeType) {
Intent intent = new Intent(Intent.ACTION_SEND);
if (ContentResolver.SCHEME_FILE.equalsIgnoreCase(uri.getScheme())) {
String path = uri.getPath();
if (path == null) return;
String applicationId = context.getApplicationContext().getPackageName();
Uri apkUri = FileProvider.getUriForFile(context, applicationId + ".fileprovider", new File(path));
intent.putExtra(Intent.EXTRA_STREAM, apkUri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
intent.putExtra(Intent.EXTRA_STREAM, uri);
}
intent.setType(mimeType);
context.startActivity(Intent.createChooser(intent, title));
}
}

View file

@ -12,8 +12,8 @@ class CollectionSource {
final EventBus _eventBus = EventBus(); final EventBus _eventBus = EventBus();
List<String> sortedAlbums = List.unmodifiable(const Iterable.empty()); List<String> sortedAlbums = List.unmodifiable(const Iterable.empty());
List<String> sortedCities = List.unmodifiable(const Iterable.empty());
List<String> sortedCountries = List.unmodifiable(const Iterable.empty()); List<String> sortedCountries = List.unmodifiable(const Iterable.empty());
List<String> sortedPlaces = List.unmodifiable(const Iterable.empty());
List<String> sortedTags = List.unmodifiable(const Iterable.empty()); List<String> sortedTags = List.unmodifiable(const Iterable.empty());
List<ImageEntry> get entries => List.unmodifiable(_rawEntries); List<ImageEntry> get entries => List.unmodifiable(_rawEntries);
@ -125,7 +125,7 @@ class CollectionSource {
final locations = _rawEntries.where((entry) => entry.isLocated).map((entry) => entry.addressDetails); final locations = _rawEntries.where((entry) => entry.isLocated).map((entry) => entry.addressDetails);
final lister = (String Function(AddressDetails a) f) => List<String>.unmodifiable(locations.map(f).where((s) => s != null && s.isNotEmpty).toSet().toList()..sort(compareAsciiUpperCase)); final lister = (String Function(AddressDetails a) f) => List<String>.unmodifiable(locations.map(f).where((s) => s != null && s.isNotEmpty).toSet().toList()..sort(compareAsciiUpperCase));
sortedCountries = lister((address) => '${address.countryName};${address.countryCode}'); sortedCountries = lister((address) => '${address.countryName};${address.countryCode}');
sortedCities = lister((address) => address.city); sortedPlaces = lister((address) => address.place);
} }
void addAll(Iterable<ImageEntry> entries) { void addAll(Iterable<ImageEntry> entries) {

View file

@ -17,7 +17,7 @@ class LocationFilter extends CollectionFilter {
} }
@override @override
bool filter(ImageEntry entry) => entry.isLocated && ((level == LocationLevel.country && entry.addressDetails.countryName == _location) || (level == LocationLevel.city && entry.addressDetails.city == _location)); bool filter(ImageEntry entry) => entry.isLocated && ((level == LocationLevel.country && entry.addressDetails.countryName == _location) || (level == LocationLevel.place && entry.addressDetails.place == _location));
@override @override
String get label => _location; String get label => _location;
@ -50,4 +50,4 @@ class LocationFilter extends CollectionFilter {
} }
} }
enum LocationLevel { city, country } enum LocationLevel { place, country }

View file

@ -110,7 +110,7 @@ class AddressDetails {
final int contentId; final int contentId;
final String addressLine, countryCode, countryName, adminArea, locality; final String addressLine, countryCode, countryName, adminArea, locality;
String get city => locality != null && locality.isNotEmpty ? locality : adminArea; String get place => locality != null && locality.isNotEmpty ? locality : adminArea;
AddressDetails({ AddressDetails({
this.contentId, this.contentId,

View file

View file

@ -31,7 +31,7 @@ class CollectionDrawer extends StatefulWidget {
} }
class _CollectionDrawerState extends State<CollectionDrawer> { class _CollectionDrawerState extends State<CollectionDrawer> {
bool _albumsExpanded = false, _citiesExpanded = false, _countriesExpanded = false, _tagsExpanded = false; bool _albumsExpanded = false, _placesExpanded = false, _countriesExpanded = false, _tagsExpanded = false;
CollectionSource get source => widget.source; CollectionSource get source => widget.source;
@ -153,8 +153,8 @@ class _CollectionDrawerState extends State<CollectionDrawer> {
break; break;
} }
} }
final cities = source.sortedCities;
final countries = source.sortedCountries; final countries = source.sortedCountries;
final places = source.sortedPlaces;
final tags = source.sortedTags; final tags = source.sortedTags;
final drawerItems = <Widget>[ final drawerItems = <Widget>[
@ -193,28 +193,6 @@ class _CollectionDrawerState extends State<CollectionDrawer> {
], ],
), ),
), ),
if (cities.isNotEmpty)
SafeArea(
top: false,
bottom: false,
child: ExpansionTile(
leading: const Icon(AIcons.location),
title: Row(
children: [
const Text('Cities'),
const Spacer(),
Text(
'${cities.length}',
style: TextStyle(
color: (_citiesExpanded ? Theme.of(context).accentColor : Colors.white).withOpacity(.6),
),
),
],
),
onExpansionChanged: (expanded) => setState(() => _citiesExpanded = expanded),
children: cities.map((s) => buildLocationEntry(LocationLevel.city, s)).toList(),
),
),
if (countries.isNotEmpty) if (countries.isNotEmpty)
SafeArea( SafeArea(
top: false, top: false,
@ -237,6 +215,28 @@ class _CollectionDrawerState extends State<CollectionDrawer> {
children: countries.map((s) => buildLocationEntry(LocationLevel.country, s)).toList(), children: countries.map((s) => buildLocationEntry(LocationLevel.country, s)).toList(),
), ),
), ),
if (places.isNotEmpty)
SafeArea(
top: false,
bottom: false,
child: ExpansionTile(
leading: const Icon(AIcons.location),
title: Row(
children: [
const Text('Places'),
const Spacer(),
Text(
'${places.length}',
style: TextStyle(
color: (_placesExpanded ? Theme.of(context).accentColor : Colors.white).withOpacity(.6),
),
),
],
),
onExpansionChanged: (expanded) => setState(() => _placesExpanded = expanded),
children: places.map((s) => buildLocationEntry(LocationLevel.place, s)).toList(),
),
),
if (tags.isNotEmpty) if (tags.isNotEmpty)
SafeArea( SafeArea(
top: false, top: false,

View file

@ -71,13 +71,13 @@ class ImageSearchDelegate extends SearchDelegate<CollectionFilter> {
), ),
_buildFilterRow( _buildFilterRow(
context: context, context: context,
title: 'Cities', title: 'Countries',
filters: source.sortedCities.where(containQuery).map((s) => LocationFilter(LocationLevel.city, s)), filters: source.sortedCountries.where(containQuery).map((s) => LocationFilter(LocationLevel.country, s)),
), ),
_buildFilterRow( _buildFilterRow(
context: context, context: context,
title: 'Countries', title: 'Places',
filters: source.sortedCountries.where(containQuery).map((s) => LocationFilter(LocationLevel.country, s)), filters: source.sortedPlaces.where(containQuery).map((s) => LocationFilter(LocationLevel.place, s)),
), ),
_buildFilterRow( _buildFilterRow(
context: context, context: context,

View file

@ -82,8 +82,8 @@ class _LocationSectionState extends State<LocationSection> {
location = address.addressLine; location = address.addressLine;
final country = address.countryName; final country = address.countryName;
if (country != null && country.isNotEmpty) filters.add(LocationFilter(LocationLevel.country, '$country;${address.countryCode}')); if (country != null && country.isNotEmpty) filters.add(LocationFilter(LocationLevel.country, '$country;${address.countryCode}'));
final city = address.city; final place = address.place;
if (city != null && city.isNotEmpty) filters.add(LocationFilter(LocationLevel.city, city)); if (place != null && place.isNotEmpty) filters.add(LocationFilter(LocationLevel.place, place));
} else if (entry.hasGps) { } else if (entry.hasGps) {
location = toDMS(entry.latLng).join(', '); location = toDMS(entry.latLng).join(', ');
} }

View file

@ -20,7 +20,7 @@ import 'package:percent_indicator/linear_percent_indicator.dart';
class StatsPage extends StatelessWidget { class StatsPage extends StatelessWidget {
final CollectionLens collection; final CollectionLens collection;
final Map<String, int> entryCountPerCity = {}, entryCountPerCountry = {}, entryCountPerTag = {}; final Map<String, int> entryCountPerCountry = {}, entryCountPerPlace = {}, entryCountPerTag = {};
List<ImageEntry> get entries => collection.sortedEntries; List<ImageEntry> get entries => collection.sortedEntries;
@ -30,15 +30,15 @@ class StatsPage extends StatelessWidget {
entries.forEach((entry) { entries.forEach((entry) {
if (entry.isLocated) { if (entry.isLocated) {
final address = entry.addressDetails; final address = entry.addressDetails;
final city = address.city;
if (city != null && city.isNotEmpty) {
entryCountPerCity[city] = (entryCountPerCity[city] ?? 0) + 1;
}
var country = address.countryName; var country = address.countryName;
if (country != null && country.isNotEmpty) { if (country != null && country.isNotEmpty) {
country += ';${address.countryCode}'; country += ';${address.countryCode}';
entryCountPerCountry[country] = (entryCountPerCountry[country] ?? 0) + 1; entryCountPerCountry[country] = (entryCountPerCountry[country] ?? 0) + 1;
} }
final place = address.place;
if (place != null && place.isNotEmpty) {
entryCountPerPlace[place] = (entryCountPerPlace[place] ?? 0) + 1;
}
} }
entry.xmpSubjects.forEach((tag) { entry.xmpSubjects.forEach((tag) {
entryCountPerTag[tag] = (entryCountPerTag[tag] ?? 0) + 1; entryCountPerTag[tag] = (entryCountPerTag[tag] ?? 0) + 1;
@ -87,8 +87,8 @@ class StatsPage extends StatelessWidget {
], ],
), ),
), ),
..._buildTopFilters(context, 'Top cities', entryCountPerCity, (s) => LocationFilter(LocationLevel.city, s)),
..._buildTopFilters(context, 'Top countries', entryCountPerCountry, (s) => LocationFilter(LocationLevel.country, s)), ..._buildTopFilters(context, 'Top countries', entryCountPerCountry, (s) => LocationFilter(LocationLevel.country, s)),
..._buildTopFilters(context, 'Top places', entryCountPerPlace, (s) => LocationFilter(LocationLevel.place, s)),
..._buildTopFilters(context, 'Top tags', entryCountPerTag, (s) => TagFilter(s)), ..._buildTopFilters(context, 'Top tags', entryCountPerTag, (s) => TagFilter(s)),
], ],
); );