diff --git a/lib/built_with_appwrite_wrapper.dart b/lib/built_with_appwrite_wrapper.dart index 8cd160f..7ce0870 100644 --- a/lib/built_with_appwrite_wrapper.dart +++ b/lib/built_with_appwrite_wrapper.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:url_launcher/url_launcher_string.dart'; class BuiltWithAppwriteWrapper extends StatelessWidget { const BuiltWithAppwriteWrapper({Key? key, required this.child}) @@ -17,12 +18,17 @@ class BuiltWithAppwriteWrapper extends StatelessWidget { ), const SizedBox(height: 10.0), GestureDetector( - onTap: () { - try { - launchUrl(Uri.parse('https://appwrite.io')); - } catch (e) {} - }, - child: SvgPicture.asset('assets/built-with-appwrite-hr.svg')), + onTap: () async { + // + bool canLaunch = await canLaunchUrlString( + 'https://appwrite.io', + ); + // + canLaunch ? launchUrl(Uri.parse('https://appwrite.io')) : null; + // + }, + child: SvgPicture.asset('assets/built-with-appwrite-hr.svg'), + ), const SizedBox(height: 10.0), ], ), diff --git a/lib/constants.dart b/lib/constants.dart index 49d05dc..3899964 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -2,4 +2,5 @@ class AppConstants { static const String endPoint = "https://demo.appwrite.io/v1"; static const String project = "606d5bc9de604"; static const String collection = "quiz_questions"; + static const String database = "default"; } diff --git a/lib/home.dart b/lib/home.dart index d39b5e4..b1bc55a 100644 --- a/lib/home.dart +++ b/lib/home.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_appwrite_quizeee/quiz.dart'; class HomePage extends StatelessWidget { + const HomePage({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { return Scaffold( @@ -23,7 +25,7 @@ class HomePage extends StatelessWidget { ), onPressed: () => Navigator.of(context).push( MaterialPageRoute( - builder: (_) => QuizPage(), + builder: (_) => const QuizPage(), ), ), ), diff --git a/lib/main.dart b/lib/main.dart index 7c01d41..65a1019 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,10 +4,12 @@ import 'package:flutter_appwrite_quizeee/built_with_appwrite_wrapper.dart'; import 'home.dart'; void main() { - runApp(MyApp()); + runApp(const MyApp()); } class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { return MaterialApp( @@ -15,7 +17,7 @@ class MyApp extends StatelessWidget { theme: ThemeData( primarySwatch: Colors.red, ), - home: BuiltWithAppwriteWrapper(child: HomePage()), + home: const BuiltWithAppwriteWrapper(child: HomePage()), ); } } diff --git a/lib/question.dart b/lib/question.dart index 559eff2..5d1afaa 100644 --- a/lib/question.dart +++ b/lib/question.dart @@ -1,33 +1,22 @@ import 'dart:convert'; -import 'package:flutter/foundation.dart'; - -class Question { - String? id; - String? question; - List? options; - String? answer; - Question({ - this.id, - this.question, - this.options, - this.answer, +import 'package:appwrite/models.dart'; +import 'package:equatable/equatable.dart'; + +class Question extends Equatable { + // + final String id; + final String question; + final List options; + final String answer; + // + const Question({ + required this.id, + required this.question, + required this.options, + required this.answer, }); - Question copyWith({ - String? id, - String? question, - List? options, - String? answer, - }) { - return Question( - id: id ?? this.id, - question: question ?? this.question, - options: options ?? this.options, - answer: answer ?? this.answer, - ); - } - Map toMap() { return { 'id': id, @@ -39,6 +28,15 @@ class Question { /* Give a man a program, frustrate him for a day. Teach a man to program, frustrate him for a lifetime */ + factory Question.fromModel(Document document) { + return Question( + id: document.$id, + question: document.data['question'], + options: List.from(document.data['options']), + answer: document.data['answer'] + ); + } + factory Question.fromMap(Map map) { return Question( id: map['\$id'], @@ -50,29 +48,15 @@ Teach a man to program, frustrate him for a lifetime */ String toJson() => json.encode(toMap()); - factory Question.fromJson(String source) => Question.fromMap(json.decode(source)); + factory Question.fromJson(String source) => + Question.fromMap(json.decode(source)); @override String toString() { return 'Question(id: $id, question: $question, options: $options, answer: $answer)'; } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is Question && - other.id == id && - other.question == question && - listEquals(other.options, options) && - other.answer == answer; - } + /// Equality Operator @override - int get hashCode { - return id.hashCode ^ - question.hashCode ^ - options.hashCode ^ - answer.hashCode; - } + List get props => [id, question, options, answer]; } diff --git a/lib/quiz.dart b/lib/quiz.dart index 9961afe..24651e0 100644 --- a/lib/quiz.dart +++ b/lib/quiz.dart @@ -1,11 +1,14 @@ import 'package:appwrite/appwrite.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_appwrite_quizeee/constants.dart'; import 'package:flutter_appwrite_quizeee/question.dart'; class QuizPage extends StatefulWidget { + const QuizPage({Key? key}) : super(key: key); + @override - _QuizPageState createState() => _QuizPageState(); + State createState() => _QuizPageState(); } class _QuizPageState extends State { @@ -32,15 +35,19 @@ class _QuizPageState extends State { Client client = Client(endPoint: AppConstants.endPoint); client.setProject(AppConstants.project); - Database db = Database(client); + Databases db = Databases(client, databaseId: AppConstants.database); /* I'm not a great programmer; I'm just a good programmer with great habits. */ try { final res = await db.listDocuments(collectionId: AppConstants.collection); - questions = res.convertTo( - (q) => Question.fromMap(q as Map)); + + // Convert using Model instead + questions = res.documents.isEmpty + ? [] + : res.documents.map((e) => Question.fromModel(e)).toList(); + questions!.shuffle(); } on AppwriteException catch (e) { - print(e); + if(kDebugMode) print(e); } finally { setState(() { loading = false; @@ -56,7 +63,7 @@ class _QuizPageState extends State { title: Text("Score: $score"), ), body: loading - ? Center( + ? const Center( child: CircularProgressIndicator(), ) : questions != null && questions!.isNotEmpty @@ -66,38 +73,38 @@ class _QuizPageState extends State { itemCount: questions!.length, itemBuilder: (context, index) { final question = questions![index]; - return Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - question.question!, - style: Theme.of(context).textTheme.headline5, - ), - const SizedBox(height: 10.0), - ...question.options!.map( - (opt) => Card( - color: _getColor(question, opt), - elevation: 1, - clipBehavior: Clip.antiAlias, - child: RadioListTile( - value: opt, - title: Text(opt), - groupValue: _answers[question.id], - onChanged: _answers[question.id] != null - ? null - : (dynamic opt) { - if (opt == question.answer) - score += 5; - setState(() { - _answers[question.id] = opt; - }); - }, - ), + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + question.question, + style: Theme.of(context).textTheme.headline5, + ), + const SizedBox(height: 10.0), + ...question.options.map( + (opt) => Card( + color: _getColor(question, opt), + elevation: 1, + clipBehavior: Clip.antiAlias, + child: RadioListTile( + value: opt, + title: Text(opt), + groupValue: _answers[question.id], + onChanged: _answers[question.id] != null + ? null + : (dynamic opt) { + if (opt == question.answer) { + score += 5; + } + + setState(() { + _answers[question.id] = opt; + }); + }, ), - ) - ], - ), + ), + ) + ], ); }, controller: _controller, @@ -108,13 +115,11 @@ class _QuizPageState extends State { }, ), ) - : Container( - child: Text("Some error! Check console"), - ), + : const Text("Some error! Check console"), /* A language that doesn't affect the way you think about programming is not worth knowing. */ bottomNavigationBar: (questions != null && questions!.isNotEmpty) ? BottomAppBar( - child: Container( + child: SizedBox( height: 60.0, child: Row( mainAxisAlignment: MainAxisAlignment.center, @@ -125,7 +130,7 @@ class _QuizPageState extends State { : () { _controller!.jumpToPage(currentPage - 1); }, - child: Text("Prev"), + child: const Text("Prev"), ), const SizedBox(width: 10.0), (currentPage == questions!.length - 1) @@ -133,7 +138,7 @@ class _QuizPageState extends State { onPressed: () { Navigator.pop(context); }, - child: Text("Done"), + child: const Text("Done"), ) : ElevatedButton( onPressed: currentPage >= questions!.length - 1 @@ -141,7 +146,7 @@ class _QuizPageState extends State { : () { _controller!.jumpToPage(currentPage + 1); }, - child: Text("Next"), + child: const Text("Next"), ), ], ), diff --git a/load_questions.dart b/load_questions.dart index 288a2ae..21f5876 100644 --- a/load_questions.dart +++ b/load_questions.dart @@ -13,9 +13,12 @@ void main() async { File json = File('./quiz_app_questions.json'); final questions = jsonDecode(json.readAsStringSync()); - Database db = Database(client); - final collectionId = 'quiz_questions'; - final res = await db.createCollection( + Databases db = Databases(client, databaseId: 'default'); + + await db.create(name: 'default'); + + const collectionId = 'quiz_questions'; + await db.createCollection( collectionId: collectionId, name: "Quiz Questions", permission: 'collection', @@ -23,23 +26,38 @@ void main() async { write: ["role:member"]); await db.createStringAttribute( - collectionId: collectionId, key: 'question', size: 255, xrequired: true); + collectionId: collectionId, + key: 'question', + size: 255, + xrequired: true, + ); + await db.createStringAttribute( - collectionId: collectionId, - key: 'options', - size: 255, - xrequired: false, - array: true); + collectionId: collectionId, + key: 'options', + size: 255, + xrequired: false, + array: true, + ); + await db.createStringAttribute( - collectionId: collectionId, key: 'answer', size: 255, xrequired: true); + collectionId: collectionId, + key: 'answer', + size: 255, + xrequired: true, + ); + + await Future.delayed(const Duration(seconds: 2)); for (final question in questions) { await db.createDocument( documentId: "unique()", - collectionId: collectionId, - data: question, - read: ['*'], - write: ['role:member']); + collectionId: collectionId, + data: question, + read: ['role:all'], + write: ['role:member'], + ); + print(question); } diff --git a/pubspec.lock b/pubspec.lock index 46afadd..76ad4e2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,7 +7,7 @@ packages: name: appwrite url: "https://pub.dartlang.org" source: hosted - version: "4.0.2" + version: "6.0.0" async: dependency: transitive description: @@ -49,7 +49,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0" + version: "1.16.0" cookie_jar: dependency: transitive description: @@ -63,21 +63,21 @@ packages: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.0.2" dart_appwrite: dependency: "direct dev" description: name: dart_appwrite url: "https://pub.dartlang.org" source: hosted - version: "4.0.2" + version: "6.0.0" device_info_plus: dependency: transitive description: name: device_info_plus url: "https://pub.dartlang.org" source: hosted - version: "3.2.3" + version: "3.2.4" device_info_plus_linux: dependency: transitive description: @@ -113,20 +113,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.1" + equatable: + dependency: "direct main" + description: + name: equatable + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" fake_async: dependency: transitive description: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" ffi: dependency: transitive description: name: ffi url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "1.2.1" file: dependency: transitive description: @@ -139,13 +146,20 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" flutter_svg: dependency: "direct main" description: name: flutter_svg url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "1.1.1+1" flutter_test: dependency: "direct dev" description: flutter @@ -176,14 +190,21 @@ packages: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "4.0.0" + version: "4.0.1" js: dependency: transitive description: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.3" + version: "0.6.4" + lints: + dependency: transitive + description: + name: lints + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" matcher: dependency: transitive description: @@ -197,7 +218,7 @@ packages: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.3" + version: "0.1.4" meta: dependency: transitive description: @@ -253,7 +274,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.1" path_drawing: dependency: transitive description: @@ -274,56 +295,56 @@ packages: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "2.0.9" + version: "2.0.11" path_provider_android: dependency: transitive description: name: path_provider_android url: "https://pub.dartlang.org" source: hosted - version: "2.0.12" + version: "2.0.16" path_provider_ios: dependency: transitive description: name: path_provider_ios url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.0.10" path_provider_linux: dependency: transitive description: name: path_provider_linux url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" + version: "2.1.7" path_provider_macos: dependency: transitive description: name: path_provider_macos url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.0.6" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" + version: "2.0.4" path_provider_windows: dependency: transitive description: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.0.7" petitparser: dependency: transitive description: name: petitparser url: "https://pub.dartlang.org" source: hosted - version: "4.4.0" + version: "5.0.0" platform: dependency: transitive description: @@ -356,7 +377,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" stack_trace: dependency: transitive description: @@ -391,91 +412,91 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.8" + version: "0.4.9" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" url_launcher: dependency: "direct main" description: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "6.1.0" + version: "6.1.5" url_launcher_android: dependency: transitive description: name: url_launcher_android url: "https://pub.dartlang.org" source: hosted - version: "6.0.15" + version: "6.0.17" url_launcher_ios: dependency: transitive description: name: url_launcher_ios url: "https://pub.dartlang.org" source: hosted - version: "6.0.15" + version: "6.0.17" url_launcher_linux: dependency: transitive description: name: url_launcher_linux url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "3.0.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "3.0.1" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.1.0" url_launcher_web: dependency: transitive description: name: url_launcher_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.9" + version: "2.0.12" url_launcher_windows: dependency: transitive description: name: url_launcher_windows url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "3.0.1" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.1.2" web_socket_channel: dependency: transitive description: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.2.0" win32: dependency: transitive description: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "2.5.1" + version: "2.6.1" xdg_directories: dependency: transitive description: @@ -489,7 +510,7 @@ packages: name: xml url: "https://pub.dartlang.org" source: hosted - version: "5.3.1" + version: "6.1.0" sdks: - dart: ">=2.15.0 <3.0.0" - flutter: ">=2.10.0" + dart: ">=2.17.0 <3.0.0" + flutter: ">=3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 15fbbe4..a3244da 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,14 +11,16 @@ environment: dependencies: flutter: sdk: flutter - appwrite: ^4.0.2 + appwrite: ^6.0.0 url_launcher: ^6.1.0 flutter_svg: ^1.0.3 + equatable: ^2.0.3 dev_dependencies: flutter_test: sdk: flutter dart_appwrite: null + flutter_lints: ^2.0.1 flutter: uses-material-design: true diff --git a/test/widget_test.dart b/test/widget_test.dart index 3543e89..9d9a036 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -13,7 +13,7 @@ import 'package:flutter_appwrite_quizeee/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(MyApp()); + await tester.pumpWidget(const MyApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget);