Commit dbef14dc authored by George's avatar George 💬

add flutter wrappers

parent 6c221ee7
# 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: 7a4c33425ddd78c54aba07d86f3f9a4a0051769b
channel: stable
project_type: package
# flutter_ghost_content_api
# Flutter Ghost Content API
A Flutter plugin acting as a wrapper around the Ghost Content API
\ No newline at end of file
A flutter wrapper around the [Ghost Content API](https://docs.ghost.org/api/content/).
## Usage
Firstly, create a new custom app integration in your Ghost publication and make a note of the content API key.
Next, create a `GhostContentAPIProvider` at the root of your Flutter application:
```
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GhostContentAPIProvider(
client: GhostContentAPIClient(
version: 'v2',
url: 'https://your-subdomain.ghost.io',
contentAPIkey: 'your content api key',
),
child: MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
),
);
}
}
```
Finally, use a `FutureBuilder` anywhere you would like to access your Ghost content:
```
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Flutter Ghost"),
),
body: FutureBuilder(
future: GhostContentAPIProvider.of(context).client.getPosts(),
builder: (BuildContext context, AsyncSnapshot<PostsResponse> snapshot) {
// Build your UI
},
),
);
}
}
```
## Example
See my [Flutter Ghost App](https://gitlab.com/learn-software/learn-software-app) for [Learn Software](https://learnsoftware.app).
## Tests
Run:
- `flutter packages get`
- `flutter test`
This diff is collapsed.
This diff is collapsed.
import 'dart:convert';
import 'package:flutter/widgets.dart';
import 'package:http/http.dart' show Client;
import '../api/api_models.dart';
const _supportedVersions = ['v2'];
enum AuthorInclude {
COUNT_POSTS,
}
enum PostPageInclude {
AUTHORS,
TAGS,
}
enum TagInclude {
COUNT_POSTS,
}
class GhostContentAPIClient {
Client client = Client();
final String url;
final String version;
final String contentAPIKey;
final String ghostPath;
GhostContentAPIClient({
Key key,
@required this.url,
@required this.version,
@required this.contentAPIKey,
this.ghostPath = 'ghost',
}) : assert(url != null, "URL must not be null"),
assert(url.startsWith("https://"), "URL must start with https://"),
assert(!url.endsWith("/"), "URL must not end with /"),
assert(!ghostPath.startsWith("/") && !ghostPath.endsWith("/"), "Ghost Path cannot start or end with /"),
// todo asset key regex
assert(version != null && _supportedVersions.contains(version));
Future<PostsResponse> getPosts({List<PostPageInclude> includes}) async {
String endpoint = '$url/$ghostPath/api/$version/content/posts?key=$contentAPIKey';
endpoint = _appendPostPageIncludes(endpoint, includes);
final response = await client.get(endpoint);
if (response != null && response.statusCode == 200) {
Map<String, dynamic> body = json.decode(response.body);
return PostsResponse.fromJson(body);
}
throw Exception('Failed to get posts');
}
Future<PostResponse> getPostById(String id, {List<PostPageInclude> includes}) async {
String endpoint = '$url/$ghostPath/api/$version/content/posts/$id/?key=$contentAPIKey';
endpoint = _appendPostPageIncludes(endpoint, includes);
final response = await client.get(endpoint);
if (response != null && response.statusCode == 200) {
Map<String, dynamic> body = json.decode(response.body);
return PostResponse.fromJson(body);
}
throw Exception('Failed to get post');
}
Future<PostResponse> getPostBySlug(String slug, {List<PostPageInclude> includes}) async {
String endpoint = '$url/$ghostPath/api/$version/content/posts/slug/$slug/?key=$contentAPIKey';
endpoint = _appendPostPageIncludes(endpoint, includes);
final response = await client.get(endpoint);
if (response != null && response.statusCode == 200) {
Map<String, dynamic> body = json.decode(response.body);
return PostResponse.fromJson(body);
}
throw Exception('Failed to get post');
}
Future<AuthorsResponse> getAuthors({List<AuthorInclude> includes}) async {
String endpoint = '$url/$ghostPath/api/$version/content/authors?key=$contentAPIKey';
endpoint = _appendAuthorIncludes(endpoint, includes);
final response = await client.get(endpoint);
if (response != null && response.statusCode == 200) {
Map<String, dynamic> body = json.decode(response.body);
return AuthorsResponse.fromJson(body);
}
throw Exception('Failed to get authors');
}
Future<AuthorResponse> getAuthorById(String id, {List<AuthorInclude> includes}) async {
String endpoint = '$url/$ghostPath/api/$version/content/authors/$id/?key=$contentAPIKey';
endpoint = _appendAuthorIncludes(endpoint, includes);
final response = await client.get(endpoint);
if (response != null && response.statusCode == 200) {
Map<String, dynamic> body = json.decode(response.body);
return AuthorResponse.fromJson(body);
}
throw Exception('Failed to get author');
}
Future<AuthorResponse> getAuthorBySlug(String slug, {List<AuthorInclude> includes}) async {
String endpoint = '$url/$ghostPath/api/$version/content/authors/slug/$slug/?key=$contentAPIKey';
endpoint = _appendAuthorIncludes(endpoint, includes);
final response = await client.get(endpoint);
if (response != null && response.statusCode == 200) {
Map<String, dynamic> body = json.decode(response.body);
return AuthorResponse.fromJson(body);
}
throw Exception('Failed to get author');
}
Future<TagsResponse> getTags({List<TagInclude> includes}) async {
String endpoint = '$url/$ghostPath/api/$version/content/tags?key=$contentAPIKey';
endpoint = _appendTagIncludes(endpoint, includes);
final response = await client.get(endpoint);
if (response != null && response.statusCode == 200) {
Map<String, dynamic> body = json.decode(response.body);
return TagsResponse.fromJson(body);
}
throw Exception('Failed to get tags');
}
Future<TagResponse> getTagById(String id, {List<TagInclude> includes}) async {
String endpoint = '$url/$ghostPath/api/$version/content/tags/$id/?key=$contentAPIKey';
endpoint = _appendTagIncludes(endpoint, includes);
final response = await client.get(endpoint);
if (response != null && response.statusCode == 200) {
Map<String, dynamic> body = json.decode(response.body);
return TagResponse.fromJson(body);
}
throw Exception('Failed to get tag');
}
Future<TagResponse> getTagBySlug(String slug, {List<TagInclude> includes}) async {
String endpoint = '$url/$ghostPath/api/$version/content/tags/slug/$slug/?key=$contentAPIKey';
endpoint = _appendTagIncludes(endpoint, includes);
final response = await client.get(endpoint);
if (response != null && response.statusCode == 200) {
Map<String, dynamic> body = json.decode(response.body);
return TagResponse.fromJson(body);
}
throw Exception('Failed to get tag');
}
Future<PagesResponse> getPages({List<PostPageInclude> includes}) async {
String endpoint = '$url/$ghostPath/api/$version/content/pages?key=$contentAPIKey';
endpoint = _appendPostPageIncludes(endpoint, includes);
final response = await client.get(endpoint);
if (response != null && response.statusCode == 200) {
Map<String, dynamic> body = json.decode(response.body);
return PagesResponse.fromJson(body);
}
throw Exception('Failed to get pages');
}
Future<PageResponse> getPageById(String id, {List<PostPageInclude> includes}) async {
String endpoint = '$url/$ghostPath/api/$version/content/pages/$id/?key=$contentAPIKey';
endpoint = _appendPostPageIncludes(endpoint, includes);
final response = await client.get(endpoint);
if (response != null && response.statusCode == 200) {
Map<String, dynamic> body = json.decode(response.body);
return PageResponse.fromJson(body);
}
throw Exception('Failed to get page');
}
Future<PageResponse> getPageBySlug(String slug, {List<PostPageInclude> includes}) async {
String endpoint = '$url/$ghostPath/api/$version/content/pages/slug/$slug/?key=$contentAPIKey';
endpoint = _appendPostPageIncludes(endpoint, includes);
final response = await client.get(endpoint);
if (response != null && response.statusCode == 200) {
Map<String, dynamic> body = json.decode(response.body);
return PageResponse.fromJson(body);
}
throw Exception('Failed to get page');
}
Future<SettingsResponse> getSettings() async {
String endpoint = '$url/$ghostPath/api/$version/content/settings?key=$contentAPIKey';
final response = await client.get(endpoint);
if (response != null && response.statusCode == 200) {
Map<String, dynamic> body = json.decode(response.body);
return SettingsResponse.fromJson(body);
}
throw Exception('Failed to get settings');
}
String _appendPostPageIncludes(String endpoint, List<PostPageInclude> includes) {
if (includes != null && includes.length > 0) {
endpoint += '&include=';
endpoint += includes.map((include) => include.toString().toLowerCase().replaceAll('postpageinclude.', '')).join(',');
}
return endpoint;
}
String _appendAuthorIncludes(String endpoint, List<AuthorInclude> includes) {
if (includes != null && includes.length > 0) {
// there's only one enum value right now
endpoint += '&include=count.posts';
}
return endpoint;
}
String _appendTagIncludes(String endpoint, List<TagInclude> includes) {
if (includes != null && includes.length > 0) {
// there's only one enum value right now
endpoint += '&include=count.posts';
}
return endpoint;
}
}
import 'package:flutter/widgets.dart';
import 'package:flutter_ghost_content_api/src/widgets/ghost_content_api_client.dart';
class GhostContentAPIProvider extends StatefulWidget {
final GhostContentAPIClient client;
final Widget child;
GhostContentAPIProvider({
@required this.client,
@required this.child,
});
static _GhostContentAPIProviderState of(BuildContext context) {
return (context.inheritFromWidgetOfExactType(_InheritedGhostContentAPIProvider) as _InheritedGhostContentAPIProvider).state;
}
@override
State createState() => new _GhostContentAPIProviderState();
}
class _GhostContentAPIProviderState extends State<GhostContentAPIProvider> {
GhostContentAPIClient client;
@override
void initState() {
this.client = widget.client;
super.initState();
}
@override
Widget build(BuildContext context) {
return new _InheritedGhostContentAPIProvider(
state: this,
child: widget.child
);
}
}
class _InheritedGhostContentAPIProvider extends InheritedWidget {
final _GhostContentAPIProviderState state;
_InheritedGhostContentAPIProvider({
Key key,
@required this.state,
@required Widget child
}) : super(key: key, child: child);
@override
bool updateShouldNotify(InheritedWidget oldWidget) {
return true;
}
}
name: flutter_ghost_content_api
description: Access your Ghost blog content in Flutter
description: A Flutter wrapper around the Ghost Content API
version: 0.0.1
author: George O'Toole <george@learnsoftware.app>
homepage: https://learnsoftware.app/flutter-ghost-content-api-plugin
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment