snapshot of https://github.com/flutter/samples.git (main, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Main Branch CI / Test Flutter beta on ubuntu-latest (push) Has been skipped
Main Branch CI / Test Flutter stable on ubuntu-latest (push) Has been skipped
Main Branch CI / Test Flutter beta on macos-latest (push) Has been cancelled
Main Branch CI / Test Flutter stable on macos-latest (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-16 01:43:26 +05:00
commit 9ed6a80afa
2886 changed files with 140184 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Web related
lib/generated_plugin_registrant.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

View File

@@ -0,0 +1,36 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "db7ef5bf9f59442b0e200a90587e8fa5e0c6336a"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a
base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a
- platform: linux
create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a
base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a
- platform: macos
create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a
base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a
- platform: windows
create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a
base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View File

@@ -0,0 +1,66 @@
# Photo Search app
This desktop application enables you to search
[Unsplash](https://unsplash.com/) for photographs that interest you.
To use it, you need to add an **Access Key** from
[Unsplash API](https://unsplash.com/developers) to
`lib/unsplash_access_key.dart`.
This sample works on Windows, macOS and Linux.
## A quick tour of the code
This Flutter project builds a desktop application. It utilises the following
desktop specific plugins:
- [fluent_ui] for the Fluent UI widget set
- [file_chooser] to enable the application user to select where to save a photo
from the Unsplash API.
- [menubar] for exposing Image Search functionality through the menu bar.
- [url_launcher] to open external links.
The Unsplash API client entry point is in the [Unsplash] class, and is built
atop [http], [built_value] and [built_collection] for JSON Rest API access.
## Deploying to the Microsoft Store
This sample uses Yehuda Kremer's [MSIX pub package] to bundle up the Windows
release build for distribution to the [Microsoft Store]. Microsoft maintains
a [plethora of documentation][ms_store_publishing_doc] on deploying to the
Microsoft Store.
See the `msix_config` stanza in this sample's `pubspec.yaml` for an
example configuration. Make sure the `display_name`, `publisher_display_name`,
`publisher` and `identity_name` attributes match the settings in your
Microsoft Partner Center application submission.
## macOS Network and File entitlements
To access the network, macOS requires applications enable the
[com.apple.security.network.client entitlement][macOS-client]. For this
sample, this entitlement is required to access the Unsplash API.
Likewise, to save a Photo to the local file system using the `file_chooser` plugin requires the
[com.apple.security.files.user-selected.read-write entitlement][macOS-read-write].
Please see [macOS Signing and Security][macOS-security] for more detail.
[Unsplash]: lib/src/unsplash/unsplash.dart
[built_collection]: https://pub.dev/packages/built_collection
[built_value]: https://pub.dev/packages/built_value
[file_chooser]: https://github.com/google/flutter-desktop-embedding/tree/master/plugins/file_chooser
[fluent_ui]: https://pub.dev/packages/fluent_ui
[flutter_channels]: https://github.com/flutter/flutter/blob/master/docs/releases/Flutter-build-release-channels.md
[http]: https://pub.dev/packages/http
[macOS-client]: https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_network_client
[macOS-read-write]: https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_files_user-selected_read-write
[macOS-security]: https://github.com/google/flutter-desktop-embedding/blob/master/macOS-Security.md
[menubar]: https://github.com/google/flutter-desktop-embedding/tree/master/plugins/menubar
[setup documentation]: https://flutter.dev/desktop#set-up
[url_launcher]: https://pub.dev/packages/url_launcher
[MSIX pub package]: https://pub.dev/packages/msix
[Microsoft Store]: https://www.microsoft.com/en-au/p/flutter-desktop-photo-search/9nh719dxcpj4
[ms_store_publishing_doc]: https://docs.microsoft.com/en-us/windows/uwp/publish/

View File

@@ -0,0 +1,5 @@
include: ../../analysis_options.yaml
linter:
rules:
sort_pub_dependencies: true

View File

@@ -0,0 +1,119 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/services.dart';
import 'package:logging/logging.dart';
import 'package:menubar/menubar.dart' as menubar;
import 'package:provider/provider.dart';
import 'src/model/photo_search_model.dart';
import 'src/unsplash/unsplash.dart';
import 'src/widgets/photo_search_dialog.dart';
import 'src/widgets/policy_dialog.dart';
import 'src/widgets/unsplash_notice.dart';
import 'src/widgets/unsplash_search_content.dart';
import 'unsplash_access_key.dart';
void main() {
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((rec) {
// ignore: avoid_print
print(
'${rec.loggerName} ${rec.level.name}: ${rec.time}: ${rec.message}',
);
});
if (unsplashAccessKey.isEmpty) {
Logger('main').severe(
'Unsplash Access Key is required. '
'Please add to `lib/unsplash_access_key.dart`.',
);
exit(1);
}
// Removed setupWindow() call
runApp(
ChangeNotifierProvider<PhotoSearchModel>(
create: (context) =>
PhotoSearchModel(Unsplash(accessKey: unsplashAccessKey)),
child: const UnsplashSearchApp(),
),
);
}
class UnsplashSearchApp extends StatelessWidget {
const UnsplashSearchApp({super.key});
@override
Widget build(BuildContext context) {
return const FluentApp(
title: 'Photo Search',
home: UnsplashHomePage(title: 'Photo Search'),
);
}
}
class UnsplashHomePage extends StatelessWidget {
const UnsplashHomePage({required this.title, super.key});
final String title;
@override
Widget build(BuildContext context) {
final photoSearchModel = Provider.of<PhotoSearchModel>(context);
menubar.setApplicationMenu([
menubar.NativeSubmenu(
label: 'Search',
children: [
menubar.NativeMenuItem(
label: 'Search…',
onSelected: () {
showDialog<void>(
context: context,
builder: (context) => PhotoSearchDialog(
callback: photoSearchModel.addSearch,
),
);
},
),
if (!Platform.isMacOS)
menubar.NativeMenuItem(
label: 'Quit',
onSelected: () {
SystemNavigator.pop();
},
),
],
),
menubar.NativeSubmenu(
label: 'About',
children: [
menubar.NativeMenuItem(
label: 'About',
onSelected: () {
showDialog<void>(
context: context,
builder: (context) => const PolicyDialog(),
);
},
),
],
),
]);
return UnsplashNotice(
child: Container(
color: Colors.white,
child: photoSearchModel.entries.isNotEmpty
? const UnsplashSearchContent()
: const Center(
child: Text('Search for Photos using the Search menu'),
),
),
);
}
}

View File

@@ -0,0 +1,50 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import '../unsplash/photo.dart';
import '../unsplash/unsplash.dart';
import 'search.dart';
class SearchEntry {
const SearchEntry(this.query, this.photos, this.model);
final String query;
final List<Photo> photos;
final PhotoSearchModel model;
}
class PhotoSearchModel extends ChangeNotifier {
PhotoSearchModel(this._client);
final Unsplash _client;
List<SearchEntry> get entries => List.unmodifiable(_entries);
final List<SearchEntry> _entries = [];
Photo? get selectedPhoto => _selectedPhoto;
set selectedPhoto(Photo? photo) {
_selectedPhoto = photo;
notifyListeners();
}
Photo? _selectedPhoto;
Future<void> addSearch(String query) async {
final result = await _client.searchPhotos(
query: query,
orientation: SearchPhotosOrientation.portrait,
);
final search = Search((s) {
s
..query = query
..results.addAll(result!.results);
});
_entries.add(SearchEntry(query, search.results.toList(), this));
notifyListeners();
}
Future<Uint8List> download({required Photo photo}) =>
_client.download(photo);
}

View File

@@ -0,0 +1,38 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import '../serializers.dart';
import '../unsplash/photo.dart';
part 'search.g.dart';
abstract class Search implements Built<Search, SearchBuilder> {
factory Search([void Function(SearchBuilder)? updates]) = _$Search;
Search._();
@BuiltValueField(wireName: 'query')
String get query;
@BuiltValueField(wireName: 'results')
BuiltList<Photo> get results;
String toJson() {
return json.encode(serializers.serializeWith(Search.serializer, this));
}
static Search? fromJson(String jsonString) {
return serializers.deserializeWith(
Search.serializer,
json.decode(jsonString),
);
}
static Serializer<Search> get serializer => _$searchSerializer;
}

View File

@@ -0,0 +1,200 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'search.dart';
// **************************************************************************
// BuiltValueGenerator
// **************************************************************************
Serializer<Search> _$searchSerializer = new _$SearchSerializer();
class _$SearchSerializer implements StructuredSerializer<Search> {
@override
final Iterable<Type> types = const [Search, _$Search];
@override
final String wireName = 'Search';
@override
Iterable<Object?> serialize(
Serializers serializers,
Search object, {
FullType specifiedType = FullType.unspecified,
}) {
final result = <Object?>[
'query',
serializers.serialize(
object.query,
specifiedType: const FullType(String),
),
'results',
serializers.serialize(
object.results,
specifiedType: const FullType(BuiltList, const [
const FullType(Photo),
]),
),
];
return result;
}
@override
Search deserialize(
Serializers serializers,
Iterable<Object?> serialized, {
FullType specifiedType = FullType.unspecified,
}) {
final result = new SearchBuilder();
final iterator = serialized.iterator;
while (iterator.moveNext()) {
final key = iterator.current! as String;
iterator.moveNext();
final Object? value = iterator.current;
switch (key) {
case 'query':
result.query =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)!
as String;
break;
case 'results':
result.results.replace(
serializers.deserialize(
value,
specifiedType: const FullType(BuiltList, const [
const FullType(Photo),
]),
)!
as BuiltList<Object?>,
);
break;
}
}
return result.build();
}
}
class _$Search extends Search {
@override
final String query;
@override
final BuiltList<Photo> results;
factory _$Search([void Function(SearchBuilder)? updates]) =>
(new SearchBuilder()..update(updates))._build();
_$Search._({required this.query, required this.results}) : super._() {
BuiltValueNullFieldError.checkNotNull(query, r'Search', 'query');
BuiltValueNullFieldError.checkNotNull(results, r'Search', 'results');
}
@override
Search rebuild(void Function(SearchBuilder) updates) =>
(toBuilder()..update(updates)).build();
@override
SearchBuilder toBuilder() => new SearchBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is Search && query == other.query && results == other.results;
}
@override
int get hashCode {
var _$hash = 0;
_$hash = $jc(_$hash, query.hashCode);
_$hash = $jc(_$hash, results.hashCode);
_$hash = $jf(_$hash);
return _$hash;
}
@override
String toString() {
return (newBuiltValueToStringHelper(r'Search')
..add('query', query)
..add('results', results))
.toString();
}
}
class SearchBuilder implements Builder<Search, SearchBuilder> {
_$Search? _$v;
String? _query;
String? get query => _$this._query;
set query(String? query) => _$this._query = query;
ListBuilder<Photo>? _results;
ListBuilder<Photo> get results =>
_$this._results ??= new ListBuilder<Photo>();
set results(ListBuilder<Photo>? results) => _$this._results = results;
SearchBuilder();
SearchBuilder get _$this {
final $v = _$v;
if ($v != null) {
_query = $v.query;
_results = $v.results.toBuilder();
_$v = null;
}
return this;
}
@override
void replace(Search other) {
ArgumentError.checkNotNull(other, 'other');
_$v = other as _$Search;
}
@override
void update(void Function(SearchBuilder)? updates) {
if (updates != null) updates(this);
}
@override
Search build() => _build();
_$Search _build() {
_$Search _$result;
try {
_$result =
_$v ??
new _$Search._(
query: BuiltValueNullFieldError.checkNotNull(
query,
r'Search',
'query',
),
results: results.build(),
);
} catch (_) {
late String _$failedField;
try {
_$failedField = 'results';
results.build();
} catch (e) {
throw new BuiltValueNestedFieldError(
r'Search',
_$failedField,
e.toString(),
);
}
rethrow;
}
replace(_$result);
return _$result;
}
}
// ignore_for_file: deprecated_member_use_from_same_package,type=lint

View File

@@ -0,0 +1,27 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:built_collection/built_collection.dart';
import 'package:built_value/serializer.dart';
import 'package:built_value/standard_json_plugin.dart';
import 'model/search.dart';
import 'unsplash/api_error.dart';
import 'unsplash/current_user_collections.dart';
import 'unsplash/exif.dart';
import 'unsplash/links.dart';
import 'unsplash/location.dart';
import 'unsplash/photo.dart';
import 'unsplash/position.dart';
import 'unsplash/search_photos_response.dart';
import 'unsplash/tags.dart';
import 'unsplash/urls.dart';
import 'unsplash/user.dart';
part 'serializers.g.dart';
//add all of the built value types that require serialization
@SerializersFor([Search, ApiError, Photo, SearchPhotosResponse])
final Serializers serializers =
(_$serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build();

View File

@@ -0,0 +1,51 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'serializers.dart';
// **************************************************************************
// BuiltValueGenerator
// **************************************************************************
Serializers _$serializers =
(new Serializers().toBuilder()
..add(ApiError.serializer)
..add(CurrentUserCollections.serializer)
..add(Exif.serializer)
..add(Links.serializer)
..add(Location.serializer)
..add(Photo.serializer)
..add(Position.serializer)
..add(Search.serializer)
..add(SearchPhotosResponse.serializer)
..add(Tags.serializer)
..add(Urls.serializer)
..add(User.serializer)
..addBuilderFactory(
const FullType(BuiltList, const [const FullType(Photo)]),
() => new ListBuilder<Photo>(),
)
..addBuilderFactory(
const FullType(BuiltList, const [const FullType(Photo)]),
() => new ListBuilder<Photo>(),
)
..addBuilderFactory(
const FullType(BuiltList, const [const FullType(String)]),
() => new ListBuilder<String>(),
)
..addBuilderFactory(
const FullType(BuiltList, const [const FullType(Tags)]),
() => new ListBuilder<Tags>(),
)
..addBuilderFactory(
const FullType(BuiltList, const [
const FullType(CurrentUserCollections),
]),
() => new ListBuilder<CurrentUserCollections>(),
))
.build();
// ignore_for_file: deprecated_member_use_from_same_package,type=lint

View File

@@ -0,0 +1,37 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import '../serializers.dart';
part 'api_error.g.dart';
abstract class ApiError implements Built<ApiError, ApiErrorBuilder> {
factory ApiError([void Function(ApiErrorBuilder)? updates]) = _$ApiError;
ApiError._();
@BuiltValueField(wireName: 'errors')
BuiltList<String>? get errors;
String toJson() {
return json.encode(
serializers.serializeWith(ApiError.serializer, this),
);
}
static ApiError? fromJson(String jsonString) {
return serializers.deserializeWith(
ApiError.serializer,
json.decode(jsonString),
);
}
static Serializer<ApiError> get serializer => _$apiErrorSerializer;
}

View File

@@ -0,0 +1,171 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'api_error.dart';
// **************************************************************************
// BuiltValueGenerator
// **************************************************************************
Serializer<ApiError> _$apiErrorSerializer = new _$ApiErrorSerializer();
class _$ApiErrorSerializer implements StructuredSerializer<ApiError> {
@override
final Iterable<Type> types = const [ApiError, _$ApiError];
@override
final String wireName = 'ApiError';
@override
Iterable<Object?> serialize(
Serializers serializers,
ApiError object, {
FullType specifiedType = FullType.unspecified,
}) {
final result = <Object?>[];
Object? value;
value = object.errors;
if (value != null) {
result
..add('errors')
..add(
serializers.serialize(
value,
specifiedType: const FullType(BuiltList, const [
const FullType(String),
]),
),
);
}
return result;
}
@override
ApiError deserialize(
Serializers serializers,
Iterable<Object?> serialized, {
FullType specifiedType = FullType.unspecified,
}) {
final result = new ApiErrorBuilder();
final iterator = serialized.iterator;
while (iterator.moveNext()) {
final key = iterator.current! as String;
iterator.moveNext();
final Object? value = iterator.current;
switch (key) {
case 'errors':
result.errors.replace(
serializers.deserialize(
value,
specifiedType: const FullType(BuiltList, const [
const FullType(String),
]),
)!
as BuiltList<Object?>,
);
break;
}
}
return result.build();
}
}
class _$ApiError extends ApiError {
@override
final BuiltList<String>? errors;
factory _$ApiError([void Function(ApiErrorBuilder)? updates]) =>
(new ApiErrorBuilder()..update(updates))._build();
_$ApiError._({this.errors}) : super._();
@override
ApiError rebuild(void Function(ApiErrorBuilder) updates) =>
(toBuilder()..update(updates)).build();
@override
ApiErrorBuilder toBuilder() => new ApiErrorBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is ApiError && errors == other.errors;
}
@override
int get hashCode {
var _$hash = 0;
_$hash = $jc(_$hash, errors.hashCode);
_$hash = $jf(_$hash);
return _$hash;
}
@override
String toString() {
return (newBuiltValueToStringHelper(
r'ApiError',
)..add('errors', errors)).toString();
}
}
class ApiErrorBuilder implements Builder<ApiError, ApiErrorBuilder> {
_$ApiError? _$v;
ListBuilder<String>? _errors;
ListBuilder<String> get errors =>
_$this._errors ??= new ListBuilder<String>();
set errors(ListBuilder<String>? errors) => _$this._errors = errors;
ApiErrorBuilder();
ApiErrorBuilder get _$this {
final $v = _$v;
if ($v != null) {
_errors = $v.errors?.toBuilder();
_$v = null;
}
return this;
}
@override
void replace(ApiError other) {
ArgumentError.checkNotNull(other, 'other');
_$v = other as _$ApiError;
}
@override
void update(void Function(ApiErrorBuilder)? updates) {
if (updates != null) updates(this);
}
@override
ApiError build() => _build();
_$ApiError _build() {
_$ApiError _$result;
try {
_$result = _$v ?? new _$ApiError._(errors: _errors?.build());
} catch (_) {
late String _$failedField;
try {
_$failedField = 'errors';
_errors?.build();
} catch (e) {
throw new BuiltValueNestedFieldError(
r'ApiError',
_$failedField,
e.toString(),
);
}
rethrow;
}
replace(_$result);
return _$result;
}
}
// ignore_for_file: deprecated_member_use_from_same_package,type=lint

View File

@@ -0,0 +1,49 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import '../serializers.dart';
part 'current_user_collections.g.dart';
abstract class CurrentUserCollections
implements Built<CurrentUserCollections, CurrentUserCollectionsBuilder> {
factory CurrentUserCollections([
void Function(CurrentUserCollectionsBuilder)? updates,
]) = _$CurrentUserCollections;
CurrentUserCollections._();
@BuiltValueField(wireName: 'id')
int get id;
@BuiltValueField(wireName: 'title')
String? get title;
@BuiltValueField(wireName: 'published_at')
String? get publishedAt;
@BuiltValueField(wireName: 'updated_at')
String? get updatedAt;
String toJson() {
return json.encode(
serializers.serializeWith(CurrentUserCollections.serializer, this),
);
}
static CurrentUserCollections? fromJson(String jsonString) {
return serializers.deserializeWith(
CurrentUserCollections.serializer,
json.decode(jsonString),
);
}
static Serializer<CurrentUserCollections> get serializer =>
_$currentUserCollectionsSerializer;
}

View File

@@ -0,0 +1,260 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'current_user_collections.dart';
// **************************************************************************
// BuiltValueGenerator
// **************************************************************************
Serializer<CurrentUserCollections> _$currentUserCollectionsSerializer =
new _$CurrentUserCollectionsSerializer();
class _$CurrentUserCollectionsSerializer
implements StructuredSerializer<CurrentUserCollections> {
@override
final Iterable<Type> types = const [
CurrentUserCollections,
_$CurrentUserCollections,
];
@override
final String wireName = 'CurrentUserCollections';
@override
Iterable<Object?> serialize(
Serializers serializers,
CurrentUserCollections object, {
FullType specifiedType = FullType.unspecified,
}) {
final result = <Object?>[
'id',
serializers.serialize(object.id, specifiedType: const FullType(int)),
];
Object? value;
value = object.title;
if (value != null) {
result
..add('title')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
value = object.publishedAt;
if (value != null) {
result
..add('published_at')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
value = object.updatedAt;
if (value != null) {
result
..add('updated_at')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
return result;
}
@override
CurrentUserCollections deserialize(
Serializers serializers,
Iterable<Object?> serialized, {
FullType specifiedType = FullType.unspecified,
}) {
final result = new CurrentUserCollectionsBuilder();
final iterator = serialized.iterator;
while (iterator.moveNext()) {
final key = iterator.current! as String;
iterator.moveNext();
final Object? value = iterator.current;
switch (key) {
case 'id':
result.id =
serializers.deserialize(
value,
specifiedType: const FullType(int),
)!
as int;
break;
case 'title':
result.title =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
case 'published_at':
result.publishedAt =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
case 'updated_at':
result.updatedAt =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
}
}
return result.build();
}
}
class _$CurrentUserCollections extends CurrentUserCollections {
@override
final int id;
@override
final String? title;
@override
final String? publishedAt;
@override
final String? updatedAt;
factory _$CurrentUserCollections([
void Function(CurrentUserCollectionsBuilder)? updates,
]) => (new CurrentUserCollectionsBuilder()..update(updates))._build();
_$CurrentUserCollections._({
required this.id,
this.title,
this.publishedAt,
this.updatedAt,
}) : super._() {
BuiltValueNullFieldError.checkNotNull(
id,
r'CurrentUserCollections',
'id',
);
}
@override
CurrentUserCollections rebuild(
void Function(CurrentUserCollectionsBuilder) updates,
) => (toBuilder()..update(updates)).build();
@override
CurrentUserCollectionsBuilder toBuilder() =>
new CurrentUserCollectionsBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is CurrentUserCollections &&
id == other.id &&
title == other.title &&
publishedAt == other.publishedAt &&
updatedAt == other.updatedAt;
}
@override
int get hashCode {
var _$hash = 0;
_$hash = $jc(_$hash, id.hashCode);
_$hash = $jc(_$hash, title.hashCode);
_$hash = $jc(_$hash, publishedAt.hashCode);
_$hash = $jc(_$hash, updatedAt.hashCode);
_$hash = $jf(_$hash);
return _$hash;
}
@override
String toString() {
return (newBuiltValueToStringHelper(r'CurrentUserCollections')
..add('id', id)
..add('title', title)
..add('publishedAt', publishedAt)
..add('updatedAt', updatedAt))
.toString();
}
}
class CurrentUserCollectionsBuilder
implements Builder<CurrentUserCollections, CurrentUserCollectionsBuilder> {
_$CurrentUserCollections? _$v;
int? _id;
int? get id => _$this._id;
set id(int? id) => _$this._id = id;
String? _title;
String? get title => _$this._title;
set title(String? title) => _$this._title = title;
String? _publishedAt;
String? get publishedAt => _$this._publishedAt;
set publishedAt(String? publishedAt) => _$this._publishedAt = publishedAt;
String? _updatedAt;
String? get updatedAt => _$this._updatedAt;
set updatedAt(String? updatedAt) => _$this._updatedAt = updatedAt;
CurrentUserCollectionsBuilder();
CurrentUserCollectionsBuilder get _$this {
final $v = _$v;
if ($v != null) {
_id = $v.id;
_title = $v.title;
_publishedAt = $v.publishedAt;
_updatedAt = $v.updatedAt;
_$v = null;
}
return this;
}
@override
void replace(CurrentUserCollections other) {
ArgumentError.checkNotNull(other, 'other');
_$v = other as _$CurrentUserCollections;
}
@override
void update(void Function(CurrentUserCollectionsBuilder)? updates) {
if (updates != null) updates(this);
}
@override
CurrentUserCollections build() => _build();
_$CurrentUserCollections _build() {
final _$result =
_$v ??
new _$CurrentUserCollections._(
id: BuiltValueNullFieldError.checkNotNull(
id,
r'CurrentUserCollections',
'id',
),
title: title,
publishedAt: publishedAt,
updatedAt: updatedAt,
);
replace(_$result);
return _$result;
}
}
// ignore_for_file: deprecated_member_use_from_same_package,type=lint

View File

@@ -0,0 +1,49 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import '../serializers.dart';
part 'exif.g.dart';
abstract class Exif implements Built<Exif, ExifBuilder> {
factory Exif([void Function(ExifBuilder)? updates]) = _$Exif;
Exif._();
@BuiltValueField(wireName: 'make')
String? get make;
@BuiltValueField(wireName: 'model')
String? get model;
@BuiltValueField(wireName: 'exposure_time')
String? get exposureTime;
@BuiltValueField(wireName: 'aperture')
String? get aperture;
@BuiltValueField(wireName: 'focal_length')
String? get focalLength;
@BuiltValueField(wireName: 'iso')
int? get iso;
String toJson() {
return json.encode(serializers.serializeWith(Exif.serializer, this));
}
static Exif? fromJson(String jsonString) {
return serializers.deserializeWith(
Exif.serializer,
json.decode(jsonString),
);
}
static Serializer<Exif> get serializer => _$exifSerializer;
}

View File

@@ -0,0 +1,309 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'exif.dart';
// **************************************************************************
// BuiltValueGenerator
// **************************************************************************
Serializer<Exif> _$exifSerializer = new _$ExifSerializer();
class _$ExifSerializer implements StructuredSerializer<Exif> {
@override
final Iterable<Type> types = const [Exif, _$Exif];
@override
final String wireName = 'Exif';
@override
Iterable<Object?> serialize(
Serializers serializers,
Exif object, {
FullType specifiedType = FullType.unspecified,
}) {
final result = <Object?>[];
Object? value;
value = object.make;
if (value != null) {
result
..add('make')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
value = object.model;
if (value != null) {
result
..add('model')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
value = object.exposureTime;
if (value != null) {
result
..add('exposure_time')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
value = object.aperture;
if (value != null) {
result
..add('aperture')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
value = object.focalLength;
if (value != null) {
result
..add('focal_length')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
value = object.iso;
if (value != null) {
result
..add('iso')
..add(
serializers.serialize(value, specifiedType: const FullType(int)),
);
}
return result;
}
@override
Exif deserialize(
Serializers serializers,
Iterable<Object?> serialized, {
FullType specifiedType = FullType.unspecified,
}) {
final result = new ExifBuilder();
final iterator = serialized.iterator;
while (iterator.moveNext()) {
final key = iterator.current! as String;
iterator.moveNext();
final Object? value = iterator.current;
switch (key) {
case 'make':
result.make =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
case 'model':
result.model =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
case 'exposure_time':
result.exposureTime =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
case 'aperture':
result.aperture =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
case 'focal_length':
result.focalLength =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
case 'iso':
result.iso =
serializers.deserialize(
value,
specifiedType: const FullType(int),
)
as int?;
break;
}
}
return result.build();
}
}
class _$Exif extends Exif {
@override
final String? make;
@override
final String? model;
@override
final String? exposureTime;
@override
final String? aperture;
@override
final String? focalLength;
@override
final int? iso;
factory _$Exif([void Function(ExifBuilder)? updates]) =>
(new ExifBuilder()..update(updates))._build();
_$Exif._({
this.make,
this.model,
this.exposureTime,
this.aperture,
this.focalLength,
this.iso,
}) : super._();
@override
Exif rebuild(void Function(ExifBuilder) updates) =>
(toBuilder()..update(updates)).build();
@override
ExifBuilder toBuilder() => new ExifBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is Exif &&
make == other.make &&
model == other.model &&
exposureTime == other.exposureTime &&
aperture == other.aperture &&
focalLength == other.focalLength &&
iso == other.iso;
}
@override
int get hashCode {
var _$hash = 0;
_$hash = $jc(_$hash, make.hashCode);
_$hash = $jc(_$hash, model.hashCode);
_$hash = $jc(_$hash, exposureTime.hashCode);
_$hash = $jc(_$hash, aperture.hashCode);
_$hash = $jc(_$hash, focalLength.hashCode);
_$hash = $jc(_$hash, iso.hashCode);
_$hash = $jf(_$hash);
return _$hash;
}
@override
String toString() {
return (newBuiltValueToStringHelper(r'Exif')
..add('make', make)
..add('model', model)
..add('exposureTime', exposureTime)
..add('aperture', aperture)
..add('focalLength', focalLength)
..add('iso', iso))
.toString();
}
}
class ExifBuilder implements Builder<Exif, ExifBuilder> {
_$Exif? _$v;
String? _make;
String? get make => _$this._make;
set make(String? make) => _$this._make = make;
String? _model;
String? get model => _$this._model;
set model(String? model) => _$this._model = model;
String? _exposureTime;
String? get exposureTime => _$this._exposureTime;
set exposureTime(String? exposureTime) =>
_$this._exposureTime = exposureTime;
String? _aperture;
String? get aperture => _$this._aperture;
set aperture(String? aperture) => _$this._aperture = aperture;
String? _focalLength;
String? get focalLength => _$this._focalLength;
set focalLength(String? focalLength) => _$this._focalLength = focalLength;
int? _iso;
int? get iso => _$this._iso;
set iso(int? iso) => _$this._iso = iso;
ExifBuilder();
ExifBuilder get _$this {
final $v = _$v;
if ($v != null) {
_make = $v.make;
_model = $v.model;
_exposureTime = $v.exposureTime;
_aperture = $v.aperture;
_focalLength = $v.focalLength;
_iso = $v.iso;
_$v = null;
}
return this;
}
@override
void replace(Exif other) {
ArgumentError.checkNotNull(other, 'other');
_$v = other as _$Exif;
}
@override
void update(void Function(ExifBuilder)? updates) {
if (updates != null) updates(this);
}
@override
Exif build() => _build();
_$Exif _build() {
final _$result =
_$v ??
new _$Exif._(
make: make,
model: model,
exposureTime: exposureTime,
aperture: aperture,
focalLength: focalLength,
iso: iso,
);
replace(_$result);
return _$result;
}
}
// ignore_for_file: deprecated_member_use_from_same_package,type=lint

View File

@@ -0,0 +1,43 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import '../serializers.dart';
part 'links.g.dart';
abstract class Links implements Built<Links, LinksBuilder> {
factory Links([void Function(LinksBuilder)? updates]) = _$Links;
Links._();
@BuiltValueField(wireName: 'self')
String? get self;
@BuiltValueField(wireName: 'html')
String? get html;
@BuiltValueField(wireName: 'download')
String? get download;
@BuiltValueField(wireName: 'download_location')
String? get downloadLocation;
String toJson() {
return json.encode(serializers.serializeWith(Links.serializer, this));
}
static Links? fromJson(String jsonString) {
return serializers.deserializeWith(
Links.serializer,
json.decode(jsonString),
);
}
static Serializer<Links> get serializer => _$linksSerializer;
}

View File

@@ -0,0 +1,246 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'links.dart';
// **************************************************************************
// BuiltValueGenerator
// **************************************************************************
Serializer<Links> _$linksSerializer = new _$LinksSerializer();
class _$LinksSerializer implements StructuredSerializer<Links> {
@override
final Iterable<Type> types = const [Links, _$Links];
@override
final String wireName = 'Links';
@override
Iterable<Object?> serialize(
Serializers serializers,
Links object, {
FullType specifiedType = FullType.unspecified,
}) {
final result = <Object?>[];
Object? value;
value = object.self;
if (value != null) {
result
..add('self')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
value = object.html;
if (value != null) {
result
..add('html')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
value = object.download;
if (value != null) {
result
..add('download')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
value = object.downloadLocation;
if (value != null) {
result
..add('download_location')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
return result;
}
@override
Links deserialize(
Serializers serializers,
Iterable<Object?> serialized, {
FullType specifiedType = FullType.unspecified,
}) {
final result = new LinksBuilder();
final iterator = serialized.iterator;
while (iterator.moveNext()) {
final key = iterator.current! as String;
iterator.moveNext();
final Object? value = iterator.current;
switch (key) {
case 'self':
result.self =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
case 'html':
result.html =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
case 'download':
result.download =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
case 'download_location':
result.downloadLocation =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
}
}
return result.build();
}
}
class _$Links extends Links {
@override
final String? self;
@override
final String? html;
@override
final String? download;
@override
final String? downloadLocation;
factory _$Links([void Function(LinksBuilder)? updates]) =>
(new LinksBuilder()..update(updates))._build();
_$Links._({this.self, this.html, this.download, this.downloadLocation})
: super._();
@override
Links rebuild(void Function(LinksBuilder) updates) =>
(toBuilder()..update(updates)).build();
@override
LinksBuilder toBuilder() => new LinksBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is Links &&
self == other.self &&
html == other.html &&
download == other.download &&
downloadLocation == other.downloadLocation;
}
@override
int get hashCode {
var _$hash = 0;
_$hash = $jc(_$hash, self.hashCode);
_$hash = $jc(_$hash, html.hashCode);
_$hash = $jc(_$hash, download.hashCode);
_$hash = $jc(_$hash, downloadLocation.hashCode);
_$hash = $jf(_$hash);
return _$hash;
}
@override
String toString() {
return (newBuiltValueToStringHelper(r'Links')
..add('self', self)
..add('html', html)
..add('download', download)
..add('downloadLocation', downloadLocation))
.toString();
}
}
class LinksBuilder implements Builder<Links, LinksBuilder> {
_$Links? _$v;
String? _self;
String? get self => _$this._self;
set self(String? self) => _$this._self = self;
String? _html;
String? get html => _$this._html;
set html(String? html) => _$this._html = html;
String? _download;
String? get download => _$this._download;
set download(String? download) => _$this._download = download;
String? _downloadLocation;
String? get downloadLocation => _$this._downloadLocation;
set downloadLocation(String? downloadLocation) =>
_$this._downloadLocation = downloadLocation;
LinksBuilder();
LinksBuilder get _$this {
final $v = _$v;
if ($v != null) {
_self = $v.self;
_html = $v.html;
_download = $v.download;
_downloadLocation = $v.downloadLocation;
_$v = null;
}
return this;
}
@override
void replace(Links other) {
ArgumentError.checkNotNull(other, 'other');
_$v = other as _$Links;
}
@override
void update(void Function(LinksBuilder)? updates) {
if (updates != null) updates(this);
}
@override
Links build() => _build();
_$Links _build() {
final _$result =
_$v ??
new _$Links._(
self: self,
html: html,
download: download,
downloadLocation: downloadLocation,
);
replace(_$result);
return _$result;
}
}
// ignore_for_file: deprecated_member_use_from_same_package,type=lint

View File

@@ -0,0 +1,43 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import '../serializers.dart';
import 'position.dart';
part 'location.g.dart';
abstract class Location implements Built<Location, LocationBuilder> {
factory Location([void Function(LocationBuilder)? updates]) = _$Location;
Location._();
@BuiltValueField(wireName: 'city')
String? get city;
@BuiltValueField(wireName: 'country')
String? get country;
@BuiltValueField(wireName: 'position')
Position? get position;
String toJson() {
return json.encode(
serializers.serializeWith(Location.serializer, this),
);
}
static Location? fromJson(String jsonString) {
return serializers.deserializeWith(
Location.serializer,
json.decode(jsonString),
);
}
static Serializer<Location> get serializer => _$locationSerializer;
}

View File

@@ -0,0 +1,231 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'location.dart';
// **************************************************************************
// BuiltValueGenerator
// **************************************************************************
Serializer<Location> _$locationSerializer = new _$LocationSerializer();
class _$LocationSerializer implements StructuredSerializer<Location> {
@override
final Iterable<Type> types = const [Location, _$Location];
@override
final String wireName = 'Location';
@override
Iterable<Object?> serialize(
Serializers serializers,
Location object, {
FullType specifiedType = FullType.unspecified,
}) {
final result = <Object?>[];
Object? value;
value = object.city;
if (value != null) {
result
..add('city')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
value = object.country;
if (value != null) {
result
..add('country')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
value = object.position;
if (value != null) {
result
..add('position')
..add(
serializers.serialize(
value,
specifiedType: const FullType(Position),
),
);
}
return result;
}
@override
Location deserialize(
Serializers serializers,
Iterable<Object?> serialized, {
FullType specifiedType = FullType.unspecified,
}) {
final result = new LocationBuilder();
final iterator = serialized.iterator;
while (iterator.moveNext()) {
final key = iterator.current! as String;
iterator.moveNext();
final Object? value = iterator.current;
switch (key) {
case 'city':
result.city =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
case 'country':
result.country =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
case 'position':
result.position.replace(
serializers.deserialize(
value,
specifiedType: const FullType(Position),
)!
as Position,
);
break;
}
}
return result.build();
}
}
class _$Location extends Location {
@override
final String? city;
@override
final String? country;
@override
final Position? position;
factory _$Location([void Function(LocationBuilder)? updates]) =>
(new LocationBuilder()..update(updates))._build();
_$Location._({this.city, this.country, this.position}) : super._();
@override
Location rebuild(void Function(LocationBuilder) updates) =>
(toBuilder()..update(updates)).build();
@override
LocationBuilder toBuilder() => new LocationBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is Location &&
city == other.city &&
country == other.country &&
position == other.position;
}
@override
int get hashCode {
var _$hash = 0;
_$hash = $jc(_$hash, city.hashCode);
_$hash = $jc(_$hash, country.hashCode);
_$hash = $jc(_$hash, position.hashCode);
_$hash = $jf(_$hash);
return _$hash;
}
@override
String toString() {
return (newBuiltValueToStringHelper(r'Location')
..add('city', city)
..add('country', country)
..add('position', position))
.toString();
}
}
class LocationBuilder implements Builder<Location, LocationBuilder> {
_$Location? _$v;
String? _city;
String? get city => _$this._city;
set city(String? city) => _$this._city = city;
String? _country;
String? get country => _$this._country;
set country(String? country) => _$this._country = country;
PositionBuilder? _position;
PositionBuilder get position => _$this._position ??= new PositionBuilder();
set position(PositionBuilder? position) => _$this._position = position;
LocationBuilder();
LocationBuilder get _$this {
final $v = _$v;
if ($v != null) {
_city = $v.city;
_country = $v.country;
_position = $v.position?.toBuilder();
_$v = null;
}
return this;
}
@override
void replace(Location other) {
ArgumentError.checkNotNull(other, 'other');
_$v = other as _$Location;
}
@override
void update(void Function(LocationBuilder)? updates) {
if (updates != null) updates(this);
}
@override
Location build() => _build();
_$Location _build() {
_$Location _$result;
try {
_$result =
_$v ??
new _$Location._(
city: city,
country: country,
position: _position?.build(),
);
} catch (_) {
late String _$failedField;
try {
_$failedField = 'position';
_position?.build();
} catch (e) {
throw new BuiltValueNestedFieldError(
r'Location',
_$failedField,
e.toString(),
);
}
rethrow;
}
replace(_$result);
return _$result;
}
}
// ignore_for_file: deprecated_member_use_from_same_package,type=lint

View File

@@ -0,0 +1,90 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import '../serializers.dart';
import 'current_user_collections.dart';
import 'exif.dart';
import 'links.dart';
import 'location.dart';
import 'tags.dart';
import 'urls.dart';
import 'user.dart';
part 'photo.g.dart';
abstract class Photo implements Built<Photo, PhotoBuilder> {
factory Photo([void Function(PhotoBuilder)? updates]) = _$Photo;
Photo._();
@BuiltValueField(wireName: 'id')
String get id;
@BuiltValueField(wireName: 'created_at')
String? get createdAt;
@BuiltValueField(wireName: 'updated_at')
String? get updatedAt;
@BuiltValueField(wireName: 'width')
int? get width;
@BuiltValueField(wireName: 'height')
int? get height;
@BuiltValueField(wireName: 'color')
String? get color;
@BuiltValueField(wireName: 'downloads')
int? get downloads;
@BuiltValueField(wireName: 'likes')
int? get likes;
@BuiltValueField(wireName: 'liked_by_user')
bool? get likedByUser;
@BuiltValueField(wireName: 'description')
String? get description;
@BuiltValueField(wireName: 'exif')
Exif? get exif;
@BuiltValueField(wireName: 'location')
Location? get location;
@BuiltValueField(wireName: 'tags')
BuiltList<Tags>? get tags;
@BuiltValueField(wireName: 'current_user_collections')
BuiltList<CurrentUserCollections>? get currentUserCollections;
@BuiltValueField(wireName: 'urls')
Urls? get urls;
@BuiltValueField(wireName: 'links')
Links? get links;
@BuiltValueField(wireName: 'user')
User? get user;
String toJson() {
return json.encode(serializers.serializeWith(Photo.serializer, this));
}
static Photo? fromJson(String jsonString) {
return serializers.deserializeWith(
Photo.serializer,
json.decode(jsonString),
);
}
static Serializer<Photo> get serializer => _$photoSerializer;
}

View File

@@ -0,0 +1,684 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'photo.dart';
// **************************************************************************
// BuiltValueGenerator
// **************************************************************************
Serializer<Photo> _$photoSerializer = new _$PhotoSerializer();
class _$PhotoSerializer implements StructuredSerializer<Photo> {
@override
final Iterable<Type> types = const [Photo, _$Photo];
@override
final String wireName = 'Photo';
@override
Iterable<Object?> serialize(
Serializers serializers,
Photo object, {
FullType specifiedType = FullType.unspecified,
}) {
final result = <Object?>[
'id',
serializers.serialize(
object.id,
specifiedType: const FullType(String),
),
];
Object? value;
value = object.createdAt;
if (value != null) {
result
..add('created_at')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
value = object.updatedAt;
if (value != null) {
result
..add('updated_at')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
value = object.width;
if (value != null) {
result
..add('width')
..add(
serializers.serialize(value, specifiedType: const FullType(int)),
);
}
value = object.height;
if (value != null) {
result
..add('height')
..add(
serializers.serialize(value, specifiedType: const FullType(int)),
);
}
value = object.color;
if (value != null) {
result
..add('color')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
value = object.downloads;
if (value != null) {
result
..add('downloads')
..add(
serializers.serialize(value, specifiedType: const FullType(int)),
);
}
value = object.likes;
if (value != null) {
result
..add('likes')
..add(
serializers.serialize(value, specifiedType: const FullType(int)),
);
}
value = object.likedByUser;
if (value != null) {
result
..add('liked_by_user')
..add(
serializers.serialize(
value,
specifiedType: const FullType(bool),
),
);
}
value = object.description;
if (value != null) {
result
..add('description')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
value = object.exif;
if (value != null) {
result
..add('exif')
..add(
serializers.serialize(
value,
specifiedType: const FullType(Exif),
),
);
}
value = object.location;
if (value != null) {
result
..add('location')
..add(
serializers.serialize(
value,
specifiedType: const FullType(Location),
),
);
}
value = object.tags;
if (value != null) {
result
..add('tags')
..add(
serializers.serialize(
value,
specifiedType: const FullType(BuiltList, const [
const FullType(Tags),
]),
),
);
}
value = object.currentUserCollections;
if (value != null) {
result
..add('current_user_collections')
..add(
serializers.serialize(
value,
specifiedType: const FullType(BuiltList, const [
const FullType(CurrentUserCollections),
]),
),
);
}
value = object.urls;
if (value != null) {
result
..add('urls')
..add(
serializers.serialize(
value,
specifiedType: const FullType(Urls),
),
);
}
value = object.links;
if (value != null) {
result
..add('links')
..add(
serializers.serialize(
value,
specifiedType: const FullType(Links),
),
);
}
value = object.user;
if (value != null) {
result
..add('user')
..add(
serializers.serialize(
value,
specifiedType: const FullType(User),
),
);
}
return result;
}
@override
Photo deserialize(
Serializers serializers,
Iterable<Object?> serialized, {
FullType specifiedType = FullType.unspecified,
}) {
final result = new PhotoBuilder();
final iterator = serialized.iterator;
while (iterator.moveNext()) {
final key = iterator.current! as String;
iterator.moveNext();
final Object? value = iterator.current;
switch (key) {
case 'id':
result.id =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)!
as String;
break;
case 'created_at':
result.createdAt =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
case 'updated_at':
result.updatedAt =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
case 'width':
result.width =
serializers.deserialize(
value,
specifiedType: const FullType(int),
)
as int?;
break;
case 'height':
result.height =
serializers.deserialize(
value,
specifiedType: const FullType(int),
)
as int?;
break;
case 'color':
result.color =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
case 'downloads':
result.downloads =
serializers.deserialize(
value,
specifiedType: const FullType(int),
)
as int?;
break;
case 'likes':
result.likes =
serializers.deserialize(
value,
specifiedType: const FullType(int),
)
as int?;
break;
case 'liked_by_user':
result.likedByUser =
serializers.deserialize(
value,
specifiedType: const FullType(bool),
)
as bool?;
break;
case 'description':
result.description =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
case 'exif':
result.exif.replace(
serializers.deserialize(
value,
specifiedType: const FullType(Exif),
)!
as Exif,
);
break;
case 'location':
result.location.replace(
serializers.deserialize(
value,
specifiedType: const FullType(Location),
)!
as Location,
);
break;
case 'tags':
result.tags.replace(
serializers.deserialize(
value,
specifiedType: const FullType(BuiltList, const [
const FullType(Tags),
]),
)!
as BuiltList<Object?>,
);
break;
case 'current_user_collections':
result.currentUserCollections.replace(
serializers.deserialize(
value,
specifiedType: const FullType(BuiltList, const [
const FullType(CurrentUserCollections),
]),
)!
as BuiltList<Object?>,
);
break;
case 'urls':
result.urls.replace(
serializers.deserialize(
value,
specifiedType: const FullType(Urls),
)!
as Urls,
);
break;
case 'links':
result.links.replace(
serializers.deserialize(
value,
specifiedType: const FullType(Links),
)!
as Links,
);
break;
case 'user':
result.user.replace(
serializers.deserialize(
value,
specifiedType: const FullType(User),
)!
as User,
);
break;
}
}
return result.build();
}
}
class _$Photo extends Photo {
@override
final String id;
@override
final String? createdAt;
@override
final String? updatedAt;
@override
final int? width;
@override
final int? height;
@override
final String? color;
@override
final int? downloads;
@override
final int? likes;
@override
final bool? likedByUser;
@override
final String? description;
@override
final Exif? exif;
@override
final Location? location;
@override
final BuiltList<Tags>? tags;
@override
final BuiltList<CurrentUserCollections>? currentUserCollections;
@override
final Urls? urls;
@override
final Links? links;
@override
final User? user;
factory _$Photo([void Function(PhotoBuilder)? updates]) =>
(new PhotoBuilder()..update(updates))._build();
_$Photo._({
required this.id,
this.createdAt,
this.updatedAt,
this.width,
this.height,
this.color,
this.downloads,
this.likes,
this.likedByUser,
this.description,
this.exif,
this.location,
this.tags,
this.currentUserCollections,
this.urls,
this.links,
this.user,
}) : super._() {
BuiltValueNullFieldError.checkNotNull(id, r'Photo', 'id');
}
@override
Photo rebuild(void Function(PhotoBuilder) updates) =>
(toBuilder()..update(updates)).build();
@override
PhotoBuilder toBuilder() => new PhotoBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is Photo &&
id == other.id &&
createdAt == other.createdAt &&
updatedAt == other.updatedAt &&
width == other.width &&
height == other.height &&
color == other.color &&
downloads == other.downloads &&
likes == other.likes &&
likedByUser == other.likedByUser &&
description == other.description &&
exif == other.exif &&
location == other.location &&
tags == other.tags &&
currentUserCollections == other.currentUserCollections &&
urls == other.urls &&
links == other.links &&
user == other.user;
}
@override
int get hashCode {
var _$hash = 0;
_$hash = $jc(_$hash, id.hashCode);
_$hash = $jc(_$hash, createdAt.hashCode);
_$hash = $jc(_$hash, updatedAt.hashCode);
_$hash = $jc(_$hash, width.hashCode);
_$hash = $jc(_$hash, height.hashCode);
_$hash = $jc(_$hash, color.hashCode);
_$hash = $jc(_$hash, downloads.hashCode);
_$hash = $jc(_$hash, likes.hashCode);
_$hash = $jc(_$hash, likedByUser.hashCode);
_$hash = $jc(_$hash, description.hashCode);
_$hash = $jc(_$hash, exif.hashCode);
_$hash = $jc(_$hash, location.hashCode);
_$hash = $jc(_$hash, tags.hashCode);
_$hash = $jc(_$hash, currentUserCollections.hashCode);
_$hash = $jc(_$hash, urls.hashCode);
_$hash = $jc(_$hash, links.hashCode);
_$hash = $jc(_$hash, user.hashCode);
_$hash = $jf(_$hash);
return _$hash;
}
@override
String toString() {
return (newBuiltValueToStringHelper(r'Photo')
..add('id', id)
..add('createdAt', createdAt)
..add('updatedAt', updatedAt)
..add('width', width)
..add('height', height)
..add('color', color)
..add('downloads', downloads)
..add('likes', likes)
..add('likedByUser', likedByUser)
..add('description', description)
..add('exif', exif)
..add('location', location)
..add('tags', tags)
..add('currentUserCollections', currentUserCollections)
..add('urls', urls)
..add('links', links)
..add('user', user))
.toString();
}
}
class PhotoBuilder implements Builder<Photo, PhotoBuilder> {
_$Photo? _$v;
String? _id;
String? get id => _$this._id;
set id(String? id) => _$this._id = id;
String? _createdAt;
String? get createdAt => _$this._createdAt;
set createdAt(String? createdAt) => _$this._createdAt = createdAt;
String? _updatedAt;
String? get updatedAt => _$this._updatedAt;
set updatedAt(String? updatedAt) => _$this._updatedAt = updatedAt;
int? _width;
int? get width => _$this._width;
set width(int? width) => _$this._width = width;
int? _height;
int? get height => _$this._height;
set height(int? height) => _$this._height = height;
String? _color;
String? get color => _$this._color;
set color(String? color) => _$this._color = color;
int? _downloads;
int? get downloads => _$this._downloads;
set downloads(int? downloads) => _$this._downloads = downloads;
int? _likes;
int? get likes => _$this._likes;
set likes(int? likes) => _$this._likes = likes;
bool? _likedByUser;
bool? get likedByUser => _$this._likedByUser;
set likedByUser(bool? likedByUser) => _$this._likedByUser = likedByUser;
String? _description;
String? get description => _$this._description;
set description(String? description) => _$this._description = description;
ExifBuilder? _exif;
ExifBuilder get exif => _$this._exif ??= new ExifBuilder();
set exif(ExifBuilder? exif) => _$this._exif = exif;
LocationBuilder? _location;
LocationBuilder get location => _$this._location ??= new LocationBuilder();
set location(LocationBuilder? location) => _$this._location = location;
ListBuilder<Tags>? _tags;
ListBuilder<Tags> get tags => _$this._tags ??= new ListBuilder<Tags>();
set tags(ListBuilder<Tags>? tags) => _$this._tags = tags;
ListBuilder<CurrentUserCollections>? _currentUserCollections;
ListBuilder<CurrentUserCollections> get currentUserCollections =>
_$this._currentUserCollections ??=
new ListBuilder<CurrentUserCollections>();
set currentUserCollections(
ListBuilder<CurrentUserCollections>? currentUserCollections,
) => _$this._currentUserCollections = currentUserCollections;
UrlsBuilder? _urls;
UrlsBuilder get urls => _$this._urls ??= new UrlsBuilder();
set urls(UrlsBuilder? urls) => _$this._urls = urls;
LinksBuilder? _links;
LinksBuilder get links => _$this._links ??= new LinksBuilder();
set links(LinksBuilder? links) => _$this._links = links;
UserBuilder? _user;
UserBuilder get user => _$this._user ??= new UserBuilder();
set user(UserBuilder? user) => _$this._user = user;
PhotoBuilder();
PhotoBuilder get _$this {
final $v = _$v;
if ($v != null) {
_id = $v.id;
_createdAt = $v.createdAt;
_updatedAt = $v.updatedAt;
_width = $v.width;
_height = $v.height;
_color = $v.color;
_downloads = $v.downloads;
_likes = $v.likes;
_likedByUser = $v.likedByUser;
_description = $v.description;
_exif = $v.exif?.toBuilder();
_location = $v.location?.toBuilder();
_tags = $v.tags?.toBuilder();
_currentUserCollections = $v.currentUserCollections?.toBuilder();
_urls = $v.urls?.toBuilder();
_links = $v.links?.toBuilder();
_user = $v.user?.toBuilder();
_$v = null;
}
return this;
}
@override
void replace(Photo other) {
ArgumentError.checkNotNull(other, 'other');
_$v = other as _$Photo;
}
@override
void update(void Function(PhotoBuilder)? updates) {
if (updates != null) updates(this);
}
@override
Photo build() => _build();
_$Photo _build() {
_$Photo _$result;
try {
_$result =
_$v ??
new _$Photo._(
id: BuiltValueNullFieldError.checkNotNull(id, r'Photo', 'id'),
createdAt: createdAt,
updatedAt: updatedAt,
width: width,
height: height,
color: color,
downloads: downloads,
likes: likes,
likedByUser: likedByUser,
description: description,
exif: _exif?.build(),
location: _location?.build(),
tags: _tags?.build(),
currentUserCollections: _currentUserCollections?.build(),
urls: _urls?.build(),
links: _links?.build(),
user: _user?.build(),
);
} catch (_) {
late String _$failedField;
try {
_$failedField = 'exif';
_exif?.build();
_$failedField = 'location';
_location?.build();
_$failedField = 'tags';
_tags?.build();
_$failedField = 'currentUserCollections';
_currentUserCollections?.build();
_$failedField = 'urls';
_urls?.build();
_$failedField = 'links';
_links?.build();
_$failedField = 'user';
_user?.build();
} catch (e) {
throw new BuiltValueNestedFieldError(
r'Photo',
_$failedField,
e.toString(),
);
}
rethrow;
}
replace(_$result);
return _$result;
}
}
// ignore_for_file: deprecated_member_use_from_same_package,type=lint

View File

@@ -0,0 +1,39 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import '../serializers.dart';
part 'position.g.dart';
abstract class Position implements Built<Position, PositionBuilder> {
factory Position([void Function(PositionBuilder)? updates]) = _$Position;
Position._();
@BuiltValueField(wireName: 'latitude')
double get latitude;
@BuiltValueField(wireName: 'longitude')
double get longitude;
String toJson() {
return json.encode(
serializers.serializeWith(Position.serializer, this),
);
}
static Position? fromJson(String jsonString) {
return serializers.deserializeWith(
Position.serializer,
json.decode(jsonString),
);
}
static Serializer<Position> get serializer => _$positionSerializer;
}

View File

@@ -0,0 +1,192 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'position.dart';
// **************************************************************************
// BuiltValueGenerator
// **************************************************************************
Serializer<Position> _$positionSerializer = new _$PositionSerializer();
class _$PositionSerializer implements StructuredSerializer<Position> {
@override
final Iterable<Type> types = const [Position, _$Position];
@override
final String wireName = 'Position';
@override
Iterable<Object?> serialize(
Serializers serializers,
Position object, {
FullType specifiedType = FullType.unspecified,
}) {
final result = <Object?>[
'latitude',
serializers.serialize(
object.latitude,
specifiedType: const FullType(double),
),
'longitude',
serializers.serialize(
object.longitude,
specifiedType: const FullType(double),
),
];
return result;
}
@override
Position deserialize(
Serializers serializers,
Iterable<Object?> serialized, {
FullType specifiedType = FullType.unspecified,
}) {
final result = new PositionBuilder();
final iterator = serialized.iterator;
while (iterator.moveNext()) {
final key = iterator.current! as String;
iterator.moveNext();
final Object? value = iterator.current;
switch (key) {
case 'latitude':
result.latitude =
serializers.deserialize(
value,
specifiedType: const FullType(double),
)!
as double;
break;
case 'longitude':
result.longitude =
serializers.deserialize(
value,
specifiedType: const FullType(double),
)!
as double;
break;
}
}
return result.build();
}
}
class _$Position extends Position {
@override
final double latitude;
@override
final double longitude;
factory _$Position([void Function(PositionBuilder)? updates]) =>
(new PositionBuilder()..update(updates))._build();
_$Position._({required this.latitude, required this.longitude}) : super._() {
BuiltValueNullFieldError.checkNotNull(
latitude,
r'Position',
'latitude',
);
BuiltValueNullFieldError.checkNotNull(
longitude,
r'Position',
'longitude',
);
}
@override
Position rebuild(void Function(PositionBuilder) updates) =>
(toBuilder()..update(updates)).build();
@override
PositionBuilder toBuilder() => new PositionBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is Position &&
latitude == other.latitude &&
longitude == other.longitude;
}
@override
int get hashCode {
var _$hash = 0;
_$hash = $jc(_$hash, latitude.hashCode);
_$hash = $jc(_$hash, longitude.hashCode);
_$hash = $jf(_$hash);
return _$hash;
}
@override
String toString() {
return (newBuiltValueToStringHelper(r'Position')
..add('latitude', latitude)
..add('longitude', longitude))
.toString();
}
}
class PositionBuilder implements Builder<Position, PositionBuilder> {
_$Position? _$v;
double? _latitude;
double? get latitude => _$this._latitude;
set latitude(double? latitude) => _$this._latitude = latitude;
double? _longitude;
double? get longitude => _$this._longitude;
set longitude(double? longitude) => _$this._longitude = longitude;
PositionBuilder();
PositionBuilder get _$this {
final $v = _$v;
if ($v != null) {
_latitude = $v.latitude;
_longitude = $v.longitude;
_$v = null;
}
return this;
}
@override
void replace(Position other) {
ArgumentError.checkNotNull(other, 'other');
_$v = other as _$Position;
}
@override
void update(void Function(PositionBuilder)? updates) {
if (updates != null) updates(this);
}
@override
Position build() => _build();
_$Position _build() {
final _$result =
_$v ??
new _$Position._(
latitude: BuiltValueNullFieldError.checkNotNull(
latitude,
r'Position',
'latitude',
),
longitude: BuiltValueNullFieldError.checkNotNull(
longitude,
r'Position',
'longitude',
),
);
replace(_$result);
return _$result;
}
}
// ignore_for_file: deprecated_member_use_from_same_package,type=lint

View File

@@ -0,0 +1,48 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import '../serializers.dart';
import 'photo.dart';
part 'search_photos_response.g.dart';
abstract class SearchPhotosResponse
implements Built<SearchPhotosResponse, SearchPhotosResponseBuilder> {
factory SearchPhotosResponse([
void Function(SearchPhotosResponseBuilder)? updates,
]) = _$SearchPhotosResponse;
SearchPhotosResponse._();
@BuiltValueField(wireName: 'total')
int? get total;
@BuiltValueField(wireName: 'total_pages')
int? get totalPages;
@BuiltValueField(wireName: 'results')
BuiltList<Photo> get results;
String toJson() {
return json.encode(
serializers.serializeWith(SearchPhotosResponse.serializer, this),
);
}
static SearchPhotosResponse? fromJson(String jsonString) {
return serializers.deserializeWith(
SearchPhotosResponse.serializer,
json.decode(jsonString),
);
}
static Serializer<SearchPhotosResponse> get serializer =>
_$searchPhotosResponseSerializer;
}

View File

@@ -0,0 +1,244 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'search_photos_response.dart';
// **************************************************************************
// BuiltValueGenerator
// **************************************************************************
Serializer<SearchPhotosResponse> _$searchPhotosResponseSerializer =
new _$SearchPhotosResponseSerializer();
class _$SearchPhotosResponseSerializer
implements StructuredSerializer<SearchPhotosResponse> {
@override
final Iterable<Type> types = const [
SearchPhotosResponse,
_$SearchPhotosResponse,
];
@override
final String wireName = 'SearchPhotosResponse';
@override
Iterable<Object?> serialize(
Serializers serializers,
SearchPhotosResponse object, {
FullType specifiedType = FullType.unspecified,
}) {
final result = <Object?>[
'results',
serializers.serialize(
object.results,
specifiedType: const FullType(BuiltList, const [
const FullType(Photo),
]),
),
];
Object? value;
value = object.total;
if (value != null) {
result
..add('total')
..add(
serializers.serialize(value, specifiedType: const FullType(int)),
);
}
value = object.totalPages;
if (value != null) {
result
..add('total_pages')
..add(
serializers.serialize(value, specifiedType: const FullType(int)),
);
}
return result;
}
@override
SearchPhotosResponse deserialize(
Serializers serializers,
Iterable<Object?> serialized, {
FullType specifiedType = FullType.unspecified,
}) {
final result = new SearchPhotosResponseBuilder();
final iterator = serialized.iterator;
while (iterator.moveNext()) {
final key = iterator.current! as String;
iterator.moveNext();
final Object? value = iterator.current;
switch (key) {
case 'total':
result.total =
serializers.deserialize(
value,
specifiedType: const FullType(int),
)
as int?;
break;
case 'total_pages':
result.totalPages =
serializers.deserialize(
value,
specifiedType: const FullType(int),
)
as int?;
break;
case 'results':
result.results.replace(
serializers.deserialize(
value,
specifiedType: const FullType(BuiltList, const [
const FullType(Photo),
]),
)!
as BuiltList<Object?>,
);
break;
}
}
return result.build();
}
}
class _$SearchPhotosResponse extends SearchPhotosResponse {
@override
final int? total;
@override
final int? totalPages;
@override
final BuiltList<Photo> results;
factory _$SearchPhotosResponse([
void Function(SearchPhotosResponseBuilder)? updates,
]) => (new SearchPhotosResponseBuilder()..update(updates))._build();
_$SearchPhotosResponse._({
this.total,
this.totalPages,
required this.results,
}) : super._() {
BuiltValueNullFieldError.checkNotNull(
results,
r'SearchPhotosResponse',
'results',
);
}
@override
SearchPhotosResponse rebuild(
void Function(SearchPhotosResponseBuilder) updates,
) => (toBuilder()..update(updates)).build();
@override
SearchPhotosResponseBuilder toBuilder() =>
new SearchPhotosResponseBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is SearchPhotosResponse &&
total == other.total &&
totalPages == other.totalPages &&
results == other.results;
}
@override
int get hashCode {
var _$hash = 0;
_$hash = $jc(_$hash, total.hashCode);
_$hash = $jc(_$hash, totalPages.hashCode);
_$hash = $jc(_$hash, results.hashCode);
_$hash = $jf(_$hash);
return _$hash;
}
@override
String toString() {
return (newBuiltValueToStringHelper(r'SearchPhotosResponse')
..add('total', total)
..add('totalPages', totalPages)
..add('results', results))
.toString();
}
}
class SearchPhotosResponseBuilder
implements Builder<SearchPhotosResponse, SearchPhotosResponseBuilder> {
_$SearchPhotosResponse? _$v;
int? _total;
int? get total => _$this._total;
set total(int? total) => _$this._total = total;
int? _totalPages;
int? get totalPages => _$this._totalPages;
set totalPages(int? totalPages) => _$this._totalPages = totalPages;
ListBuilder<Photo>? _results;
ListBuilder<Photo> get results =>
_$this._results ??= new ListBuilder<Photo>();
set results(ListBuilder<Photo>? results) => _$this._results = results;
SearchPhotosResponseBuilder();
SearchPhotosResponseBuilder get _$this {
final $v = _$v;
if ($v != null) {
_total = $v.total;
_totalPages = $v.totalPages;
_results = $v.results.toBuilder();
_$v = null;
}
return this;
}
@override
void replace(SearchPhotosResponse other) {
ArgumentError.checkNotNull(other, 'other');
_$v = other as _$SearchPhotosResponse;
}
@override
void update(void Function(SearchPhotosResponseBuilder)? updates) {
if (updates != null) updates(this);
}
@override
SearchPhotosResponse build() => _build();
_$SearchPhotosResponse _build() {
_$SearchPhotosResponse _$result;
try {
_$result =
_$v ??
new _$SearchPhotosResponse._(
total: total,
totalPages: totalPages,
results: results.build(),
);
} catch (_) {
late String _$failedField;
try {
_$failedField = 'results';
results.build();
} catch (e) {
throw new BuiltValueNestedFieldError(
r'SearchPhotosResponse',
_$failedField,
e.toString(),
);
}
rethrow;
}
replace(_$result);
return _$result;
}
}
// ignore_for_file: deprecated_member_use_from_same_package,type=lint

View File

@@ -0,0 +1,34 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import '../serializers.dart';
part 'tags.g.dart';
abstract class Tags implements Built<Tags, TagsBuilder> {
factory Tags([void Function(TagsBuilder)? updates]) = _$Tags;
Tags._();
@BuiltValueField(wireName: 'title')
String get title;
String toJson() {
return json.encode(serializers.serializeWith(Tags.serializer, this));
}
static Tags? fromJson(String jsonString) {
return serializers.deserializeWith(
Tags.serializer,
json.decode(jsonString),
);
}
static Serializer<Tags> get serializer => _$tagsSerializer;
}

View File

@@ -0,0 +1,154 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'tags.dart';
// **************************************************************************
// BuiltValueGenerator
// **************************************************************************
Serializer<Tags> _$tagsSerializer = new _$TagsSerializer();
class _$TagsSerializer implements StructuredSerializer<Tags> {
@override
final Iterable<Type> types = const [Tags, _$Tags];
@override
final String wireName = 'Tags';
@override
Iterable<Object?> serialize(
Serializers serializers,
Tags object, {
FullType specifiedType = FullType.unspecified,
}) {
final result = <Object?>[
'title',
serializers.serialize(
object.title,
specifiedType: const FullType(String),
),
];
return result;
}
@override
Tags deserialize(
Serializers serializers,
Iterable<Object?> serialized, {
FullType specifiedType = FullType.unspecified,
}) {
final result = new TagsBuilder();
final iterator = serialized.iterator;
while (iterator.moveNext()) {
final key = iterator.current! as String;
iterator.moveNext();
final Object? value = iterator.current;
switch (key) {
case 'title':
result.title =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)!
as String;
break;
}
}
return result.build();
}
}
class _$Tags extends Tags {
@override
final String title;
factory _$Tags([void Function(TagsBuilder)? updates]) =>
(new TagsBuilder()..update(updates))._build();
_$Tags._({required this.title}) : super._() {
BuiltValueNullFieldError.checkNotNull(title, r'Tags', 'title');
}
@override
Tags rebuild(void Function(TagsBuilder) updates) =>
(toBuilder()..update(updates)).build();
@override
TagsBuilder toBuilder() => new TagsBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is Tags && title == other.title;
}
@override
int get hashCode {
var _$hash = 0;
_$hash = $jc(_$hash, title.hashCode);
_$hash = $jf(_$hash);
return _$hash;
}
@override
String toString() {
return (newBuiltValueToStringHelper(
r'Tags',
)..add('title', title)).toString();
}
}
class TagsBuilder implements Builder<Tags, TagsBuilder> {
_$Tags? _$v;
String? _title;
String? get title => _$this._title;
set title(String? title) => _$this._title = title;
TagsBuilder();
TagsBuilder get _$this {
final $v = _$v;
if ($v != null) {
_title = $v.title;
_$v = null;
}
return this;
}
@override
void replace(Tags other) {
ArgumentError.checkNotNull(other, 'other');
_$v = other as _$Tags;
}
@override
void update(void Function(TagsBuilder)? updates) {
if (updates != null) updates(this);
}
@override
Tags build() => _build();
_$Tags _build() {
final _$result =
_$v ??
new _$Tags._(
title: BuiltValueNullFieldError.checkNotNull(
title,
r'Tags',
'title',
),
);
replace(_$result);
return _$result;
}
}
// ignore_for_file: deprecated_member_use_from_same_package,type=lint

View File

@@ -0,0 +1,121 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:http/http.dart' as http;
import 'package:logging/logging.dart';
import 'api_error.dart';
import 'photo.dart';
import 'search_photos_response.dart';
final _unsplashBaseUrl = Uri.parse('https://api.unsplash.com/');
/// Unsplash API Client. Requires an
/// [Unsplash API](https://unsplash.com/developers) `accessKey` to make
/// requests to the Unsplash API.
class Unsplash {
Unsplash({required String accessKey, http.BaseClient? httpClient})
: _accessKey = accessKey,
_client = httpClient ?? http.Client();
final String _accessKey;
final http.Client _client;
final _log = Logger('Unsplash');
Future<SearchPhotosResponse?> searchPhotos({
required String query,
num page = 1,
num perPage = 10,
List<num> collections = const [],
SearchPhotosOrientation? orientation,
}) async {
final searchPhotosUrl = _unsplashBaseUrl.replace(
path: '/search/photos',
queryParameters: <String, String>{
'query': query,
if (page != 1) 'page': '$page',
if (perPage != 10) 'per_page': '$perPage',
if (collections.isNotEmpty) 'collections': collections.join(','),
if (orientation == SearchPhotosOrientation.landscape)
'orientation': 'landscape',
if (orientation == SearchPhotosOrientation.portrait)
'orientation': 'portrait',
if (orientation == SearchPhotosOrientation.squarish)
'orientation': 'squarish',
},
);
_log.info('GET $searchPhotosUrl');
final response = await _client.get(
searchPhotosUrl,
headers: {
'Accept-Version': 'v1',
'Authorization': 'Client-ID $_accessKey',
},
);
dynamic body;
try {
body = json.fuse(utf8).decode(response.bodyBytes);
} catch (e) {
throw UnsplashException('Invalid JSON received');
}
if (body is Map &&
body['errors'] is List &&
body['errors'].isNotEmpty as bool) {
final apiError = ApiError.fromJson(response.body)!;
throw UnsplashException(apiError.errors!.join(', '));
}
return SearchPhotosResponse.fromJson(json.encode(body));
}
Future<Uint8List> download(Photo photo) async {
// For detail on how downloading photos from Unsplash, please see
// https://help.unsplash.com/en/articles/2511258-guideline-triggering-a-download
_log.info('GET ${photo.urls!.full}');
final futureBytes = http.readBytes(
Uri.parse(photo.urls!.full!),
headers: {
'Accept-Version': 'v1',
'Authorization': 'Client-ID $_accessKey',
},
);
_log.info('GET ${photo.links!.downloadLocation}');
unawaited(
http.get(
Uri.parse(photo.links!.downloadLocation!),
headers: {
'Accept-Version': 'v1',
'Authorization': 'Client-ID $_accessKey',
},
),
);
return futureBytes;
}
}
enum SearchPhotosOrientation { landscape, portrait, squarish }
class UnsplashException implements Exception {
UnsplashException([this.message]);
final String? message;
@override
String toString() {
if (message == null) {
return 'UnsplashException';
}
return 'UnsplashException: $message';
}
}

View File

@@ -0,0 +1,46 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import '../serializers.dart';
part 'urls.g.dart';
abstract class Urls implements Built<Urls, UrlsBuilder> {
factory Urls([void Function(UrlsBuilder b)? updates]) = _$Urls;
Urls._();
@BuiltValueField(wireName: 'raw')
String? get raw;
@BuiltValueField(wireName: 'full')
String? get full;
@BuiltValueField(wireName: 'regular')
String? get regular;
@BuiltValueField(wireName: 'small')
String? get small;
@BuiltValueField(wireName: 'thumb')
String? get thumb;
String toJson() {
return json.encode(serializers.serializeWith(Urls.serializer, this));
}
static Urls? fromJson(String jsonString) {
return serializers.deserializeWith(
Urls.serializer,
json.decode(jsonString),
);
}
static Serializer<Urls> get serializer => _$urlsSerializer;
}

View File

@@ -0,0 +1,275 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'urls.dart';
// **************************************************************************
// BuiltValueGenerator
// **************************************************************************
Serializer<Urls> _$urlsSerializer = new _$UrlsSerializer();
class _$UrlsSerializer implements StructuredSerializer<Urls> {
@override
final Iterable<Type> types = const [Urls, _$Urls];
@override
final String wireName = 'Urls';
@override
Iterable<Object?> serialize(
Serializers serializers,
Urls object, {
FullType specifiedType = FullType.unspecified,
}) {
final result = <Object?>[];
Object? value;
value = object.raw;
if (value != null) {
result
..add('raw')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
value = object.full;
if (value != null) {
result
..add('full')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
value = object.regular;
if (value != null) {
result
..add('regular')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
value = object.small;
if (value != null) {
result
..add('small')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
value = object.thumb;
if (value != null) {
result
..add('thumb')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
return result;
}
@override
Urls deserialize(
Serializers serializers,
Iterable<Object?> serialized, {
FullType specifiedType = FullType.unspecified,
}) {
final result = new UrlsBuilder();
final iterator = serialized.iterator;
while (iterator.moveNext()) {
final key = iterator.current! as String;
iterator.moveNext();
final Object? value = iterator.current;
switch (key) {
case 'raw':
result.raw =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
case 'full':
result.full =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
case 'regular':
result.regular =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
case 'small':
result.small =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
case 'thumb':
result.thumb =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
}
}
return result.build();
}
}
class _$Urls extends Urls {
@override
final String? raw;
@override
final String? full;
@override
final String? regular;
@override
final String? small;
@override
final String? thumb;
factory _$Urls([void Function(UrlsBuilder)? updates]) =>
(new UrlsBuilder()..update(updates))._build();
_$Urls._({this.raw, this.full, this.regular, this.small, this.thumb})
: super._();
@override
Urls rebuild(void Function(UrlsBuilder) updates) =>
(toBuilder()..update(updates)).build();
@override
UrlsBuilder toBuilder() => new UrlsBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is Urls &&
raw == other.raw &&
full == other.full &&
regular == other.regular &&
small == other.small &&
thumb == other.thumb;
}
@override
int get hashCode {
var _$hash = 0;
_$hash = $jc(_$hash, raw.hashCode);
_$hash = $jc(_$hash, full.hashCode);
_$hash = $jc(_$hash, regular.hashCode);
_$hash = $jc(_$hash, small.hashCode);
_$hash = $jc(_$hash, thumb.hashCode);
_$hash = $jf(_$hash);
return _$hash;
}
@override
String toString() {
return (newBuiltValueToStringHelper(r'Urls')
..add('raw', raw)
..add('full', full)
..add('regular', regular)
..add('small', small)
..add('thumb', thumb))
.toString();
}
}
class UrlsBuilder implements Builder<Urls, UrlsBuilder> {
_$Urls? _$v;
String? _raw;
String? get raw => _$this._raw;
set raw(String? raw) => _$this._raw = raw;
String? _full;
String? get full => _$this._full;
set full(String? full) => _$this._full = full;
String? _regular;
String? get regular => _$this._regular;
set regular(String? regular) => _$this._regular = regular;
String? _small;
String? get small => _$this._small;
set small(String? small) => _$this._small = small;
String? _thumb;
String? get thumb => _$this._thumb;
set thumb(String? thumb) => _$this._thumb = thumb;
UrlsBuilder();
UrlsBuilder get _$this {
final $v = _$v;
if ($v != null) {
_raw = $v.raw;
_full = $v.full;
_regular = $v.regular;
_small = $v.small;
_thumb = $v.thumb;
_$v = null;
}
return this;
}
@override
void replace(Urls other) {
ArgumentError.checkNotNull(other, 'other');
_$v = other as _$Urls;
}
@override
void update(void Function(UrlsBuilder)? updates) {
if (updates != null) updates(this);
}
@override
Urls build() => _build();
_$Urls _build() {
final _$result =
_$v ??
new _$Urls._(
raw: raw,
full: full,
regular: regular,
small: small,
thumb: thumb,
);
replace(_$result);
return _$result;
}
}
// ignore_for_file: deprecated_member_use_from_same_package,type=lint

View File

@@ -0,0 +1,65 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import '../serializers.dart';
import 'links.dart';
part 'user.g.dart';
abstract class User implements Built<User, UserBuilder> {
factory User([void Function(UserBuilder)? updates]) = _$User;
User._();
@BuiltValueField(wireName: 'id')
String get id;
@BuiltValueField(wireName: 'updated_at')
String? get updatedAt;
@BuiltValueField(wireName: 'username')
String get username;
@BuiltValueField(wireName: 'name')
String get name;
@BuiltValueField(wireName: 'portfolio_url')
String? get portfolioUrl;
@BuiltValueField(wireName: 'bio')
String? get bio;
@BuiltValueField(wireName: 'location')
String? get location;
@BuiltValueField(wireName: 'total_likes')
int? get totalLikes;
@BuiltValueField(wireName: 'total_photos')
int? get totalPhotos;
@BuiltValueField(wireName: 'total_collections')
int? get totalCollections;
@BuiltValueField(wireName: 'links')
Links? get links;
String toJson() {
return json.encode(serializers.serializeWith(User.serializer, this));
}
static User? fromJson(String jsonString) {
return serializers.deserializeWith(
User.serializer,
json.decode(jsonString),
);
}
static Serializer<User> get serializer => _$userSerializer;
}

View File

@@ -0,0 +1,471 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'user.dart';
// **************************************************************************
// BuiltValueGenerator
// **************************************************************************
Serializer<User> _$userSerializer = new _$UserSerializer();
class _$UserSerializer implements StructuredSerializer<User> {
@override
final Iterable<Type> types = const [User, _$User];
@override
final String wireName = 'User';
@override
Iterable<Object?> serialize(
Serializers serializers,
User object, {
FullType specifiedType = FullType.unspecified,
}) {
final result = <Object?>[
'id',
serializers.serialize(
object.id,
specifiedType: const FullType(String),
),
'username',
serializers.serialize(
object.username,
specifiedType: const FullType(String),
),
'name',
serializers.serialize(
object.name,
specifiedType: const FullType(String),
),
];
Object? value;
value = object.updatedAt;
if (value != null) {
result
..add('updated_at')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
value = object.portfolioUrl;
if (value != null) {
result
..add('portfolio_url')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
value = object.bio;
if (value != null) {
result
..add('bio')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
value = object.location;
if (value != null) {
result
..add('location')
..add(
serializers.serialize(
value,
specifiedType: const FullType(String),
),
);
}
value = object.totalLikes;
if (value != null) {
result
..add('total_likes')
..add(
serializers.serialize(value, specifiedType: const FullType(int)),
);
}
value = object.totalPhotos;
if (value != null) {
result
..add('total_photos')
..add(
serializers.serialize(value, specifiedType: const FullType(int)),
);
}
value = object.totalCollections;
if (value != null) {
result
..add('total_collections')
..add(
serializers.serialize(value, specifiedType: const FullType(int)),
);
}
value = object.links;
if (value != null) {
result
..add('links')
..add(
serializers.serialize(
value,
specifiedType: const FullType(Links),
),
);
}
return result;
}
@override
User deserialize(
Serializers serializers,
Iterable<Object?> serialized, {
FullType specifiedType = FullType.unspecified,
}) {
final result = new UserBuilder();
final iterator = serialized.iterator;
while (iterator.moveNext()) {
final key = iterator.current! as String;
iterator.moveNext();
final Object? value = iterator.current;
switch (key) {
case 'id':
result.id =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)!
as String;
break;
case 'updated_at':
result.updatedAt =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
case 'username':
result.username =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)!
as String;
break;
case 'name':
result.name =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)!
as String;
break;
case 'portfolio_url':
result.portfolioUrl =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
case 'bio':
result.bio =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
case 'location':
result.location =
serializers.deserialize(
value,
specifiedType: const FullType(String),
)
as String?;
break;
case 'total_likes':
result.totalLikes =
serializers.deserialize(
value,
specifiedType: const FullType(int),
)
as int?;
break;
case 'total_photos':
result.totalPhotos =
serializers.deserialize(
value,
specifiedType: const FullType(int),
)
as int?;
break;
case 'total_collections':
result.totalCollections =
serializers.deserialize(
value,
specifiedType: const FullType(int),
)
as int?;
break;
case 'links':
result.links.replace(
serializers.deserialize(
value,
specifiedType: const FullType(Links),
)!
as Links,
);
break;
}
}
return result.build();
}
}
class _$User extends User {
@override
final String id;
@override
final String? updatedAt;
@override
final String username;
@override
final String name;
@override
final String? portfolioUrl;
@override
final String? bio;
@override
final String? location;
@override
final int? totalLikes;
@override
final int? totalPhotos;
@override
final int? totalCollections;
@override
final Links? links;
factory _$User([void Function(UserBuilder)? updates]) =>
(new UserBuilder()..update(updates))._build();
_$User._({
required this.id,
this.updatedAt,
required this.username,
required this.name,
this.portfolioUrl,
this.bio,
this.location,
this.totalLikes,
this.totalPhotos,
this.totalCollections,
this.links,
}) : super._() {
BuiltValueNullFieldError.checkNotNull(id, r'User', 'id');
BuiltValueNullFieldError.checkNotNull(username, r'User', 'username');
BuiltValueNullFieldError.checkNotNull(name, r'User', 'name');
}
@override
User rebuild(void Function(UserBuilder) updates) =>
(toBuilder()..update(updates)).build();
@override
UserBuilder toBuilder() => new UserBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is User &&
id == other.id &&
updatedAt == other.updatedAt &&
username == other.username &&
name == other.name &&
portfolioUrl == other.portfolioUrl &&
bio == other.bio &&
location == other.location &&
totalLikes == other.totalLikes &&
totalPhotos == other.totalPhotos &&
totalCollections == other.totalCollections &&
links == other.links;
}
@override
int get hashCode {
var _$hash = 0;
_$hash = $jc(_$hash, id.hashCode);
_$hash = $jc(_$hash, updatedAt.hashCode);
_$hash = $jc(_$hash, username.hashCode);
_$hash = $jc(_$hash, name.hashCode);
_$hash = $jc(_$hash, portfolioUrl.hashCode);
_$hash = $jc(_$hash, bio.hashCode);
_$hash = $jc(_$hash, location.hashCode);
_$hash = $jc(_$hash, totalLikes.hashCode);
_$hash = $jc(_$hash, totalPhotos.hashCode);
_$hash = $jc(_$hash, totalCollections.hashCode);
_$hash = $jc(_$hash, links.hashCode);
_$hash = $jf(_$hash);
return _$hash;
}
@override
String toString() {
return (newBuiltValueToStringHelper(r'User')
..add('id', id)
..add('updatedAt', updatedAt)
..add('username', username)
..add('name', name)
..add('portfolioUrl', portfolioUrl)
..add('bio', bio)
..add('location', location)
..add('totalLikes', totalLikes)
..add('totalPhotos', totalPhotos)
..add('totalCollections', totalCollections)
..add('links', links))
.toString();
}
}
class UserBuilder implements Builder<User, UserBuilder> {
_$User? _$v;
String? _id;
String? get id => _$this._id;
set id(String? id) => _$this._id = id;
String? _updatedAt;
String? get updatedAt => _$this._updatedAt;
set updatedAt(String? updatedAt) => _$this._updatedAt = updatedAt;
String? _username;
String? get username => _$this._username;
set username(String? username) => _$this._username = username;
String? _name;
String? get name => _$this._name;
set name(String? name) => _$this._name = name;
String? _portfolioUrl;
String? get portfolioUrl => _$this._portfolioUrl;
set portfolioUrl(String? portfolioUrl) =>
_$this._portfolioUrl = portfolioUrl;
String? _bio;
String? get bio => _$this._bio;
set bio(String? bio) => _$this._bio = bio;
String? _location;
String? get location => _$this._location;
set location(String? location) => _$this._location = location;
int? _totalLikes;
int? get totalLikes => _$this._totalLikes;
set totalLikes(int? totalLikes) => _$this._totalLikes = totalLikes;
int? _totalPhotos;
int? get totalPhotos => _$this._totalPhotos;
set totalPhotos(int? totalPhotos) => _$this._totalPhotos = totalPhotos;
int? _totalCollections;
int? get totalCollections => _$this._totalCollections;
set totalCollections(int? totalCollections) =>
_$this._totalCollections = totalCollections;
LinksBuilder? _links;
LinksBuilder get links => _$this._links ??= new LinksBuilder();
set links(LinksBuilder? links) => _$this._links = links;
UserBuilder();
UserBuilder get _$this {
final $v = _$v;
if ($v != null) {
_id = $v.id;
_updatedAt = $v.updatedAt;
_username = $v.username;
_name = $v.name;
_portfolioUrl = $v.portfolioUrl;
_bio = $v.bio;
_location = $v.location;
_totalLikes = $v.totalLikes;
_totalPhotos = $v.totalPhotos;
_totalCollections = $v.totalCollections;
_links = $v.links?.toBuilder();
_$v = null;
}
return this;
}
@override
void replace(User other) {
ArgumentError.checkNotNull(other, 'other');
_$v = other as _$User;
}
@override
void update(void Function(UserBuilder)? updates) {
if (updates != null) updates(this);
}
@override
User build() => _build();
_$User _build() {
_$User _$result;
try {
_$result =
_$v ??
new _$User._(
id: BuiltValueNullFieldError.checkNotNull(id, r'User', 'id'),
updatedAt: updatedAt,
username: BuiltValueNullFieldError.checkNotNull(
username,
r'User',
'username',
),
name: BuiltValueNullFieldError.checkNotNull(
name,
r'User',
'name',
),
portfolioUrl: portfolioUrl,
bio: bio,
location: location,
totalLikes: totalLikes,
totalPhotos: totalPhotos,
totalCollections: totalCollections,
links: _links?.build(),
);
} catch (_) {
late String _$failedField;
try {
_$failedField = 'links';
_links?.build();
} catch (e) {
throw new BuiltValueNestedFieldError(
r'User',
_$failedField,
e.toString(),
);
}
rethrow;
}
replace(_$result);
return _$result;
}
}
// ignore_for_file: deprecated_member_use_from_same_package,type=lint

View File

@@ -0,0 +1,119 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:fluent_ui/fluent_ui.dart' hide Card, FluentIcons;
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
import 'package:flutter/material.dart' show Card, BorderSide;
import 'package:transparent_image/transparent_image.dart';
import 'package:url_launcher/link.dart';
import '../../unsplash_access_key.dart';
import '../unsplash/photo.dart';
final _unsplashHomepage = Uri.parse(
'https://unsplash.com/?utm_source=$unsplashAppName&utm_medium=referral',
);
typedef PhotoDetailsPhotoSaveCallback = void Function(Photo);
class PhotoDetails extends StatefulWidget {
const PhotoDetails({
required this.photo,
required this.onPhotoSave,
super.key,
});
final Photo photo;
final PhotoDetailsPhotoSaveCallback onPhotoSave;
@override
State<PhotoDetails> createState() => _PhotoDetailsState();
}
class _PhotoDetailsState extends State<PhotoDetails> {
Widget _buildPhotoAttribution(BuildContext context) {
return Row(
children: [
const Text('Photo by'),
Link(
uri: Uri.parse(
'https://unsplash.com/@${widget.photo.user!.username}?utm_source=$unsplashAppName&utm_medium=referral',
),
builder: (context, followLink) => HyperlinkButton(
onPressed: followLink,
child: Text(widget.photo.user!.name),
),
),
const Text('on'),
Link(
uri: _unsplashHomepage,
builder: (context, followLink) => HyperlinkButton(
onPressed: followLink,
child: const Text('Unsplash'),
),
),
],
);
}
@override
Widget build(BuildContext context) {
return Scrollbar(
child: SingleChildScrollView(
primary: true,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 16),
Card(
shape: ContinuousRectangleBorder(
side: const BorderSide(color: Colors.black),
borderRadius: BorderRadius.circular(4),
),
child: AnimatedSize(
duration: const Duration(milliseconds: 750),
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 40),
child: ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 400,
minHeight: 400,
),
child: FadeInImage.memoryNetwork(
placeholder: kTransparentImage,
imageSemanticLabel: widget.photo.description,
image: widget.photo.urls!.small!,
),
),
),
),
),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.only(left: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
_buildPhotoAttribution(context),
const SizedBox(width: 8),
IconButton(
icon: const Icon(
FluentIcons.arrow_download_20_regular,
size: 20,
),
onPressed: () => widget.onPhotoSave(widget.photo),
),
],
),
),
const SizedBox(height: 48),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,67 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:fluent_ui/fluent_ui.dart';
typedef PhotoSearchDialogCallback = void Function(String searchQuery);
class PhotoSearchDialog extends StatefulWidget {
const PhotoSearchDialog({required this.callback, super.key});
final PhotoSearchDialogCallback callback;
@override
State<PhotoSearchDialog> createState() => _PhotoSearchDialogState();
}
class _PhotoSearchDialogState extends State<PhotoSearchDialog> {
final _controller = TextEditingController();
bool _searchEnabled = false;
@override
void initState() {
super.initState();
_controller.addListener(() {
setState(() {
_searchEnabled = _controller.text.isNotEmpty;
});
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => ContentDialog(
title: const Text('Photo Search'),
content: TextBox(
autofocus: true,
controller: _controller,
onSubmitted: (content) {
if (content.isNotEmpty) {
widget.callback(content);
Navigator.of(context).pop();
}
},
),
actions: [
FilledButton(
onPressed: _searchEnabled
? () {
widget.callback(_controller.text);
Navigator.of(context).pop();
}
: null,
child: const Text('Search'),
),
Button(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Cancel'),
),
],
);
}

View File

@@ -0,0 +1,80 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/gestures.dart';
import 'package:url_launcher/url_launcher.dart' as url_launcher;
class PolicyDialog extends StatelessWidget {
const PolicyDialog({super.key});
@override
Widget build(BuildContext context) {
return ContentDialog(
title: const Text('Terms & Conditions'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RichText(
textAlign: TextAlign.left,
text: TextSpan(
text: '• ',
style: const TextStyle(color: Colors.black, fontSize: 18),
children: [
TextSpan(
text: 'https://policies.google.com/terms',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.blue.normal,
),
recognizer: TapGestureRecognizer()
..onTap = () async {
final url = Uri.parse(
'https://policies.google.com/terms',
);
if (await url_launcher.canLaunchUrl(url)) {
await url_launcher.launchUrl(url);
}
},
),
],
),
),
RichText(
textAlign: TextAlign.left,
text: TextSpan(
text: '• ',
style: const TextStyle(color: Colors.black, fontSize: 18),
children: [
TextSpan(
text: 'https://unsplash.com/terms',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.blue.normal,
),
recognizer: TapGestureRecognizer()
..onTap = () async {
final url = Uri.parse('https://unsplash.com/terms');
if (await url_launcher.canLaunchUrl(url)) {
await url_launcher.launchUrl(url);
}
},
),
],
),
),
],
),
actions: [
FilledButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Accept'),
),
],
);
}
}

View File

@@ -0,0 +1,181 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
/// A widget that takes two children, lays them out along [axis], and allows
/// the user to resize them.
///
/// The user can customize the amount of space allocated to each child by
/// dragging a divider between them.
///
/// [initialFirstFraction] defines how much space to give the [firstChild]
/// when first building this widget. [secondChild] will take the remaining
/// space.
///
/// The user can drag the widget with key [dividerKey] to change
/// the space allocated between [firstChild] and [secondChild].
class Split extends StatefulWidget {
/// Builds a split oriented along [axis].
const Split({
super.key,
required this.axis,
required this.firstChild,
required this.secondChild,
double? initialFirstFraction,
}) : initialFirstFraction = initialFirstFraction ?? 0.5;
/// The main axis the children will lay out on.
///
/// If [Axis.horizontal], the children will be placed in a [Row]
/// and they will be horizontally resizable.
///
/// If [Axis.vertical], the children will be placed in a [Column]
/// and they will be vertically resizable.
///
/// Cannot be null.
final Axis axis;
/// The child that will be laid out first along [axis].
final Widget firstChild;
/// The child that will be laid out last along [axis].
final Widget secondChild;
/// The fraction of the layout to allocate to [firstChild].
///
/// [secondChild] will receive a fraction of `1 - initialFirstFraction`.
final double initialFirstFraction;
/// The key passed to the divider between [firstChild] and [secondChild].
///
/// Visible to grab it in tests.
@visibleForTesting
Key get dividerKey => Key('$this dividerKey');
/// The size of the divider between [firstChild] and [secondChild] in
/// logical pixels (dp, not px).
static const double dividerMainAxisSize = 10;
static Axis axisFor(BuildContext context, double horizontalAspectRatio) {
final screenSize = MediaQuery.of(context).size;
final aspectRatio = screenSize.width / screenSize.height;
if (aspectRatio >= horizontalAspectRatio) {
return Axis.horizontal;
}
return Axis.vertical;
}
@override
State<StatefulWidget> createState() => _SplitState();
}
class _SplitState extends State<Split> {
late double firstFraction;
double get secondFraction => 1 - firstFraction;
bool get isHorizontal => widget.axis == Axis.horizontal;
@override
void initState() {
super.initState();
firstFraction = widget.initialFirstFraction;
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: _buildLayout);
}
Widget _buildLayout(BuildContext context, BoxConstraints constraints) {
final width = constraints.maxWidth;
final height = constraints.maxHeight;
final axisSize = isHorizontal ? width : height;
final crossAxisSize = isHorizontal ? height : width;
const halfDivider = Split.dividerMainAxisSize / 2.0;
// Determine what fraction to give each child, including enough space to
// display the divider.
var firstSize = axisSize * firstFraction;
var secondSize = axisSize * secondFraction;
// Clamp the sizes to be sure there is enough space for the dividers.
firstSize = firstSize.clamp(halfDivider, axisSize - halfDivider);
secondSize = secondSize.clamp(halfDivider, axisSize - halfDivider);
// Remove space from each child to place the divider in the middle.
firstSize = firstSize - halfDivider;
secondSize = secondSize - halfDivider;
void updateSpacing(DragUpdateDetails dragDetails) {
final delta = isHorizontal ? dragDetails.delta.dx : dragDetails.delta.dy;
final fractionalDelta = delta / axisSize;
setState(() {
// Update the fraction of space consumed by the children,
// being sure not to allocate any negative space.
firstFraction += fractionalDelta;
firstFraction = firstFraction.clamp(0.0, 1.0);
});
}
final dragIndicator = Flex(
direction: isHorizontal ? Axis.vertical : Axis.horizontal,
mainAxisSize: MainAxisSize.min,
children: [
for (var i = 0; i < min(crossAxisSize / 6.0, 3).floor(); i++)
Padding(
padding: EdgeInsets.symmetric(
vertical: isHorizontal ? 2.0 : 0.0,
horizontal: isHorizontal ? 0.0 : 2.0,
),
child: DecoratedBox(
decoration: BoxDecoration(
color: Theme.of(context).dividerColor,
borderRadius: BorderRadius.circular(
Split.dividerMainAxisSize,
),
),
child: SizedBox(
height: isHorizontal ? 2.0 : Split.dividerMainAxisSize - 2.0,
width: isHorizontal ? Split.dividerMainAxisSize - 2.0 : 2.0,
),
),
),
],
);
final children = [
SizedBox(
width: isHorizontal ? firstSize : width,
height: isHorizontal ? height : firstSize,
child: widget.firstChild,
),
GestureDetector(
key: widget.dividerKey,
behavior: HitTestBehavior.translucent,
onHorizontalDragUpdate: isHorizontal ? updateSpacing : null,
onVerticalDragUpdate: isHorizontal ? null : updateSpacing,
// DartStartBehavior.down is needed to keep the mouse pointer stuck to
// the drag bar. There still appears to be a few frame lag before the
// drag action triggers which is't ideal but isn't a launch blocker.
dragStartBehavior: DragStartBehavior.down,
child: SizedBox(
width: isHorizontal ? Split.dividerMainAxisSize : width,
height: isHorizontal ? height : Split.dividerMainAxisSize,
child: Center(child: dragIndicator),
),
),
SizedBox(
width: isHorizontal ? secondSize : width,
height: isHorizontal ? height : secondSize,
child: widget.secondChild,
),
];
return Flex(direction: widget.axis, children: children);
}
}

View File

@@ -0,0 +1,115 @@
// Copyright 2022 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/gestures.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../unsplash_access_key.dart';
final _unsplashHomepage = Uri.parse(
'https://unsplash.com/?utm_source=${Uri.encodeFull(unsplashAppName)}&utm_medium=referral',
);
final _unsplashPrivacyPolicy = Uri.parse(
'https://unsplash.com/privacy?utm_source=${Uri.encodeFull(unsplashAppName)}&utm_medium=referral',
);
class UnsplashNotice extends StatefulWidget {
const UnsplashNotice({super.key, required this.child});
final Widget child;
@override
State<UnsplashNotice> createState() => _UnsplashNoticeState();
}
class _UnsplashNoticeState extends State<UnsplashNotice> {
bool noticeAccepted = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
showDialog(
context: context,
builder: (context) {
return _UnsplashDialog(
accepted: () {
setState(() {
noticeAccepted = true;
});
},
);
},
);
});
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}
class _UnsplashDialog extends StatelessWidget {
const _UnsplashDialog({required this.accepted});
final Function accepted;
@override
Widget build(BuildContext context) {
return ContentDialog(
title: const Text('Unsplash Notice'),
content: RichText(
text: TextSpan(
text:
'This is a sample desktop application provided by Google'
' that enables you to search ',
style: const TextStyle(color: Colors.grey),
children: [
TextSpan(
text: 'Unsplash',
recognizer: TapGestureRecognizer()
..onTap = () async {
if (!await launchUrl(_unsplashHomepage)) {
throw 'Could not launch $_unsplashHomepage';
}
},
style: TextStyle(color: Colors.blue),
),
const TextSpan(
text:
' for photographs that interest you. When you search'
' for and interact with photos, Unsplash will collect'
' information about you and your use of the Unsplash'
' services. Learn more about ',
style: TextStyle(color: Colors.grey),
),
TextSpan(
text: 'how Unsplash collects and uses data',
recognizer: TapGestureRecognizer()
..onTap = () async {
if (!await launchUrl(_unsplashPrivacyPolicy)) {
throw 'Could not launch $_unsplashPrivacyPolicy';
}
},
style: TextStyle(color: Colors.blue),
),
const TextSpan(
text: '.',
style: TextStyle(color: Colors.grey),
),
],
),
),
actions: [
Button(
child: const Text('Got it'),
onPressed: () {
accepted();
Navigator.pop(context);
},
),
],
);
}
}

View File

@@ -0,0 +1,108 @@
// Copyright 2022 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:file_selector/file_selector.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:provider/provider.dart';
import '../model/photo_search_model.dart';
import '../unsplash/photo.dart';
import '../widgets/photo_details.dart';
import '../widgets/split.dart' as split;
class UnsplashSearchContent extends StatefulWidget {
const UnsplashSearchContent({super.key});
@override
State<UnsplashSearchContent> createState() => _UnsplashSearchContentState();
}
class _UnsplashSearchContentState extends State<UnsplashSearchContent> {
final _treeViewScrollController = ScrollController();
@override
dispose() {
_treeViewScrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final photoSearchModel = Provider.of<PhotoSearchModel>(context);
return split.Split(
axis: Axis.horizontal,
initialFirstFraction: 0.4,
firstChild: Scrollbar(
controller: _treeViewScrollController,
child: SingleChildScrollView(
controller: _treeViewScrollController,
child: TreeView(
items: photoSearchModel.entries.map(_buildSearchEntry).toList(),
),
),
),
secondChild: Center(
child: photoSearchModel.selectedPhoto != null
? PhotoDetails(
photo: photoSearchModel.selectedPhoto!,
onPhotoSave: (photo) async {
final saveLocation = await getSaveLocation(
suggestedName: '${photo.id}.jpg',
acceptedTypeGroups: [
const XTypeGroup(
label: 'JPG',
extensions: ['jpg'],
mimeTypes: ['image/jpeg'],
),
],
);
if (saveLocation != null) {
final fileData = await photoSearchModel.download(
photo: photo,
);
final photoFile = XFile.fromData(
fileData,
mimeType: 'image/jpeg',
);
await photoFile.saveTo(saveLocation.path);
}
},
)
: Container(),
),
);
}
TreeViewItem _buildSearchEntry(SearchEntry searchEntry) {
void selectPhoto(Photo photo) {
searchEntry.model.selectedPhoto = photo;
}
String labelForPhoto(Photo photo) => 'Photo by ${photo.user!.name}';
return TreeViewItem(
content: Text(searchEntry.query),
children: searchEntry.photos
.map<TreeViewItem>(
(photo) => TreeViewItem(
content: Semantics(
button: true,
onTap: () => selectPhoto(photo),
label: labelForPhoto(photo),
excludeSemantics: true,
child: GestureDetector(
onTap: () => selectPhoto(photo),
child: Text(
labelForPhoto(photo),
style: const TextStyle(color: Colors.black),
),
),
),
),
)
.toList(),
);
}
}

View File

@@ -0,0 +1,9 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// To use this sample: Retrieve an API Access Key from https://unsplash.com/developers
const String unsplashAccessKey = '';
// The application name for the above API Access Key.
const unsplashAppName = '';

View File

@@ -0,0 +1 @@
flutter/ephemeral

View File

@@ -0,0 +1,145 @@
# Project-level configuration.
cmake_minimum_required(VERSION 3.10)
project(runner LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "desktop_photo_search")
# The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "com.example.desktop_photo_search")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
cmake_policy(SET CMP0063 NEW)
# Load bundled libraries from the lib/ directory relative to the binary.
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Root filesystem for cross-building.
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endif()
# Define build configuration options.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
# Compilation settings that should be applied to most targets.
#
# Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead
# of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_14)
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
endfunction()
# Flutter library and tool build rules.
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
add_subdirectory(${FLUTTER_MANAGED_DIR})
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
# Define the application target. To change its name, change BINARY_NAME above,
# not the value here, or `flutter run` will no longer work.
#
# Any new source files that you add to the application should be added here.
add_executable(${BINARY_NAME}
"main.cc"
"my_application.cc"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
)
# Apply the standard set of build settings. This can be removed for applications
# that need different build settings.
apply_standard_settings(${BINARY_NAME})
# Add dependency libraries. Add any application-specific dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
# Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble)
# Only the install-generated bundle's copy of the executable will launch
# correctly, since the resources must in the right relative locations. To avoid
# people trying to run the unbundled copy, put it in a subdirectory instead of
# the default top-level location.
set_target_properties(${BINARY_NAME}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
)
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# By default, "installing" just makes a relocatable bundle in the build
# directory.
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
# Start with a clean build bundle directory every time.
install(CODE "
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
" COMPONENT Runtime)
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
install(FILES "${bundled_library}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endforeach(bundled_library)
# Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()

View File

@@ -0,0 +1,88 @@
# This file controls Flutter-level build steps. It should not be edited.
cmake_minimum_required(VERSION 3.10)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
# which isn't available in 3.10.
function(list_prepend LIST_NAME PREFIX)
set(NEW_LIST "")
foreach(element ${${LIST_NAME}})
list(APPEND NEW_LIST "${PREFIX}${element}")
endforeach(element)
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
endfunction()
# === Flutter Library ===
# System-level dependencies.
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"fl_basic_message_channel.h"
"fl_binary_codec.h"
"fl_binary_messenger.h"
"fl_dart_project.h"
"fl_engine.h"
"fl_json_message_codec.h"
"fl_json_method_codec.h"
"fl_message_codec.h"
"fl_method_call.h"
"fl_method_channel.h"
"fl_method_codec.h"
"fl_method_response.h"
"fl_plugin_registrar.h"
"fl_plugin_registry.h"
"fl_standard_message_codec.h"
"fl_standard_method_codec.h"
"fl_string_codec.h"
"fl_value.h"
"fl_view.h"
"flutter_linux.h"
)
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
target_link_libraries(flutter INTERFACE
PkgConfig::GTK
PkgConfig::GLIB
PkgConfig::GIO
)
add_dependencies(flutter flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/_phony_
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
)

View File

@@ -0,0 +1,23 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
#include <file_selector_linux/file_selector_plugin.h>
#include <menubar/menubar_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
g_autoptr(FlPluginRegistrar) menubar_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "MenubarPlugin");
menubar_plugin_register_with_registrar(menubar_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
}

View File

@@ -0,0 +1,15 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter_linux/flutter_linux.h>
// Registers Flutter plugins.
void fl_register_plugins(FlPluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_

View File

@@ -0,0 +1,26 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
file_selector_linux
menubar
url_launcher_linux
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)

View File

@@ -0,0 +1,6 @@
#include "my_application.h"
int main(int argc, char** argv) {
g_autoptr(MyApplication) app = my_application_new();
return g_application_run(G_APPLICATION(app), argc, argv);
}

View File

@@ -0,0 +1,104 @@
#include "my_application.h"
#include <flutter_linux/flutter_linux.h>
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#endif
#include "flutter/generated_plugin_registrant.h"
struct _MyApplication {
GtkApplication parent_instance;
char** dart_entrypoint_arguments;
};
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application);
GtkWindow* window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
// Use a header bar when running in GNOME as this is the common style used
// by applications and is the setup most users will be using (e.g. Ubuntu
// desktop).
// If running on X and not using GNOME then just use a traditional title bar
// in case the window manager does more exotic layout, e.g. tiling.
// If running on Wayland assume the header bar will work (may need changing
// if future cases occur).
gboolean use_header_bar = TRUE;
#ifdef GDK_WINDOWING_X11
GdkScreen* screen = gtk_window_get_screen(window);
if (GDK_IS_X11_SCREEN(screen)) {
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
use_header_bar = FALSE;
}
}
#endif
if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar));
gtk_header_bar_set_title(header_bar, "desktop_photo_search");
gtk_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else {
gtk_window_set_title(window, "desktop_photo_search");
}
gtk_window_set_default_size(window, 1280, 720);
gtk_widget_show(GTK_WIDGET(window));
g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
FlView* view = fl_view_new(project);
gtk_widget_show(GTK_WIDGET(view));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
gtk_widget_grab_focus(GTK_WIDGET(view));
}
// Implements GApplication::local_command_line.
static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
MyApplication* self = MY_APPLICATION(application);
// Strip out the first argument as it is the binary name.
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
g_autoptr(GError) error = nullptr;
if (!g_application_register(application, nullptr, &error)) {
g_warning("Failed to register: %s", error->message);
*exit_status = 1;
return TRUE;
}
g_application_activate(application);
*exit_status = 0;
return TRUE;
}
// Implements GObject::dispose.
static void my_application_dispose(GObject* object) {
MyApplication* self = MY_APPLICATION(object);
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
}
static void my_application_class_init(MyApplicationClass* klass) {
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
}
static void my_application_init(MyApplication* self) {}
MyApplication* my_application_new() {
return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID,
"flags", G_APPLICATION_NON_UNIQUE,
nullptr));
}

View File

@@ -0,0 +1,18 @@
#ifndef FLUTTER_MY_APPLICATION_H_
#define FLUTTER_MY_APPLICATION_H_
#include <gtk/gtk.h>
G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
GtkApplication)
/**
* my_application_new:
*
* Creates a new Flutter-based application.
*
* Returns: a new #MyApplication.
*/
MyApplication* my_application_new();
#endif // FLUTTER_MY_APPLICATION_H_

View File

@@ -0,0 +1,7 @@
# Flutter-related
**/Flutter/ephemeral/
**/Pods/
# Xcode-related
**/dgph
**/xcuserdata/

View File

@@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"

View File

@@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"

View File

@@ -0,0 +1,16 @@
//
// Generated file. Do not edit.
//
import FlutterMacOS
import Foundation
import file_selector_macos
import menubar
import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
MenubarPlugin.register(with: registry.registrar(forPlugin: "MenubarPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
}

View File

@@ -0,0 +1,43 @@
platform :osx, '10.14'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_macos_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_macos_build_settings(target)
end
end

View File

@@ -0,0 +1,791 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXAggregateTarget section */
33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
isa = PBXAggregateTarget;
buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
buildPhases = (
33CC111E2044C6BF0003C045 /* ShellScript */,
);
dependencies = (
);
name = "Flutter Assemble";
productName = FLX;
};
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
5338CCEA202DE1A124991C49 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 10C47C9C15752FE372FF2CDC /* Pods_RunnerTests.framework */; };
77F5965C35CDF5C41CB7B903 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7209208C496CE7646ED7B1C /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 33CC10EC2044A3C60003C045;
remoteInfo = Runner;
};
33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 33CC111A2044C6BA0003C045;
remoteInfo = FLX;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
33CC110E2044A8840003C045 /* Bundle Framework */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Bundle Framework";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
089E3F387724FC6ACEE208BB /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
10C47C9C15752FE372FF2CDC /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3287A54F487A9CDCF32D0B46 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
33CC10ED2044A3C60003C045 /* desktop_photo_search.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = desktop_photo_search.app; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; };
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
41F2FB3D8E017B613967822C /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
9628514E18CED01B58493966 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
A198730F036570B6263D1758 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
A7209208C496CE7646ED7B1C /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
CEDA18B700CC21D86FE950A1 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
331C80D2294CF70F00263BE5 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5338CCEA202DE1A124991C49 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
33CC10EA2044A3C60003C045 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
77F5965C35CDF5C41CB7B903 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C80D6294CF71000263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C80D7294CF71000263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
33BA886A226E78AF003329D5 /* Configs */ = {
isa = PBXGroup;
children = (
33E5194F232828860026EE4D /* AppInfo.xcconfig */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
);
path = Configs;
sourceTree = "<group>";
};
33CC10E42044A3C60003C045 = {
isa = PBXGroup;
children = (
33FAB671232836740065AC1E /* Runner */,
33CEB47122A05771004F2AC0 /* Flutter */,
331C80D6294CF71000263BE5 /* RunnerTests */,
33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */,
B357E0D4BD0F93B773AE2F88 /* Pods */,
);
sourceTree = "<group>";
};
33CC10EE2044A3C60003C045 /* Products */ = {
isa = PBXGroup;
children = (
33CC10ED2044A3C60003C045 /* desktop_photo_search.app */,
331C80D5294CF71000263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
33CC11242044D66E0003C045 /* Resources */ = {
isa = PBXGroup;
children = (
33CC10F22044A3C60003C045 /* Assets.xcassets */,
33CC10F42044A3C60003C045 /* MainMenu.xib */,
33CC10F72044A3C60003C045 /* Info.plist */,
);
name = Resources;
path = ..;
sourceTree = "<group>";
};
33CEB47122A05771004F2AC0 /* Flutter */ = {
isa = PBXGroup;
children = (
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
);
path = Flutter;
sourceTree = "<group>";
};
33FAB671232836740065AC1E /* Runner */ = {
isa = PBXGroup;
children = (
33CC10F02044A3C60003C045 /* AppDelegate.swift */,
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
33E51913231747F40026EE4D /* DebugProfile.entitlements */,
33E51914231749380026EE4D /* Release.entitlements */,
33CC11242044D66E0003C045 /* Resources */,
33BA886A226E78AF003329D5 /* Configs */,
);
path = Runner;
sourceTree = "<group>";
};
B357E0D4BD0F93B773AE2F88 /* Pods */ = {
isa = PBXGroup;
children = (
3287A54F487A9CDCF32D0B46 /* Pods-Runner.debug.xcconfig */,
A198730F036570B6263D1758 /* Pods-Runner.release.xcconfig */,
089E3F387724FC6ACEE208BB /* Pods-Runner.profile.xcconfig */,
CEDA18B700CC21D86FE950A1 /* Pods-RunnerTests.debug.xcconfig */,
41F2FB3D8E017B613967822C /* Pods-RunnerTests.release.xcconfig */,
9628514E18CED01B58493966 /* Pods-RunnerTests.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
isa = PBXGroup;
children = (
A7209208C496CE7646ED7B1C /* Pods_Runner.framework */,
10C47C9C15752FE372FF2CDC /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C80D4294CF70F00263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
780C18E3312D0BA7833E617F /* [CP] Check Pods Manifest.lock */,
331C80D1294CF70F00263BE5 /* Sources */,
331C80D2294CF70F00263BE5 /* Frameworks */,
331C80D3294CF70F00263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C80DA294CF71000263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
33CC10EC2044A3C60003C045 /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
EC8EEAE7E4E25AF7CDF53458 /* [CP] Check Pods Manifest.lock */,
33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
40D98D982D3C9F50CF1E0699 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
33CC11202044C79F0003C045 /* PBXTargetDependency */,
);
name = Runner;
productName = Runner;
productReference = 33CC10ED2044A3C60003C045 /* desktop_photo_search.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
33CC10E52044A3C60003C045 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1430;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C80D4294CF70F00263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 33CC10EC2044A3C60003C045;
};
33CC10EC2044A3C60003C045 = {
CreatedOnToolsVersion = 9.2;
LastSwiftMigration = 1100;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.Sandbox = {
enabled = 1;
};
};
};
33CC111A2044C6BA0003C045 = {
CreatedOnToolsVersion = 9.2;
ProvisioningStyle = Manual;
};
};
};
buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 33CC10E42044A3C60003C045;
productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
33CC10EC2044A3C60003C045 /* Runner */,
331C80D4294CF70F00263BE5 /* RunnerTests */,
33CC111A2044C6BA0003C045 /* Flutter Assemble */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C80D3294CF70F00263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
33CC10EB2044A3C60003C045 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3399D490228B24CF009A79C7 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
};
33CC111E2044C6BF0003C045 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
Flutter/ephemeral/FlutterInputs.xcfilelist,
);
inputPaths = (
Flutter/ephemeral/tripwire,
);
outputFileListPaths = (
Flutter/ephemeral/FlutterOutputs.xcfilelist,
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
};
40D98D982D3C9F50CF1E0699 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
780C18E3312D0BA7833E617F /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
EC8EEAE7E4E25AF7CDF53458 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C80D1294CF70F00263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
33CC10E92044A3C60003C045 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C80DA294CF71000263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 33CC10EC2044A3C60003C045 /* Runner */;
targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */;
};
33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
isa = PBXVariantGroup;
children = (
33CC10F52044A3C60003C045 /* Base */,
);
name = MainMenu.xib;
path = Runner;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
331C80DB294CF71000263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = CEDA18B700CC21D86FE950A1 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.desktopPhotoSearch.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/desktop_photo_search.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/desktop_photo_search";
};
name = Debug;
};
331C80DC294CF71000263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 41F2FB3D8E017B613967822C /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.desktopPhotoSearch.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/desktop_photo_search.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/desktop_photo_search";
};
name = Release;
};
331C80DD294CF71000263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9628514E18CED01B58493966 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.desktopPhotoSearch.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/desktop_photo_search.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/desktop_photo_search";
};
name = Profile;
};
338D0CE9231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Profile;
};
338D0CEA231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Profile;
};
338D0CEB231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Manual;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Profile;
};
33CC10F92044A3C60003C045 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
33CC10FA2044A3C60003C045 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Release;
};
33CC10FC2044A3C60003C045 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
33CC10FD2044A3C60003C045 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Release;
};
33CC111C2044C6BA0003C045 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Manual;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
33CC111D2044C6BA0003C045 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C80DB294CF71000263BE5 /* Debug */,
331C80DC294CF71000263BE5 /* Release */,
331C80DD294CF71000263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
33CC10F92044A3C60003C045 /* Debug */,
33CC10FA2044A3C60003C045 /* Release */,
338D0CE9231458BD00FA5F75 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
33CC10FC2044A3C60003C045 /* Debug */,
33CC10FD2044A3C60003C045 /* Release */,
338D0CEA231458BD00FA5F75 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
isa = XCConfigurationList;
buildConfigurations = (
33CC111C2044C6BA0003C045 /* Debug */,
33CC111D2044C6BA0003C045 /* Release */,
338D0CEB231458BD00FA5F75 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 33CC10E52044A3C60003C045 /* Project object */;
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "desktop_photo_search.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "desktop_photo_search.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C80D4294CF70F00263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "desktop_photo_search.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "desktop_photo_search.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,9 @@
import Cocoa
import FlutterMacOS
@NSApplicationMain
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
}

View File

@@ -0,0 +1,68 @@
{
"images" : [
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "app_icon_16.png",
"scale" : "1x"
},
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "app_icon_32.png",
"scale" : "2x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "app_icon_32.png",
"scale" : "1x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "app_icon_64.png",
"scale" : "2x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "app_icon_128.png",
"scale" : "1x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "app_icon_256.png",
"scale" : "2x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "app_icon_256.png",
"scale" : "1x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "app_icon_512.png",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "app_icon_512.png",
"scale" : "1x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "app_icon_1024.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,343 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target">
<connections>
<outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
<outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
</connections>
</customObject>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="APP_NAME" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="APP_NAME" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="About APP_NAME" id="5kV-Vb-QxS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
<menuItem title="Services" id="NMo-om-nkz">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
<menuItem title="Hide APP_NAME" keyEquivalent="h" id="Olw-nP-bQN">
<connections>
<action selector="hide:" target="-1" id="PnN-Uc-m68"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
</connections>
</menuItem>
<menuItem title="Show All" id="Kd2-mp-pUS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
<menuItem title="Quit APP_NAME" keyEquivalent="q" id="4sb-4s-VLi">
<connections>
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Edit" id="5QF-Oa-p0T">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
<items>
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
<connections>
<action selector="undo:" target="-1" id="M6e-cu-g7V"/>
</connections>
</menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
<connections>
<action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
<connections>
<action selector="cut:" target="-1" id="YJe-68-I9s"/>
</connections>
</menuItem>
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
<connections>
<action selector="copy:" target="-1" id="G1f-GL-Joy"/>
</connections>
</menuItem>
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
<connections>
<action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
</connections>
</menuItem>
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
</connections>
</menuItem>
<menuItem title="Delete" id="pa3-QI-u2k">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
</connections>
</menuItem>
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
<connections>
<action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
<menuItem title="Find" id="4EN-yA-p0u">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Find" id="1b7-l0-nxx">
<items>
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
<connections>
<action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
</connections>
</menuItem>
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
</connections>
</menuItem>
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
<connections>
<action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
</connections>
</menuItem>
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
<connections>
<action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
</connections>
</menuItem>
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
<connections>
<action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
</connections>
</menuItem>
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
<connections>
<action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
<items>
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
<connections>
<action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
</connections>
</menuItem>
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
<connections>
<action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
</connections>
</menuItem>
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
</connections>
</menuItem>
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Substitutions" id="9ic-FL-obx">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
<items>
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
</connections>
</menuItem>
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
</connections>
</menuItem>
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
</connections>
</menuItem>
<menuItem title="Smart Links" id="cwL-P1-jid">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
</connections>
</menuItem>
<menuItem title="Data Detectors" id="tRr-pd-1PS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
</connections>
</menuItem>
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Transformations" id="2oI-Rn-ZJC">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
<items>
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
</connections>
</menuItem>
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
</connections>
</menuItem>
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Speech" id="xrE-MZ-jX0">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
<items>
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
</connections>
</menuItem>
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="View" id="H8h-7b-M4v">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="View" id="HyV-fh-RgO">
<items>
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="toggleFullScreen:" target="-1" id="dU3-MA-1Rq"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Window" id="aUF-d1-5bR">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
<items>
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
<connections>
<action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
</connections>
</menuItem>
<menuItem title="Zoom" id="R4o-n2-Eq4">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Help" id="EPT-qC-fAb">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Help" systemMenu="help" id="rJ0-wn-3NY"/>
</menuItem>
</items>
<point key="canvasLocation" x="142" y="-258"/>
</menu>
<window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<rect key="contentRect" x="335" y="390" width="800" height="600"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
<autoresizingMask key="autoresizingMask"/>
</view>
</window>
</objects>
</document>

View File

@@ -0,0 +1,14 @@
// Application-level settings for the Runner target.
//
// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
// future. If not, the values below would default to using the project name when this becomes a
// 'flutter create' template.
// The application's name. By default this is also the title of the Flutter window.
PRODUCT_NAME = desktop_photo_search
// The application's bundle identifier
PRODUCT_BUNDLE_IDENTIFIER = com.example.desktopPhotoSearch
// The copyright displayed in application information
PRODUCT_COPYRIGHT = Copyright © 2023 com.example. All rights reserved.

View File

@@ -0,0 +1,2 @@
#include "../../Flutter/Flutter-Debug.xcconfig"
#include "Warnings.xcconfig"

View File

@@ -0,0 +1,2 @@
#include "../../Flutter/Flutter-Release.xcconfig"
#include "Warnings.xcconfig"

View File

@@ -0,0 +1,13 @@
WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
GCC_WARN_UNDECLARED_SELECTOR = YES
CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
CLANG_WARN_PRAGMA_PACK = YES
CLANG_WARN_STRICT_PROTOTYPES = YES
CLANG_WARN_COMMA = YES
GCC_WARN_STRICT_SELECTOR_MATCH = YES
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
GCC_WARN_SHADOW = YES
CLANG_WARN_UNREACHABLE_CODE = YES

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>$(PRODUCT_COPYRIGHT)</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

View File

@@ -0,0 +1,15 @@
import Cocoa
import FlutterMacOS
class MainFlutterWindow: NSWindow {
override func awakeFromNib() {
let flutterViewController = FlutterViewController()
let windowFrame = self.frame
self.contentViewController = flutterViewController
self.setFrame(windowFrame, display: true)
RegisterGeneratedPlugins(registry: flutterViewController)
super.awakeFromNib()
}
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,12 @@
import FlutterMacOS
import Cocoa
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}

View File

@@ -0,0 +1,55 @@
name: desktop_photo_search
description: Search for Photos, using the Unsplash API.
publish_to: 'none'
version: 1.0.0+1
resolution: workspace
environment:
sdk: ^3.9.0-0
dependencies:
built_collection: ^5.1.1
built_value: ^8.6.1
cupertino_icons: ^1.0.5
file_selector: ^1.0.0
fluent_ui: ^4.7.2
fluentui_system_icons: ^1.1.208
flutter:
sdk: flutter
http: ^1.2.1
logging: ^1.2.0
menubar:
git:
url: https://github.com/google/flutter-desktop-embedding.git
path: plugins/menubar
provider: ^6.0.5
transparent_image: ^2.0.1
url_launcher: ^6.1.12
uuid: ^4.0.0
dev_dependencies:
analysis_defaults:
path: ../../analysis_defaults
async: ^2.11.0
build: ^3.0.0
build_runner: ^2.4.6
built_value_generator: ^8.6.1
flutter_test:
sdk: flutter
grinder: ^0.9.4
msix: ^3.16.1
flutter:
uses-material-design: true
msix_config:
display_name: Flutter Desktop Photo Search
publisher_display_name: flutter.dev
store: false # Set to true to deploy to Microsoft Store
publisher: CN=01A6D5C0-D51A-4EEE-8DD0-F134DDD378F7
identity_name: 16354flutter.dev.FlutterDesktopPhotoSearch
msix_version: 1.0.0.1
icons_background_color: "#ffffff"
architecture: x64
# See https://docs.microsoft.com/en-us/windows/uwp/packaging/app-capability-declarations
capabilities: "internetClient"

View File

@@ -0,0 +1,538 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:desktop_photo_search/src/unsplash/photo.dart';
import 'package:desktop_photo_search/src/unsplash/search_photos_response.dart';
import 'package:desktop_photo_search/src/unsplash/unsplash.dart';
import 'package:desktop_photo_search/src/unsplash/user.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart';
import 'package:http/testing.dart';
void main() {
test('Photo.fromJson', () {
const input = '''
{
"id": "Dwu85P9SOIk",
"created_at": "2016-05-03T11:00:28-04:00",
"updated_at": "2016-07-10T11:00:01-05:00",
"width": 2448,
"height": 3264,
"color": "#6E633A",
"downloads": 1345,
"likes": 24,
"liked_by_user": false,
"description": "A man drinking a coffee.",
"exif": {
"make": "Canon",
"model": "Canon EOS 40D",
"exposure_time": "0.011111111111111112",
"aperture": "4.970854",
"focal_length": "37",
"iso": 100
},
"location": {
"city": "Montreal",
"country": "Canada",
"position": {
"latitude": 45.4732984,
"longitude": -73.6384879
}
},
"tags": [
{ "title": "man" },
{ "title": "drinking" },
{ "title": "coffee" }
],
"current_user_collections": [
{
"id": 206,
"title": "Makers: Cat and Ben",
"published_at": "2016-01-12T18:16:09-05:00",
"updated_at": "2016-07-10T11:00:01-05:00",
"cover_photo": null,
"user": null
}
],
"urls": {
"raw": "https://images.unsplash.com/photo-1417325384643-aac51acc9e5d",
"full": "https://images.unsplash.com/photo-1417325384643-aac51acc9e5d?q=75&fm=jpg",
"regular": "https://images.unsplash.com/photo-1417325384643-aac51acc9e5d?q=75&fm=jpg&w=1080&fit=max",
"small": "https://images.unsplash.com/photo-1417325384643-aac51acc9e5d?q=75&fm=jpg&w=400&fit=max",
"thumb": "https://images.unsplash.com/photo-1417325384643-aac51acc9e5d?q=75&fm=jpg&w=200&fit=max"
},
"links": {
"self": "https://api.unsplash.com/photos/Dwu85P9SOIk",
"html": "https://unsplash.com/photos/Dwu85P9SOIk",
"download": "https://unsplash.com/photos/Dwu85P9SOIk/download",
"download_location": "https://api.unsplash.com/photos/Dwu85P9SOIk/download"
},
"user": {
"id": "QPxL2MGqfrw",
"updated_at": "2016-07-10T11:00:01-05:00",
"username": "exampleuser",
"name": "Joe Example",
"portfolio_url": "https://example.com/",
"bio": "Just an everyday Joe",
"location": "Montreal",
"total_likes": 5,
"total_photos": 10,
"total_collections": 13,
"links": {
"self": "https://api.unsplash.com/users/exampleuser",
"html": "https://unsplash.com/exampleuser",
"photos": "https://api.unsplash.com/users/exampleuser/photos",
"likes": "https://api.unsplash.com/users/exampleuser/likes",
"portfolio": "https://api.unsplash.com/users/exampleuser/portfolio"
}
}
}
''';
final photo = Photo.fromJson(input)!;
expect(photo.id, 'Dwu85P9SOIk');
expect(photo.createdAt, '2016-05-03T11:00:28-04:00');
expect(photo.updatedAt, '2016-07-10T11:00:01-05:00');
expect(photo.width, 2448);
expect(photo.height, 3264);
expect(photo.color, '#6E633A');
expect(photo.downloads, 1345);
expect(photo.likedByUser, false);
expect(photo.exif!.make, 'Canon');
expect(photo.exif!.iso, 100);
expect(photo.location!.city, 'Montreal');
expect(photo.location!.country, 'Canada');
expect(photo.location!.position!.latitude, 45.4732984);
expect(photo.location!.position!.longitude, -73.6384879);
});
test('User.fromJson', () {
const input = '''
{
"id": "pXhwzz1JtQU",
"updated_at": "2016-07-10T11:00:01-05:00",
"username": "jimmyexample",
"name": "James Example",
"first_name": "James",
"last_name": "Example",
"instagram_username": "instantgrammer",
"twitter_username": "jimmy",
"portfolio_url": null,
"bio": "The user's bio",
"location": "Montreal, Qc",
"total_likes": 20,
"total_photos": 10,
"total_collections": 5,
"followed_by_user": false,
"followers_count": 300,
"following_count": 25,
"downloads": 225974,
"profile_image": {
"small": "https://images.unsplash.com/face-springmorning.jpg?q=80&fm=jpg&crop=faces&fit=crop&h=32&w=32",
"medium": "https://images.unsplash.com/face-springmorning.jpg?q=80&fm=jpg&crop=faces&fit=crop&h=64&w=64",
"large": "https://images.unsplash.com/face-springmorning.jpg?q=80&fm=jpg&crop=faces&fit=crop&h=128&w=128"
},
"badge": {
"title": "Book contributor",
"primary": true,
"slug": "book-contributor",
"link": "https://book.unsplash.com"
},
"links": {
"self": "https://api.unsplash.com/users/jimmyexample",
"html": "https://unsplash.com/jimmyexample",
"photos": "https://api.unsplash.com/users/jimmyexample/photos",
"likes": "https://api.unsplash.com/users/jimmyexample/likes",
"portfolio": "https://api.unsplash.com/users/jimmyexample/portfolio"
}
}
''';
final user = User.fromJson(input)!;
expect(user.id, 'pXhwzz1JtQU');
});
test('SearchPhotosResponse.fromJson', () {
const input = '''
{
"total": 133,
"total_pages": 7,
"results": [
{
"id": "eOLpJytrbsQ",
"created_at": "2014-11-18T14:35:36-05:00",
"width": 4000,
"height": 3000,
"color": "#A7A2A1",
"likes": 286,
"liked_by_user": false,
"description": "A man drinking a coffee.",
"user": {
"id": "Ul0QVz12Goo",
"username": "ugmonk",
"name": "Jeff Sheldon",
"first_name": "Jeff",
"last_name": "Sheldon",
"instagram_username": "instantgrammer",
"twitter_username": "ugmonk",
"portfolio_url": "http://ugmonk.com/",
"profile_image": {
"small": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=32&w=32&s=7cfe3b93750cb0c93e2f7caec08b5a41",
"medium": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=64&w=64&s=5a9dc749c43ce5bd60870b129a40902f",
"large": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=128&w=128&s=32085a077889586df88bfbe406692202"
},
"links": {
"self": "https://api.unsplash.com/users/ugmonk",
"html": "http://unsplash.com/@ugmonk",
"photos": "https://api.unsplash.com/users/ugmonk/photos",
"likes": "https://api.unsplash.com/users/ugmonk/likes"
}
},
"current_user_collections": [],
"urls": {
"raw": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f",
"full": "https://hd.unsplash.com/photo-1416339306562-f3d12fefd36f",
"regular": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&s=92f3e02f63678acc8416d044e189f515",
"small": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&s=263af33585f9d32af39d165b000845eb",
"thumb": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&s=8aae34cf35df31a592f0bef16e6342ef"
},
"links": {
"self": "https://api.unsplash.com/photos/eOLpJytrbsQ",
"html": "http://unsplash.com/photos/eOLpJytrbsQ",
"download": "http://unsplash.com/photos/eOLpJytrbsQ/download"
}
}
]
}
''';
final response = SearchPhotosResponse.fromJson(input)!;
expect(response.total, 133);
expect(response.totalPages, 7);
expect(response.results[0].id, 'eOLpJytrbsQ');
expect(response.results[0].user!.id, 'Ul0QVz12Goo');
});
group('Unsplash API client', () {
test('handles success', () async {
const searchPhotosResponseBody = '''
{
"total": 133,
"total_pages": 7,
"results": [
{
"id": "eOLpJytrbsQ",
"created_at": "2014-11-18T14:35:36-05:00",
"width": 4000,
"height": 3000,
"color": "#A7A2A1",
"likes": 286,
"liked_by_user": false,
"description": "A man drinking a coffee.",
"user": {
"id": "Ul0QVz12Goo",
"username": "ugmonk",
"name": "Jeff Sheldon",
"first_name": "Jeff",
"last_name": "Sheldon",
"instagram_username": "instantgrammer",
"twitter_username": "ugmonk",
"portfolio_url": "http://ugmonk.com/",
"profile_image": {
"small": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=32&w=32&s=7cfe3b93750cb0c93e2f7caec08b5a41",
"medium": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=64&w=64&s=5a9dc749c43ce5bd60870b129a40902f",
"large": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=128&w=128&s=32085a077889586df88bfbe406692202"
},
"links": {
"self": "https://api.unsplash.com/users/ugmonk",
"html": "http://unsplash.com/@ugmonk",
"photos": "https://api.unsplash.com/users/ugmonk/photos",
"likes": "https://api.unsplash.com/users/ugmonk/likes"
}
},
"current_user_collections": [],
"urls": {
"raw": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f",
"full": "https://hd.unsplash.com/photo-1416339306562-f3d12fefd36f",
"regular": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&s=92f3e02f63678acc8416d044e189f515",
"small": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&s=263af33585f9d32af39d165b000845eb",
"thumb": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&s=8aae34cf35df31a592f0bef16e6342ef"
},
"links": {
"self": "https://api.unsplash.com/photos/eOLpJytrbsQ",
"html": "http://unsplash.com/photos/eOLpJytrbsQ",
"download": "http://unsplash.com/photos/eOLpJytrbsQ/download"
}
}
]
}
''';
final httpClient = MockClient((req) async {
return Response(
searchPhotosResponseBody,
200,
request: req,
headers: {'content-type': 'application/json'},
);
});
final unsplashClient = Unsplash(
accessKey: 'not-an-access-key',
httpClient: httpClient,
);
final response = (await unsplashClient.searchPhotos(query: 'red'))!;
expect(response.total, 133);
expect(response.totalPages, 7);
expect(response.results[0].id, 'eOLpJytrbsQ');
expect(response.results[0].user!.id, 'Ul0QVz12Goo');
});
test('handles failure', () async {
const apiError =
'{"errors":["OAuth error: The access token is invalid"]}';
final httpClient = MockClient((req) async {
return Response(
apiError,
401,
request: req,
headers: {'content-type': 'application/json'},
);
});
final unsplashClient = Unsplash(
accessKey: 'not-an-access-key',
httpClient: httpClient,
);
try {
await unsplashClient.searchPhotos(query: 'red');
fail('UnsplashException should have been thrown');
} on UnsplashException catch (e) {
expect(
e.toString(),
'UnsplashException: OAuth error: The access token is invalid',
);
}
});
test('handles broken JSON', () async {
const apiError = '{"tot'; // truncated JSON.
final httpClient = MockClient((req) async {
return Response(
apiError,
401,
request: req,
headers: {'content-type': 'application/json'},
);
});
final unsplashClient = Unsplash(
accessKey: 'not-an-access-key',
httpClient: httpClient,
);
try {
await unsplashClient.searchPhotos(query: 'red');
fail('UnsplashException should have been thrown');
} on UnsplashException catch (e) {
expect(e.toString(), 'UnsplashException: Invalid JSON received');
}
});
});
test('handles utf8 content in JSON', () async {
const searchPhotosResponseBody = '''
{
"total": 22395,
"total_pages": 2240,
"results": [
{
"id": "E4u_Zo9PET8",
"created_at": "2019-12-29T13:45:28-05:00",
"updated_at": "2020-11-05T17:12:18-05:00",
"promoted_at": null,
"width": 2598,
"height": 4618,
"color": "#A53E40",
"blur_hash": "LlO{8lL#XSbu*Jtla0jZOrb^ozjF",
"description": null,
"alt_description": "red apparel",
"urls": {
"raw": "https://images.unsplash.com/photo-1577645113639-32537a4a938b?ixlib=rb-1.2.1\u0026ixid=eyJhcHBfaWQiOjM5NTU5fQ",
"full": "https://images.unsplash.com/photo-1577645113639-32537a4a938b?ixlib=rb-1.2.1\u0026q=85\u0026fm=jpg\u0026crop=entropy\u0026cs=srgb\u0026ixid=eyJhcHBfaWQiOjM5NTU5fQ",
"regular": "https://images.unsplash.com/photo-1577645113639-32537a4a938b?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=entropy\u0026cs=tinysrgb\u0026w=1080\u0026fit=max\u0026ixid=eyJhcHBfaWQiOjM5NTU5fQ",
"small": "https://images.unsplash.com/photo-1577645113639-32537a4a938b?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=entropy\u0026cs=tinysrgb\u0026w=400\u0026fit=max\u0026ixid=eyJhcHBfaWQiOjM5NTU5fQ",
"thumb": "https://images.unsplash.com/photo-1577645113639-32537a4a938b?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=entropy\u0026cs=tinysrgb\u0026w=200\u0026fit=max\u0026ixid=eyJhcHBfaWQiOjM5NTU5fQ"
},
"links": {
"self": "https://api.unsplash.com/photos/E4u_Zo9PET8",
"html": "https://unsplash.com/photos/E4u_Zo9PET8",
"download": "https://unsplash.com/photos/E4u_Zo9PET8/download",
"download_location": "https://api.unsplash.com/photos/E4u_Zo9PET8/download"
},
"categories": [],
"likes": 132,
"liked_by_user": false,
"current_user_collections": [],
"sponsorship": null,
"user": {
"id": "_2nQcPrbyuE",
"updated_at": "2020-11-06T01:37:51-05:00",
"username": "svalenas",
"name": "Sergiu Vălenaș",
"first_name": "Sergiu",
"last_name": "Vălenaș",
"twitter_username": null,
"portfolio_url": null,
"bio": "Gifted psychologist and enthusiast photographer",
"location": "Cluj-Napoca, Romania",
"links": {
"self": "https://api.unsplash.com/users/svalenas",
"html": "https://unsplash.com/@svalenas",
"photos": "https://api.unsplash.com/users/svalenas/photos",
"likes": "https://api.unsplash.com/users/svalenas/likes",
"portfolio": "https://api.unsplash.com/users/svalenas/portfolio",
"following": "https://api.unsplash.com/users/svalenas/following",
"followers": "https://api.unsplash.com/users/svalenas/followers"
},
"profile_image": {
"small": "https://images.unsplash.com/profile-1597067601066-d43176d68553image?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=faces\u0026cs=tinysrgb\u0026fit=crop\u0026h=32\u0026w=32",
"medium": "https://images.unsplash.com/profile-1597067601066-d43176d68553image?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=faces\u0026cs=tinysrgb\u0026fit=crop\u0026h=64\u0026w=64",
"large": "https://images.unsplash.com/profile-1597067601066-d43176d68553image?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=faces\u0026cs=tinysrgb\u0026fit=crop\u0026h=128\u0026w=128"
},
"instagram_username": "svalenas",
"total_collections": 0,
"total_likes": 413,
"total_photos": 129,
"accepted_tos": true
},
"tags": [
{
"type": "landing_page",
"title": "red",
"source": {
"ancestry": {
"type": {
"slug": "wallpapers",
"pretty_slug": "HD Wallpapers"
},
"category": {
"slug": "colors",
"pretty_slug": "Color"
},
"subcategory": {
"slug": "red",
"pretty_slug": "Red"
}
},
"title": "HD Red Wallpapers",
"subtitle": "Download Free Red Wallpapers",
"description": "Choose from a curated selection of red wallpapers for your mobile and desktop screens. Always free on Unsplash.",
"meta_title": "Red Wallpapers: Free HD Download [500+ HQ] | Unsplash",
"meta_description": "Choose from hundreds of free red wallpapers. Download HD wallpapers for free on Unsplash.",
"cover_photo": {
"id": "HyBXy5PHQR8",
"created_at": "2018-02-17T13:44:58-05:00",
"updated_at": "2020-10-21T01:07:42-04:00",
"promoted_at": null,
"width": 3024,
"height": 4032,
"color": "#C51918",
"blur_hash": "L9Bmx_o1o1Jl|cwxWpWpN]\$5N]sU",
"description": null,
"alt_description": "red textile",
"urls": {
"raw": "https://images.unsplash.com/photo-1518893063132-36e46dbe2428?ixlib=rb-1.2.1",
"full": "https://images.unsplash.com/photo-1518893063132-36e46dbe2428?ixlib=rb-1.2.1\u0026q=85\u0026fm=jpg\u0026crop=entropy\u0026cs=srgb",
"regular": "https://images.unsplash.com/photo-1518893063132-36e46dbe2428?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=entropy\u0026cs=tinysrgb\u0026w=1080\u0026fit=max",
"small": "https://images.unsplash.com/photo-1518893063132-36e46dbe2428?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=entropy\u0026cs=tinysrgb\u0026w=400\u0026fit=max",
"thumb": "https://images.unsplash.com/photo-1518893063132-36e46dbe2428?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=entropy\u0026cs=tinysrgb\u0026w=200\u0026fit=max"
},
"links": {
"self": "https://api.unsplash.com/photos/HyBXy5PHQR8",
"html": "https://unsplash.com/photos/HyBXy5PHQR8",
"download": "https://unsplash.com/photos/HyBXy5PHQR8/download",
"download_location": "https://api.unsplash.com/photos/HyBXy5PHQR8/download"
},
"categories": [],
"likes": 1243,
"liked_by_user": false,
"current_user_collections": [],
"sponsorship": null,
"user": {
"id": "6nkkrwW9M-s",
"updated_at": "2020-10-22T20:44:54-04:00",
"username": "montylov",
"name": "MontyLov",
"first_name": "MontyLov",
"last_name": null,
"twitter_username": "MontyLov",
"portfolio_url": "http://montylov.com",
"bio": "Stay humble and innovate.",
"location": null,
"links": {
"self": "https://api.unsplash.com/users/montylov",
"html": "https://unsplash.com/@montylov",
"photos": "https://api.unsplash.com/users/montylov/photos",
"likes": "https://api.unsplash.com/users/montylov/likes",
"portfolio": "https://api.unsplash.com/users/montylov/portfolio",
"following": "https://api.unsplash.com/users/montylov/following",
"followers": "https://api.unsplash.com/users/montylov/followers"
},
"profile_image": {
"small": "https://images.unsplash.com/profile-1588478301600-b5e5203a574aimage?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=faces\u0026cs=tinysrgb\u0026fit=crop\u0026h=32\u0026w=32",
"medium": "https://images.unsplash.com/profile-1588478301600-b5e5203a574aimage?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=faces\u0026cs=tinysrgb\u0026fit=crop\u0026h=64\u0026w=64",
"large": "https://images.unsplash.com/profile-1588478301600-b5e5203a574aimage?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=faces\u0026cs=tinysrgb\u0026fit=crop\u0026h=128\u0026w=128"
},
"instagram_username": "montylov",
"total_collections": 1,
"total_likes": 0,
"total_photos": 79,
"accepted_tos": false
}
}
}
},
{
"type": "search",
"title": "rug"
},
{
"type": "search",
"title": "plant"
}
]
}
]
}
''';
final httpClient = MockClient((req) async {
return Response(
searchPhotosResponseBody,
200,
request: req,
headers: {'content-type': 'application/json; charset=utf-8'},
);
});
final unsplashClient = Unsplash(
accessKey: 'not-an-access-key',
httpClient: httpClient,
);
final response = (await unsplashClient.searchPhotos(query: 'red'))!;
expect(response.total, 22395);
expect(response.totalPages, 2240);
expect(response.results[0].id, 'E4u_Zo9PET8');
expect(response.results[0].user!.id, '_2nQcPrbyuE');
expect(response.results[0].user!.name, 'Sergiu Vălenaș');
});
}

View File

@@ -0,0 +1,134 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:typed_data';
import 'package:desktop_photo_search/src/model/photo_search_model.dart';
import 'package:desktop_photo_search/src/unsplash/photo.dart';
import 'package:desktop_photo_search/src/unsplash/search_photos_response.dart';
import 'package:desktop_photo_search/src/unsplash/unsplash.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
class FakeUnsplash implements Unsplash {
static const searchPhotosResponse = '''
{
"total": 133,
"total_pages": 7,
"results": [
{
"id": "eOLpJytrbsQ",
"created_at": "2014-11-18T14:35:36-05:00",
"width": 4000,
"height": 3000,
"color": "#A7A2A1",
"likes": 286,
"liked_by_user": false,
"description": "A man drinking a coffee.",
"user": {
"id": "Ul0QVz12Goo",
"username": "ugmonk",
"name": "Jeff Sheldon",
"first_name": "Jeff",
"last_name": "Sheldon",
"instagram_username": "instantgrammer",
"twitter_username": "ugmonk",
"portfolio_url": "http://ugmonk.com/",
"profile_image": {
"small": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=32&w=32&s=7cfe3b93750cb0c93e2f7caec08b5a41",
"medium": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=64&w=64&s=5a9dc749c43ce5bd60870b129a40902f",
"large": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=128&w=128&s=32085a077889586df88bfbe406692202"
},
"links": {
"self": "https://api.unsplash.com/users/ugmonk",
"html": "http://unsplash.com/@ugmonk",
"photos": "https://api.unsplash.com/users/ugmonk/photos",
"likes": "https://api.unsplash.com/users/ugmonk/likes"
}
},
"current_user_collections": [],
"urls": {
"raw": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f",
"full": "https://hd.unsplash.com/photo-1416339306562-f3d12fefd36f",
"regular": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&s=92f3e02f63678acc8416d044e189f515",
"small": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&s=263af33585f9d32af39d165b000845eb",
"thumb": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&s=8aae34cf35df31a592f0bef16e6342ef"
},
"links": {
"self": "https://api.unsplash.com/photos/eOLpJytrbsQ",
"html": "http://unsplash.com/photos/eOLpJytrbsQ",
"download": "http://unsplash.com/photos/eOLpJytrbsQ/download"
}
}
]
}
''';
@override
Future<SearchPhotosResponse?> searchPhotos({
String? query,
num page = 1,
num perPage = 10,
List<num> collections = const [],
SearchPhotosOrientation? orientation,
}) async {
return SearchPhotosResponse.fromJson(searchPhotosResponse);
}
@override
Future<Uint8List> download(Photo photo) {
throw UnimplementedError();
}
}
const fabKey = Key('fab');
class PhotoSearchModelTester extends StatelessWidget {
const PhotoSearchModelTester({required this.query, super.key});
final String query;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: TextButton(
key: fabKey,
onPressed: () async {
await Provider.of<PhotoSearchModel>(
context,
listen: false,
).addSearch(query);
},
child: const Text('Search for a Photo'),
),
);
}
}
void main() {
group('search_list', () {
testWidgets('starts empty', (tester) async {
final unsplashSearches = PhotoSearchModel(FakeUnsplash());
final testWidget = ChangeNotifierProvider<PhotoSearchModel>(
create: (context) => unsplashSearches,
child: const PhotoSearchModelTester(query: 'clouds'),
);
await tester.pumpWidget(testWidget);
expect(unsplashSearches.entries.length, 0);
});
testWidgets('addSearch adds searches', (tester) async {
final unsplashSearches = PhotoSearchModel(FakeUnsplash());
final testWidget = ChangeNotifierProvider<PhotoSearchModel>(
create: (context) => unsplashSearches,
child: const PhotoSearchModelTester(query: 'clouds'),
);
await tester.pumpWidget(testWidget);
await tester.tap(find.byKey(fabKey));
await tester.tap(find.byKey(fabKey));
await tester.pumpAndSettle();
expect(unsplashSearches.entries.length, 2);
});
});
}

View File

@@ -0,0 +1,55 @@
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:async/async.dart' show StreamGroup;
import 'package:grinder/grinder.dart';
void main(List<String> args) => grind(args);
@DefaultTask()
@Depends(pubGet, generateJsonBindings, analyzeSource, test)
void build() {}
@Task()
Future<void> pubGet() async =>
_logProcessOutput(Process.start('flutter', ['pub', 'get']));
@Task()
Future<void> generateJsonBindings() async => _logProcessOutput(
Process.start('flutter', [
'pub',
'run',
'build_runner',
'build',
'--delete-conflicting-outputs',
]),
);
@Task()
Future<void> watch() async => _logProcessOutput(
Process.start('dart', ['run', 'build_runner', 'watch']),
);
@Task()
Future<void> analyzeSource() async =>
_logProcessOutput(Process.start('flutter', ['analyze']));
@Task()
Future<void> test() async =>
_logProcessOutput(Process.start('flutter', ['test']));
@Task()
Future<void> clean() => _logProcessOutput(Process.start('flutter', ['clean']));
Future<void> _logProcessOutput(Future<Process> proc) async {
final process = await proc;
final output = StreamGroup.merge([process.stdout, process.stderr]);
await for (final message in output) {
log(utf8.decode(message));
}
}

View File

@@ -0,0 +1,17 @@
flutter/ephemeral/
# Visual Studio user-specific files.
*.suo
*.user
*.userosscache
*.sln.docstates
# Visual Studio build-related files.
x64/
x86/
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/

View File

@@ -0,0 +1,108 @@
# Project-level configuration.
cmake_minimum_required(VERSION 3.14)
project(desktop_photo_search LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "desktop_photo_search")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
cmake_policy(VERSION 3.14...3.25)
# Define build configuration option.
get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(IS_MULTICONFIG)
set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
CACHE STRING "" FORCE)
else()
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
endif()
# Define settings for the Profile build mode.
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
# Use Unicode for all projects.
add_definitions(-DUNICODE -D_UNICODE)
# Compilation settings that should be applied to most targets.
#
# Be cautious about adding new options here, as plugins use this function by
# default. In most cases, you should add new options to specific targets instead
# of modifying this function.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_17)
target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
target_compile_options(${TARGET} PRIVATE /EHsc)
target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
endfunction()
# Flutter library and tool build rules.
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
add_subdirectory(${FLUTTER_MANAGED_DIR})
# Application build; see runner/CMakeLists.txt.
add_subdirectory("runner")
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# Support files are copied into place next to the executable, so that it can
# run in place. This is done instead of making a separate bundle (as on Linux)
# so that building and running from within Visual Studio will work.
set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
# Make the "install" step default, as it's required to run.
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
if(PLUGIN_BUNDLED_LIBRARIES)
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()
# Copy the native assets provided by the build.dart from all packages.
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/")
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
CONFIGURATIONS Profile;Release
COMPONENT Runtime)

View File

@@ -0,0 +1,109 @@
# This file controls Flutter-level build steps. It should not be edited.
cmake_minimum_required(VERSION 3.14)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
# Set fallback configurations for older versions of the flutter tool.
if (NOT DEFINED FLUTTER_TARGET_PLATFORM)
set(FLUTTER_TARGET_PLATFORM "windows-x64")
endif()
# === Flutter Library ===
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"flutter_export.h"
"flutter_windows.h"
"flutter_messenger.h"
"flutter_plugin_registrar.h"
"flutter_texture_registrar.h"
)
list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
add_dependencies(flutter flutter_assemble)
# === Wrapper ===
list(APPEND CPP_WRAPPER_SOURCES_CORE
"core_implementations.cc"
"standard_codec.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
"plugin_registrar.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_APP
"flutter_engine.cc"
"flutter_view_controller.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
# Wrapper sources needed for a plugin.
add_library(flutter_wrapper_plugin STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
)
apply_standard_settings(flutter_wrapper_plugin)
set_target_properties(flutter_wrapper_plugin PROPERTIES
POSITION_INDEPENDENT_CODE ON)
set_target_properties(flutter_wrapper_plugin PROPERTIES
CXX_VISIBILITY_PRESET hidden)
target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
target_include_directories(flutter_wrapper_plugin PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_plugin flutter_assemble)
# Wrapper sources needed for the runner.
add_library(flutter_wrapper_app STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_APP}
)
apply_standard_settings(flutter_wrapper_app)
target_link_libraries(flutter_wrapper_app PUBLIC flutter)
target_include_directories(flutter_wrapper_app PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_app flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
${PHONY_OUTPUT}
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
${FLUTTER_TARGET_PLATFORM} $<CONFIG>
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
)

View File

@@ -0,0 +1,20 @@
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
#include <file_selector_windows/file_selector_windows.h>
#include <menubar/menubar_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows"));
MenubarPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("MenubarPlugin"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
}

View File

@@ -0,0 +1,15 @@
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter/plugin_registry.h>
// Registers Flutter plugins.
void RegisterPlugins(flutter::PluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_

View File

@@ -0,0 +1,26 @@
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
file_selector_windows
menubar
url_launcher_windows
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)

View File

@@ -0,0 +1,40 @@
cmake_minimum_required(VERSION 3.14)
project(runner LANGUAGES CXX)
# Define the application target. To change its name, change BINARY_NAME in the
# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
# work.
#
# Any new source files that you add to the application should be added here.
add_executable(${BINARY_NAME} WIN32
"flutter_window.cpp"
"main.cpp"
"utils.cpp"
"win32_window.cpp"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
"Runner.rc"
"runner.exe.manifest"
)
# Apply the standard set of build settings. This can be removed for applications
# that need different build settings.
apply_standard_settings(${BINARY_NAME})
# Add preprocessor definitions for the build version.
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}")
# Disable Windows macros that collide with C++ standard library functions.
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
# Add dependency libraries and include directories. Add any application-specific
# dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib")
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
# Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble)

View File

@@ -0,0 +1,121 @@
// Microsoft Visual C++ generated resource script.
//
#pragma code_page(65001)
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_APP_ICON ICON "resources\\app_icon.ico"
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
#else
#define VERSION_AS_NUMBER 1,0,0,0
#endif
#if defined(FLUTTER_VERSION)
#define VERSION_AS_STRING FLUTTER_VERSION
#else
#define VERSION_AS_STRING "1.0.0"
#endif
VS_VERSION_INFO VERSIONINFO
FILEVERSION VERSION_AS_NUMBER
PRODUCTVERSION VERSION_AS_NUMBER
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS__WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904e4"
BEGIN
VALUE "CompanyName", "com.example" "\0"
VALUE "FileDescription", "desktop_photo_search" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "desktop_photo_search" "\0"
VALUE "LegalCopyright", "Copyright (C) 2023 com.example. All rights reserved." "\0"
VALUE "OriginalFilename", "desktop_photo_search.exe" "\0"
VALUE "ProductName", "desktop_photo_search" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1252
END
END
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

View File

@@ -0,0 +1,71 @@
#include "flutter_window.h"
#include <optional>
#include "flutter/generated_plugin_registrant.h"
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
: project_(project) {}
FlutterWindow::~FlutterWindow() {}
bool FlutterWindow::OnCreate() {
if (!Win32Window::OnCreate()) {
return false;
}
RECT frame = GetClientArea();
// The size here must match the window dimensions to avoid unnecessary surface
// creation / destruction in the startup path.
flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
frame.right - frame.left, frame.bottom - frame.top, project_);
// Ensure that basic setup of the controller was successful.
if (!flutter_controller_->engine() || !flutter_controller_->view()) {
return false;
}
RegisterPlugins(flutter_controller_->engine());
SetChildContent(flutter_controller_->view()->GetNativeWindow());
flutter_controller_->engine()->SetNextFrameCallback([&]() {
this->Show();
});
// Flutter can complete the first frame before the "show window" callback is
// registered. The following call ensures a frame is pending to ensure the
// window is shown. It is a no-op if the first frame hasn't completed yet.
flutter_controller_->ForceRedraw();
return true;
}
void FlutterWindow::OnDestroy() {
if (flutter_controller_) {
flutter_controller_ = nullptr;
}
Win32Window::OnDestroy();
}
LRESULT
FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
// Give Flutter, including plugins, an opportunity to handle window messages.
if (flutter_controller_) {
std::optional<LRESULT> result =
flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
lparam);
if (result) {
return *result;
}
}
switch (message) {
case WM_FONTCHANGE:
flutter_controller_->engine()->ReloadSystemFonts();
break;
}
return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
}

View File

@@ -0,0 +1,33 @@
#ifndef RUNNER_FLUTTER_WINDOW_H_
#define RUNNER_FLUTTER_WINDOW_H_
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <memory>
#include "win32_window.h"
// A window that does nothing but host a Flutter view.
class FlutterWindow : public Win32Window {
public:
// Creates a new FlutterWindow hosting a Flutter view running |project|.
explicit FlutterWindow(const flutter::DartProject& project);
virtual ~FlutterWindow();
protected:
// Win32Window:
bool OnCreate() override;
void OnDestroy() override;
LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
LPARAM const lparam) noexcept override;
private:
// The project to run.
flutter::DartProject project_;
// The Flutter instance hosted by this window.
std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
};
#endif // RUNNER_FLUTTER_WINDOW_H_

View File

@@ -0,0 +1,43 @@
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <windows.h>
#include "flutter_window.h"
#include "utils.h"
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
_In_ wchar_t *command_line, _In_ int show_command) {
// Attach to console when present (e.g., 'flutter run') or create a
// new console when running with a debugger.
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
CreateAndAttachConsole();
}
// Initialize COM, so that it is available for use in the library and/or
// plugins.
::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
flutter::DartProject project(L"data");
std::vector<std::string> command_line_arguments =
GetCommandLineArguments();
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
FlutterWindow window(project);
Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720);
if (!window.Create(L"desktop_photo_search", origin, size)) {
return EXIT_FAILURE;
}
window.SetQuitOnClose(true);
::MSG msg;
while (::GetMessage(&msg, nullptr, 0, 0)) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
::CoUninitialize();
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,16 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by Runner.rc
//
#define IDI_APP_ICON 101
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 and Windows 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
</application>
</compatibility>
</assembly>

View File

@@ -0,0 +1,65 @@
#include "utils.h"
#include <flutter_windows.h>
#include <io.h>
#include <stdio.h>
#include <windows.h>
#include <iostream>
void CreateAndAttachConsole() {
if (::AllocConsole()) {
FILE *unused;
if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
_dup2(_fileno(stdout), 1);
}
if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
_dup2(_fileno(stdout), 2);
}
std::ios::sync_with_stdio();
FlutterDesktopResyncOutputStreams();
}
}
std::vector<std::string> GetCommandLineArguments() {
// Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
int argc;
wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
if (argv == nullptr) {
return std::vector<std::string>();
}
std::vector<std::string> command_line_arguments;
// Skip the first argument as it's the binary name.
for (int i = 1; i < argc; i++) {
command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
}
::LocalFree(argv);
return command_line_arguments;
}
std::string Utf8FromUtf16(const wchar_t* utf16_string) {
if (utf16_string == nullptr) {
return std::string();
}
int target_length = ::WideCharToMultiByte(
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
-1, nullptr, 0, nullptr, nullptr)
-1; // remove the trailing null character
int input_length = (int)wcslen(utf16_string);
std::string utf8_string;
if (target_length <= 0 || target_length > utf8_string.max_size()) {
return utf8_string;
}
utf8_string.resize(target_length);
int converted_length = ::WideCharToMultiByte(
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
input_length, utf8_string.data(), target_length, nullptr, nullptr);
if (converted_length == 0) {
return std::string();
}
return utf8_string;
}

View File

@@ -0,0 +1,19 @@
#ifndef RUNNER_UTILS_H_
#define RUNNER_UTILS_H_
#include <string>
#include <vector>
// Creates a console for the process, and redirects stdout and stderr to
// it for both the runner and the Flutter library.
void CreateAndAttachConsole();
// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
// encoded in UTF-8. Returns an empty std::string on failure.
std::string Utf8FromUtf16(const wchar_t* utf16_string);
// Gets the command line arguments passed in as a std::vector<std::string>,
// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
std::vector<std::string> GetCommandLineArguments();
#endif // RUNNER_UTILS_H_

View File

@@ -0,0 +1,288 @@
#include "win32_window.h"
#include <dwmapi.h>
#include <flutter_windows.h>
#include "resource.h"
namespace {
/// Window attribute that enables dark mode window decorations.
///
/// Redefined in case the developer's machine has a Windows SDK older than
/// version 10.0.22000.0.
/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
#endif
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
/// Registry key for app theme preference.
///
/// A value of 0 indicates apps should use dark mode. A non-zero or missing
/// value indicates apps should use light mode.
constexpr const wchar_t kGetPreferredBrightnessRegKey[] =
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme";
// The number of Win32Window objects that currently exist.
static int g_active_window_count = 0;
using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
// Scale helper to convert logical scaler values to physical using passed in
// scale factor
int Scale(int source, double scale_factor) {
return static_cast<int>(source * scale_factor);
}
// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
// This API is only needed for PerMonitor V1 awareness mode.
void EnableFullDpiSupportIfAvailable(HWND hwnd) {
HMODULE user32_module = LoadLibraryA("User32.dll");
if (!user32_module) {
return;
}
auto enable_non_client_dpi_scaling =
reinterpret_cast<EnableNonClientDpiScaling*>(
GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
if (enable_non_client_dpi_scaling != nullptr) {
enable_non_client_dpi_scaling(hwnd);
}
FreeLibrary(user32_module);
}
} // namespace
// Manages the Win32Window's window class registration.
class WindowClassRegistrar {
public:
~WindowClassRegistrar() = default;
// Returns the singleton registrar instance.
static WindowClassRegistrar* GetInstance() {
if (!instance_) {
instance_ = new WindowClassRegistrar();
}
return instance_;
}
// Returns the name of the window class, registering the class if it hasn't
// previously been registered.
const wchar_t* GetWindowClass();
// Unregisters the window class. Should only be called if there are no
// instances of the window.
void UnregisterWindowClass();
private:
WindowClassRegistrar() = default;
static WindowClassRegistrar* instance_;
bool class_registered_ = false;
};
WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
const wchar_t* WindowClassRegistrar::GetWindowClass() {
if (!class_registered_) {
WNDCLASS window_class{};
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
window_class.lpszClassName = kWindowClassName;
window_class.style = CS_HREDRAW | CS_VREDRAW;
window_class.cbClsExtra = 0;
window_class.cbWndExtra = 0;
window_class.hInstance = GetModuleHandle(nullptr);
window_class.hIcon =
LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
window_class.hbrBackground = 0;
window_class.lpszMenuName = nullptr;
window_class.lpfnWndProc = Win32Window::WndProc;
RegisterClass(&window_class);
class_registered_ = true;
}
return kWindowClassName;
}
void WindowClassRegistrar::UnregisterWindowClass() {
UnregisterClass(kWindowClassName, nullptr);
class_registered_ = false;
}
Win32Window::Win32Window() {
++g_active_window_count;
}
Win32Window::~Win32Window() {
--g_active_window_count;
Destroy();
}
bool Win32Window::Create(const std::wstring& title,
const Point& origin,
const Size& size) {
Destroy();
const wchar_t* window_class =
WindowClassRegistrar::GetInstance()->GetWindowClass();
const POINT target_point = {static_cast<LONG>(origin.x),
static_cast<LONG>(origin.y)};
HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
double scale_factor = dpi / 96.0;
HWND window = CreateWindow(
window_class, title.c_str(), WS_OVERLAPPEDWINDOW,
Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
Scale(size.width, scale_factor), Scale(size.height, scale_factor),
nullptr, nullptr, GetModuleHandle(nullptr), this);
if (!window) {
return false;
}
UpdateTheme(window);
return OnCreate();
}
bool Win32Window::Show() {
return ShowWindow(window_handle_, SW_SHOWNORMAL);
}
// static
LRESULT CALLBACK Win32Window::WndProc(HWND const window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
if (message == WM_NCCREATE) {
auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
SetWindowLongPtr(window, GWLP_USERDATA,
reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
EnableFullDpiSupportIfAvailable(window);
that->window_handle_ = window;
} else if (Win32Window* that = GetThisFromHandle(window)) {
return that->MessageHandler(window, message, wparam, lparam);
}
return DefWindowProc(window, message, wparam, lparam);
}
LRESULT
Win32Window::MessageHandler(HWND hwnd,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
switch (message) {
case WM_DESTROY:
window_handle_ = nullptr;
Destroy();
if (quit_on_close_) {
PostQuitMessage(0);
}
return 0;
case WM_DPICHANGED: {
auto newRectSize = reinterpret_cast<RECT*>(lparam);
LONG newWidth = newRectSize->right - newRectSize->left;
LONG newHeight = newRectSize->bottom - newRectSize->top;
SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
return 0;
}
case WM_SIZE: {
RECT rect = GetClientArea();
if (child_content_ != nullptr) {
// Size and position the child window.
MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
rect.bottom - rect.top, TRUE);
}
return 0;
}
case WM_ACTIVATE:
if (child_content_ != nullptr) {
SetFocus(child_content_);
}
return 0;
case WM_DWMCOLORIZATIONCOLORCHANGED:
UpdateTheme(hwnd);
return 0;
}
return DefWindowProc(window_handle_, message, wparam, lparam);
}
void Win32Window::Destroy() {
OnDestroy();
if (window_handle_) {
DestroyWindow(window_handle_);
window_handle_ = nullptr;
}
if (g_active_window_count == 0) {
WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
}
}
Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
return reinterpret_cast<Win32Window*>(
GetWindowLongPtr(window, GWLP_USERDATA));
}
void Win32Window::SetChildContent(HWND content) {
child_content_ = content;
SetParent(content, window_handle_);
RECT frame = GetClientArea();
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
frame.bottom - frame.top, true);
SetFocus(child_content_);
}
RECT Win32Window::GetClientArea() {
RECT frame;
GetClientRect(window_handle_, &frame);
return frame;
}
HWND Win32Window::GetHandle() {
return window_handle_;
}
void Win32Window::SetQuitOnClose(bool quit_on_close) {
quit_on_close_ = quit_on_close;
}
bool Win32Window::OnCreate() {
// No-op; provided for subclasses.
return true;
}
void Win32Window::OnDestroy() {
// No-op; provided for subclasses.
}
void Win32Window::UpdateTheme(HWND const window) {
DWORD light_mode;
DWORD light_mode_size = sizeof(light_mode);
LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey,
kGetPreferredBrightnessRegValue,
RRF_RT_REG_DWORD, nullptr, &light_mode,
&light_mode_size);
if (result == ERROR_SUCCESS) {
BOOL enable_dark_mode = light_mode == 0;
DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE,
&enable_dark_mode, sizeof(enable_dark_mode));
}
}

View File

@@ -0,0 +1,102 @@
#ifndef RUNNER_WIN32_WINDOW_H_
#define RUNNER_WIN32_WINDOW_H_
#include <windows.h>
#include <functional>
#include <memory>
#include <string>
// A class abstraction for a high DPI-aware Win32 Window. Intended to be
// inherited from by classes that wish to specialize with custom
// rendering and input handling
class Win32Window {
public:
struct Point {
unsigned int x;
unsigned int y;
Point(unsigned int x, unsigned int y) : x(x), y(y) {}
};
struct Size {
unsigned int width;
unsigned int height;
Size(unsigned int width, unsigned int height)
: width(width), height(height) {}
};
Win32Window();
virtual ~Win32Window();
// Creates a win32 window with |title| that is positioned and sized using
// |origin| and |size|. New windows are created on the default monitor. Window
// sizes are specified to the OS in physical pixels, hence to ensure a
// consistent size this function will scale the inputted width and height as
// as appropriate for the default monitor. The window is invisible until
// |Show| is called. Returns true if the window was created successfully.
bool Create(const std::wstring& title, const Point& origin, const Size& size);
// Show the current window. Returns true if the window was successfully shown.
bool Show();
// Release OS resources associated with window.
void Destroy();
// Inserts |content| into the window tree.
void SetChildContent(HWND content);
// Returns the backing Window handle to enable clients to set icon and other
// window properties. Returns nullptr if the window has been destroyed.
HWND GetHandle();
// If true, closing this window will quit the application.
void SetQuitOnClose(bool quit_on_close);
// Return a RECT representing the bounds of the current client area.
RECT GetClientArea();
protected:
// Processes and route salient window messages for mouse handling,
// size change and DPI. Delegates handling of these to member overloads that
// inheriting classes can handle.
virtual LRESULT MessageHandler(HWND window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept;
// Called when CreateAndShow is called, allowing subclass window-related
// setup. Subclasses should return false if setup fails.
virtual bool OnCreate();
// Called when Destroy is called.
virtual void OnDestroy();
private:
friend class WindowClassRegistrar;
// OS callback called by message pump. Handles the WM_NCCREATE message which
// is passed when the non-client area is being created and enables automatic
// non-client DPI scaling so that the non-client area automatically
// responds to changes in DPI. All other messages are handled by
// MessageHandler.
static LRESULT CALLBACK WndProc(HWND const window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept;
// Retrieves a class instance pointer for |window|
static Win32Window* GetThisFromHandle(HWND const window) noexcept;
// Update the window frame's theme to match the system theme.
static void UpdateTheme(HWND const window);
bool quit_on_close_ = false;
// window handle for top level window.
HWND window_handle_ = nullptr;
// window handle for hosted content.
HWND child_content_ = nullptr;
};
#endif // RUNNER_WIN32_WINDOW_H_