Skip to content
This repository has been archived by the owner on Sep 14, 2024. It is now read-only.

Commit

Permalink
Merge branch 'release/0.3.7'
Browse files Browse the repository at this point in the history
  • Loading branch information
Teifun2 committed Jan 16, 2021
2 parents 988fc5c + 3e36953 commit 542425b
Show file tree
Hide file tree
Showing 12 changed files with 200 additions and 11 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@

# Nextcloud Cookbook Mobile Client written in Flutter

This project aims to provide a mobile client for both Android and IOs for the nextcloud app cookbook (https://github.com/nextcloud/cookbook)
This project aims to provide a mobile client for both Android and IOs for the Nextcloud Cookbook App (https://github.com/nextcloud/cookbook)

It works best with an Nextcloud installation >= 17
It works best with an Nextcloud installation >= 19 and a Cookbook plugin version 0.7.9

## Screenshots

Expand Down
4 changes: 3 additions & 1 deletion assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@
"errors": {
"unknown": "Categories in unknown state",
"load_failed": "Category Load Failed: {error_msg}",
"load_no_response": "Could not retrieve the Categories from the server."
"load_no_response": "Could not retrieve the Categories from the server.",
"api_version_check_failed": "Failed to check the API version of the Server:\n {error_msg}",
"api_version_above_confirmed": "The Api Version of the Server was Updated. Some features might not work as expected. Please wait for an update!\n {version}"
}
},
"recipe_list": {
Expand Down
4 changes: 3 additions & 1 deletion lib/src/blocs/authentication/authentication_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class AuthenticationBloc
await userRepository.loadAppAuthentication();
bool validCredentials = await userRepository.checkAppAuthentication();
if (validCredentials) {
await userRepository.fetchApiVersion();
yield AuthenticationAuthenticated();
} else {
await userRepository.deleteAppAuthentication();
Expand All @@ -36,6 +37,7 @@ class AuthenticationBloc
if (event is LoggedIn) {
yield AuthenticationLoading();
await userRepository.persistAppAuthentication(event.appAuthentication);
await userRepository.fetchApiVersion();
yield AuthenticationAuthenticated();
}

Expand All @@ -45,4 +47,4 @@ class AuthenticationBloc
yield AuthenticationUnauthenticated();
}
}
}
}
8 changes: 7 additions & 1 deletion lib/src/screens/category_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:nextcloud_cookbook_flutter/src/blocs/categories/categories.dart'
import 'package:nextcloud_cookbook_flutter/src/models/category.dart';
import 'package:nextcloud_cookbook_flutter/src/screens/recipes_list_screen.dart';
import 'package:nextcloud_cookbook_flutter/src/screens/search_screen.dart';
import 'package:nextcloud_cookbook_flutter/src/widget/api_version_warning.dart';
import 'package:nextcloud_cookbook_flutter/src/widget/category_card.dart';

class CategoryScreen extends StatefulWidget {
Expand Down Expand Up @@ -66,7 +67,12 @@ class _CategoryScreenState extends State<CategoryScreen> {
return _buildCategoriesScreen(categoriesState.categories);
} else if (categoriesState is CategoriesLoadInProgress ||
categoriesState is CategoriesInitial) {
return Center(child: CircularProgressIndicator());
return Column(
children: [
ApiVersionWarning(),
Center(child: CircularProgressIndicator()),
],
);
} else if (categoriesState is CategoriesLoadFailure) {
return Text(translate('categories.errors.load_failed',
args: {'error_msg': categoriesState.errorMsg}));
Expand Down
2 changes: 1 addition & 1 deletion lib/src/screens/form/login_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ class _LoginFormState extends State<LoginForm> with WidgetsBindingObserver {
return translate('login.server_url.validator.empty');
}
var urlPattern =
r"^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$";
r"^(?:http(s)?:\/\/)?[\w.-]+(?:(?:\.[\w\.-]+)|(?:\:\d+))+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]*$";
bool _match =
new RegExp(urlPattern, caseSensitive: false)
.hasMatch(_punyEncodeUrl(value));
Expand Down
3 changes: 3 additions & 0 deletions lib/src/services/authentication_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ class AuthenticationProvider {
}) async {
if (serverUrl.substring(0, 4) != 'http') {
serverUrl = 'https://' + serverUrl;
if (serverUrl.endsWith("/")) {
serverUrl = serverUrl.substring(0, serverUrl.length - 1);
}
}
String urlInitialCall = serverUrl + '/ocs/v2.php/core/getapppassword';

Expand Down
13 changes: 10 additions & 3 deletions lib/src/services/category_recipes_short_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,23 @@ import 'package:flutter_translate/flutter_translate.dart';
import 'package:nextcloud_cookbook_flutter/src/models/app_authentication.dart';
import 'package:nextcloud_cookbook_flutter/src/models/recipe_short.dart';
import 'package:nextcloud_cookbook_flutter/src/services/user_repository.dart';
import 'package:nextcloud_cookbook_flutter/src/services/version_provider.dart';

class CategoryRecipesShortProvider {
Future<List<RecipeShort>> fetchCategoryRecipesShort(String category) async {
AndroidApiVersion androidApiVersion = UserRepository().getAndroidVersion();
Dio client = UserRepository().getAuthenticatedClient();
AppAuthentication appAuthentication =
UserRepository().getCurrentAppAuthentication();

final response = await client.get(
"${appAuthentication.server}/index.php/apps/cookbook/category/$category",
);
Response response;
if (androidApiVersion == AndroidApiVersion.BEFORE_API_ENDPOINT) {
response = await client.get(
"${appAuthentication.server}/index.php/apps/cookbook/category/$category");
} else {
response = await client.get(
"${appAuthentication.server}/index.php/apps/cookbook/api/category/$category");
}

if (response.statusCode == 200) {
return RecipeShort.parseRecipesShort(response.data);
Expand Down
10 changes: 10 additions & 0 deletions lib/src/services/user_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:async';

import 'package:dio/dio.dart';
import 'package:nextcloud_cookbook_flutter/src/services/authentication_provider.dart';
import 'package:nextcloud_cookbook_flutter/src/services/version_provider.dart';

import '../models/app_authentication.dart';

Expand All @@ -14,6 +15,7 @@ class UserRepository {
UserRepository._internal();

AuthenticationProvider authenticationProvider = AuthenticationProvider();
VersionProvider versionProvider = VersionProvider();

Future<AppAuthentication> authenticate(
String serverUrl,
Expand Down Expand Up @@ -79,4 +81,12 @@ class UserRepository {
Future<void> deleteAppAuthentication() async {
return authenticationProvider.deleteAppAuthentication();
}

Future<ApiVersion> fetchApiVersion() async {
return versionProvider.fetchApiVersion();
}

AndroidApiVersion getAndroidVersion() {
return versionProvider.getApiVersion().getAndroidVersion();
}
}
111 changes: 111 additions & 0 deletions lib/src/services/version_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import 'dart:convert';

import 'package:nextcloud_cookbook_flutter/src/models/app_authentication.dart';
import 'package:nextcloud_cookbook_flutter/src/services/user_repository.dart';

class VersionProvider {
ApiVersion _currentApiVersion;
bool warningWasShown = false;

Future<ApiVersion> fetchApiVersion() async {
warningWasShown = false;

AppAuthentication appAuthentication =
UserRepository().getCurrentAppAuthentication();

var response = await appAuthentication.authenticatedClient
.get("${appAuthentication.server}/index.php/apps/cookbook/api/version");

if (response.statusCode == 200 &&
!response.data.toString().startsWith("<!DOCTYPE html>")) {
try {
_currentApiVersion = ApiVersion.decodeJsonApiVersion(response.data);
} catch (e) {
_currentApiVersion = ApiVersion(0, 0, 0, 0, 0);
_currentApiVersion.loadFailureMessage = e.toString();
}
} else {
_currentApiVersion = ApiVersion(0, 0, 0, 0, 0);
}

return _currentApiVersion;
}

ApiVersion getApiVersion() {
return _currentApiVersion;
}
}

class ApiVersion {
static const int CONFIRMED_MAJOR_API_VERSION = 0;
static const int CONFIRMED_MINOR_API_VERSION = 1;

final int majorApiVersion;
final int minorApiVersion;
final int majorAppVersion;
final int minorAppVersion;
final int patchAppVersion;

String loadFailureMessage = "";

ApiVersion(
this.majorApiVersion,
this.minorApiVersion,
this.majorAppVersion,
this.minorAppVersion,
this.patchAppVersion,
);

static ApiVersion decodeJsonApiVersion(jsonString) {
Map<String, dynamic> data = json.decode(jsonString);

if (!(data.containsKey("cookbook_version") &&
data.containsKey("api_version"))) {
throw Exception("Required Fields not present!\n$jsonString");
}

List<int> appVersion = data["cookbook_version"].cast<int>();
var apiVersion = data["api_version"];

if (!(appVersion.length == 3 &&
apiVersion.containsKey("major") &&
apiVersion.containsKey("minor"))) {
throw Exception("Required Fields not present!\n$jsonString");
}

return ApiVersion(
apiVersion["major"],
apiVersion["minor"],
appVersion[0],
appVersion[1],
appVersion[2],
);
}

/// Returns a VersionCode that indicates the app which endpoints to call.
/// Versions only need to be adapted if backwards comparability is required.
AndroidApiVersion getAndroidVersion() {
if (majorApiVersion == 0 && minorApiVersion == 0) {
return AndroidApiVersion.BEFORE_API_ENDPOINT;
} else {
return AndroidApiVersion.CATEGORY_API_TRANSITION;
}
}

bool isVersionAboveConfirmed() {
if (majorApiVersion > CONFIRMED_MAJOR_API_VERSION ||
(majorApiVersion == CONFIRMED_MAJOR_API_VERSION &&
minorApiVersion > CONFIRMED_MINOR_API_VERSION)) {
return true;
} else {
return false;
}
}

@override
String toString() {
return "ApiVersion: $majorApiVersion.$minorApiVersion AppVersion: $majorAppVersion.$minorAppVersion.$patchAppVersion";
}
}

enum AndroidApiVersion { BEFORE_API_ENDPOINT, CATEGORY_API_TRANSITION }
48 changes: 48 additions & 0 deletions lib/src/widget/api_version_warning.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:nextcloud_cookbook_flutter/src/services/user_repository.dart';
import 'package:nextcloud_cookbook_flutter/src/services/version_provider.dart';

class ApiVersionWarning extends StatelessWidget {
@override
Widget build(BuildContext context) {
VersionProvider versionProvider = UserRepository().versionProvider;
ApiVersion apiVersion = versionProvider.getApiVersion();

if (!versionProvider.warningWasShown) {
versionProvider.warningWasShown = true;
Future.delayed(const Duration(milliseconds: 100), () {
if (apiVersion.loadFailureMessage.isNotEmpty) {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(
translate(
"categories.errors.api_version_check_failed",
args: {"error_msg": apiVersion.loadFailureMessage},
),
),
backgroundColor: Colors.red,
),
);
} else if (apiVersion.isVersionAboveConfirmed()) {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(
translate(
"categories.errors.api_version_above_confirmed",
args: {
"version": apiVersion.majorApiVersion.toString() +
"." +
apiVersion.minorApiVersion.toString()
},
),
),
backgroundColor: Colors.orange,
),
);
}
});
}
return Container();
}
}
2 changes: 1 addition & 1 deletion lib/src/widget/authentication_cached_network_image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class AuthenticationCachedNetworkImage extends StatelessWidget {
height: height,
fit: boxFit,
imageUrl:
'${appAuthentication.server}/apps/cookbook/recipes/$imageId/image?$imageSettings',
'${appAuthentication.server}/index.php/apps/cookbook/recipes/$imageId/image?$imageSettings',
httpHeaders: {
"authorization": appAuthentication.basicAuth,
},
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ description: A new Flutter application.
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.3.6+12
version: 0.3.7+13

environment:
sdk: ">=2.6.0 <3.0.0"
Expand Down

0 comments on commit 542425b

Please sign in to comment.