commit 87405254574aee68b00de66db343b51cfddf5d16 Author: brausjonas Date: Sun Sep 15 17:03:37 2024 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..29a3a50 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# 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 +.pub-cache/ +.pub/ +/build/ + +# 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 diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..ce66da9 --- /dev/null +++ b/.metadata @@ -0,0 +1,30 @@ +# 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: "4cf269e36de2573851eaef3c763994f8f9be494d" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + base_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + - platform: android + create_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + base_revision: 4cf269e36de2573851eaef3c763994f8f9be494d + + # 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' diff --git a/README.md b/README.md new file mode 100644 index 0000000..8cc812c --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# todoapp + +Simple Todos + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..55afd91 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 0000000..a9a8fba --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,44 @@ +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" +} + +android { + namespace = "com.jonas.todoapp" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.jonas.todoapp" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.debug + } + } +} + +flutter { + source = "../.." +} diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..8621b49 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/com/jonas/todoapp/MainActivity.kt b/android/app/src/main/kotlin/com/jonas/todoapp/MainActivity.kt new file mode 100644 index 0000000..f416e5f --- /dev/null +++ b/android/app/src/main/kotlin/com/jonas/todoapp/MainActivity.kt @@ -0,0 +1,5 @@ +package com.jonas.todoapp + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..b498026 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..b498026 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..b498026 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..b498026 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..b498026 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..d2ffbff --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,18 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = "../build" +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..2597170 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..7bb2df6 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..b9e43bd --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,25 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.1.0" apply false + id "org.jetbrains.kotlin.android" version "1.8.22" apply false +} + +include ":app" diff --git a/lib/elems/filehandler.dart b/lib/elems/filehandler.dart new file mode 100644 index 0000000..fe25db8 --- /dev/null +++ b/lib/elems/filehandler.dart @@ -0,0 +1,75 @@ +import 'package:path_provider/path_provider.dart'; +import 'dart:convert'; +import 'dart:io'; +class FileHandler { + + static Future get localPath async { + final directory = await getApplicationDocumentsDirectory(); + return directory.path; + } + + static Future get localFile async { + final path = await localPath; + return File("$path/todos.json"); + } + + static Future get fileContent async { + await existCheck(); + File f = await localFile; + String content = await f.readAsString(); + final js = await json.decode(content); + return js; + } + + static Future saveFile(js) async { + File f = await localFile; + String content = json.encode(js); + f.writeAsString(content); + } + + static Future saveList(js, String listName) async { + final content = await fileContent; + content[listName] = js; + await saveFile(content); + } + + static Future renameList(String oldListName, String newListName) async { + var newJs = {}; + + final content = await fileContent; + for (String key in content.keys) { + if (key != oldListName) { + newJs[key] = content[key]; + } else { + newJs[newListName] = content[key]; + } + } + await saveFile(newJs); + } + + static Future addList(String listName) async { + var js = await fileContent; + js[listName] = {}; + await saveFile(js); + } + + static Future removeList(String listName) async { + final js = await fileContent; + var newJs = {}; + for (String key in js.keys) { + if (key != listName) { + newJs[key] = js[key]; + } + } + await saveFile(newJs); + } + + static Future existCheck() async { + File f = await localFile; + bool exists = await f.exists(); + + if (!exists) { + await saveFile({"Personal": {}}); + } + } +} \ No newline at end of file diff --git a/lib/elems/todo.dart b/lib/elems/todo.dart new file mode 100644 index 0000000..ad4f441 --- /dev/null +++ b/lib/elems/todo.dart @@ -0,0 +1,9 @@ +import 'package:flutter/material.dart'; + +class Todo { + Todo({required this.title, required this.widget, required this.checked}); + + String title; + Widget widget; + bool checked; +} \ No newline at end of file diff --git a/lib/elems/todolist.dart b/lib/elems/todolist.dart new file mode 100644 index 0000000..e6283fc --- /dev/null +++ b/lib/elems/todolist.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; +import 'package:todoapp/elems/todo.dart'; + +class TodoList { + TodoList({required this.title, required this.widget, required this.todos}); + + String title; + Widget widget; + List todos; +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..79a7e1b --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:todoapp/pages/Home.dart'; +import 'package:provider/provider.dart'; +import 'package:todoapp/providers/todoprovider.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MultiProvider( + providers: [ + ChangeNotifierProvider(create: (context) => TodoProvider()) + ], + child: MaterialApp( + theme: ThemeData( + colorScheme: ColorScheme.fromSeed( + seedColor: Colors.blue, brightness: Brightness.dark), + useMaterial3: true, + ), + home: const Home(), + ), + ); + } +} diff --git a/lib/modals/addlistmodal.dart b/lib/modals/addlistmodal.dart new file mode 100644 index 0000000..c3bc309 --- /dev/null +++ b/lib/modals/addlistmodal.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:todoapp/elems/todo.dart'; +import 'package:todoapp/providers/todoprovider.dart'; + +class AddListModal { + static String tempValue = ""; + + static void add(BuildContext context) { + showModalBottomSheet( + context: context, + showDragHandle: true, + useSafeArea: true, + scrollControlDisabledMaxHeightRatio: 1, + builder: (context) => Padding( + padding: const EdgeInsets.only(left: 30, right: 30), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextFormField( + autofocus: true, + decoration: const InputDecoration(labelText: "Title"), + onChanged: (value) => tempValue = value, + onFieldSubmitted: (value) { + context.read().addList(value); + Navigator.pop(context); + }, + ), + const SizedBox(height: 20,), + SegmentedButton( + segments: const [ + ButtonSegment( + value: "Cancel", + label: Text("Cancel"), + icon: Icon(Icons.cancel) + ), + ButtonSegment( + value: "Save", + label: Text("Save"), + icon: Icon(Icons.save) + ), + ], + selected: const {"None"}, + style: ButtonStyle(iconColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.primary)), + onSelectionChanged: (p0) { + if (p0.last == "Save") { + context.read().addList(tempValue); + } + Navigator.pop(context); + }, + ) + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/modals/addtodomodal.dart b/lib/modals/addtodomodal.dart new file mode 100644 index 0000000..6839d91 --- /dev/null +++ b/lib/modals/addtodomodal.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:todoapp/elems/todo.dart'; +import 'package:todoapp/providers/todoprovider.dart'; + +class AddTodoModal { + static String tempValue = ""; + + static void add(BuildContext context) { + showModalBottomSheet( + context: context, + showDragHandle: true, + useSafeArea: true, + scrollControlDisabledMaxHeightRatio: 1, + builder: (context) => Padding( + padding: const EdgeInsets.only(left: 30, right: 30), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextFormField( + autofocus: true, + decoration: const InputDecoration(labelText: "Title"), + onChanged: (value) => tempValue = value, + onFieldSubmitted: (value) { + context.read().addTodo(value); + Navigator.pop(context); + }, + ), + const SizedBox(height: 20,), + SegmentedButton( + segments: const [ + ButtonSegment( + value: "Cancel", + label: Text("Cancel"), + icon: Icon(Icons.cancel) + ), + ButtonSegment( + value: "Save", + label: Text("Save"), + icon: Icon(Icons.save) + ), + ], + selected: const {"None"}, + style: ButtonStyle(iconColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.primary)), + onSelectionChanged: (p0) { + if (p0.last == "Save") { + context.read().addTodo(tempValue); + } + Navigator.pop(context); + }, + ) + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/modals/editlistmodal.dart b/lib/modals/editlistmodal.dart new file mode 100644 index 0000000..802f71b --- /dev/null +++ b/lib/modals/editlistmodal.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:todoapp/elems/todo.dart'; +import 'package:todoapp/providers/todoprovider.dart'; + +class EditListModal { + static String tempValue = ""; + + static void add(BuildContext context, String title) { + showModalBottomSheet( + context: context, + showDragHandle: true, + useSafeArea: true, + scrollControlDisabledMaxHeightRatio: 1, + builder: (context) => Padding( + padding: const EdgeInsets.only(left: 30, right: 30), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextFormField( + autofocus: true, + initialValue: title, + decoration: const InputDecoration(labelText: "Title"), + onChanged: (value) => tempValue = value, + onFieldSubmitted: (value) { + context.read().renameList(title, value); + Navigator.pop(context); + }, + ), + const SizedBox(height: 20,), + SegmentedButton( + segments: const [ + ButtonSegment( + value: "Delete", + label: Text("Delete"), + icon: Icon(Icons.delete) + ), + ButtonSegment( + value: "Cancel", + label: Text("Cancel"), + icon: Icon(Icons.cancel) + ), + ButtonSegment( + value: "Save", + label: Text("Save"), + icon: Icon(Icons.save) + ), + ], + selected: const {"None"}, + style: ButtonStyle(iconColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.primary)), + onSelectionChanged: (p0) { + if (p0.last == "Save") { + context.read().renameList(title, tempValue); + } else if (p0.last == "Delete") { + context.read().removeList(title); + } + Navigator.pop(context); + }, + ) + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/modals/edittodomodal.dart b/lib/modals/edittodomodal.dart new file mode 100644 index 0000000..66edbb3 --- /dev/null +++ b/lib/modals/edittodomodal.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:todoapp/elems/todo.dart'; +import 'package:todoapp/providers/todoprovider.dart'; + +class EditTodoModal { + static String tempValue = ""; + + static void add(BuildContext context, String title) { + showModalBottomSheet( + context: context, + showDragHandle: true, + useSafeArea: true, + scrollControlDisabledMaxHeightRatio: 1, + builder: (context) => Padding( + padding: const EdgeInsets.only(left: 30, right: 30), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextFormField( + autofocus: true, + initialValue: title, + decoration: const InputDecoration(labelText: "Title"), + onChanged: (value) => tempValue = value, + onFieldSubmitted: (value) { + context.read().updateTodo(title, value); + Navigator.pop(context); + }, + ), + const SizedBox(height: 20,), + SegmentedButton( + segments: const [ + ButtonSegment( + value: "Delete", + label: Text("Delete"), + icon: Icon(Icons.delete) + ), + ButtonSegment( + value: "Cancel", + label: Text("Cancel"), + icon: Icon(Icons.cancel) + ), + ButtonSegment( + value: "Save", + label: Text("Save"), + icon: Icon(Icons.save) + ), + ], + selected: const {"None"}, + style: ButtonStyle(iconColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.primary)), + onSelectionChanged: (p0) { + if (p0.last == "Save") { + context.read().updateTodo(title, tempValue); + } else if (p0.last == "Delete") { + context.read().removeTodo(title); + } + Navigator.pop(context); + }, + ) + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/modules/actionbutton.dart b/lib/modules/actionbutton.dart new file mode 100644 index 0000000..1f17e96 --- /dev/null +++ b/lib/modules/actionbutton.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:todoapp/modals/addtodomodal.dart'; +import 'package:todoapp/providers/todoprovider.dart'; + +class TodoActionButton extends StatelessWidget { + const TodoActionButton({super.key}); + + @override + Widget build(BuildContext context) { + return FloatingActionButton.extended( + onPressed: () => { + AddTodoModal.add(context) + }, //TODO: implement + label: const Text("Add ToDo"), + icon: const Icon(Icons.add), + ); + } +} diff --git a/lib/modules/appbar.dart b/lib/modules/appbar.dart new file mode 100644 index 0000000..7e187d6 --- /dev/null +++ b/lib/modules/appbar.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:todoapp/providers/todoprovider.dart'; + +class TodoAppBar extends StatefulWidget implements PreferredSizeWidget { + const TodoAppBar({super.key}); + + @override + State createState() => _TodoAppBarState(); + + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight); +} + +class _TodoAppBarState extends State { + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, todoProvider, child) => AppBar( + elevation: 5, + title: Text(todoProvider.selectedList), + ), + ); + } +} diff --git a/lib/modules/body.dart b/lib/modules/body.dart new file mode 100644 index 0000000..1bf904c --- /dev/null +++ b/lib/modules/body.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:todoapp/providers/todoprovider.dart'; + +import '../elems/todo.dart'; + +class Body extends StatefulWidget { + const Body({super.key}); + + @override + State createState() => _BodyState(); +} + +class _BodyState extends State { + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, todoProvider, child) => Column( + children: [ + for (Todo t in context.read().todos) t.widget, + ], + ), + ); + } +} diff --git a/lib/modules/drawer.dart b/lib/modules/drawer.dart new file mode 100644 index 0000000..565ad6f --- /dev/null +++ b/lib/modules/drawer.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:todoapp/modals/addlistmodal.dart'; +import 'package:todoapp/providers/todoprovider.dart'; + +class TodoDrawer extends StatefulWidget { + const TodoDrawer({super.key}); + + @override + State createState() => _TodoDrawerState(); +} + +class _TodoDrawerState extends State { + @override + Widget build(BuildContext context) { + return Drawer( + child: Padding( + padding: const EdgeInsets.only(left: 20, right: 20), + child: Column( + children: [ + const SizedBox(height: 60), + const Text("ToDo", style: TextStyle(fontSize: 20)), + const SizedBox(height: 20), + const Divider(height: 1), + const SizedBox(height: 20), + const Text("Lists", style: TextStyle(fontSize: 25)), + const SizedBox(height: 10), + SingleChildScrollView( + child: Consumer( + builder: (context, todoProvider, child) => + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + for (String key in todoProvider.todoLists.keys) + todoProvider.todoLists[key]!.widget, + const SizedBox(height: 30), + FilledButton.icon( + onPressed: () { + AddListModal.add(context); + }, + icon: const Icon(Icons.add), + label: const Text("Add List"), + ) + ], + ), + ) + ) + ], + ), + ), + ); + } +} diff --git a/lib/modules/todolistwidget.dart b/lib/modules/todolistwidget.dart new file mode 100644 index 0000000..2b8bf77 --- /dev/null +++ b/lib/modules/todolistwidget.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:todoapp/modals/editlistmodal.dart'; +import 'package:todoapp/providers/todoprovider.dart'; + +class TodoListWidget extends StatefulWidget { + const TodoListWidget({super.key, required this.title}); + + final String title; + + @override + State createState() => _TodoListWidgetState(); +} + +class _TodoListWidgetState extends State { + @override + Widget build(BuildContext context) { + ButtonStyle buttonStyle = context + .read() + .selectedList == widget.title ? const ButtonStyle() : const ButtonStyle( + backgroundColor: WidgetStatePropertyAll(Color(0x00000000))); + + return FilledButton.tonal( + onPressed: () { + context.read().changeList(widget.title); + Navigator.pop(context); + }, + onLongPress: () { + EditListModal.add(context, widget.title); + }, + style: buttonStyle, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const Icon(Icons.list), + const SizedBox(width: 10), + Text(widget.title) + ], + ), + ); + } +} diff --git a/lib/modules/todowidget.dart b/lib/modules/todowidget.dart new file mode 100644 index 0000000..a6b2a85 --- /dev/null +++ b/lib/modules/todowidget.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:todoapp/modals/edittodomodal.dart'; +import 'package:todoapp/providers/todoprovider.dart'; + +class TodoWidget extends StatefulWidget { + const TodoWidget({super.key, required this.checked, required this.title}); + + final bool checked; + final String title; + + @override + State createState() => _TodoWidgetState(); +} + +class _TodoWidgetState extends State { + @override + Widget build(BuildContext context) { + return GestureDetector( + onLongPress: () { + EditTodoModal.add(context, widget.title); + }, + child: CheckboxListTile( + value: widget.checked, + title: Text(widget.title, + style: widget.checked + ? const TextStyle(decoration: TextDecoration.lineThrough) + : const TextStyle()), + onChanged: (value) => + {context.read().setTodoState(widget.title, value!)}, + ), + ); + } +} diff --git a/lib/pages/Home.dart b/lib/pages/Home.dart new file mode 100644 index 0000000..c936cd5 --- /dev/null +++ b/lib/pages/Home.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:todoapp/modules/actionbutton.dart'; +import 'package:todoapp/modules/appbar.dart'; +import 'package:todoapp/modules/body.dart'; +import 'package:todoapp/modules/drawer.dart'; +import 'package:provider/provider.dart'; +import 'package:todoapp/providers/todoprovider.dart'; + +class Home extends StatefulWidget { + const Home({super.key}); + + @override + State createState() => _HomeState(); +} + +class _HomeState extends State { + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: context.read().readListsFromDisk(), + builder: (context, snapshot) => const Scaffold( + appBar: TodoAppBar(), + body: Body(), + drawer: TodoDrawer(), + floatingActionButton: TodoActionButton(), + ), + ); + } +} diff --git a/lib/providers/todoprovider.dart b/lib/providers/todoprovider.dart new file mode 100644 index 0000000..454650c --- /dev/null +++ b/lib/providers/todoprovider.dart @@ -0,0 +1,141 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:todoapp/elems/filehandler.dart'; +import 'package:todoapp/elems/todo.dart'; +import 'package:todoapp/elems/todolist.dart'; +import 'package:todoapp/modules/todolistwidget.dart'; +import 'package:todoapp/modules/todowidget.dart'; + +class TodoProvider extends ChangeNotifier { + TodoProvider() {} + + Future readListsFromDisk() async { + Map js = await FileHandler.fileContent; + + for (String key in js.keys) { + todoLists[key] = TodoList(title: key, widget: buildTodoListWidget(key), todos: []); + + for (String key2 in js[key].keys) { + todoLists[key]!.todos.add(Todo(title: key2, widget: buildTodoWidget(key2, js[key][key2]), checked: js[key][key2])); + } + } + + todos = todoLists[selectedList]!.todos; + + notifyListeners(); + } + + Map todoLists = {}; + List todos = []; + String selectedList = "Personal"; + + void renameList(String oldTitle, String newTitle) async { + TodoList tempList = todoLists[oldTitle]!; + tempList.title = newTitle; + tempList.widget = buildTodoListWidget(newTitle); + todoLists.remove(oldTitle); + todoLists[newTitle] = tempList; + selectedList = newTitle; + todos = todoLists[newTitle]!.todos; + + await FileHandler.renameList(oldTitle, newTitle); + + notifyListeners(); + } + + void removeList(String title) async { + todoLists.remove(title); + selectedList = "Personal"; + todos = todoLists["Personal"]!.todos; + + await FileHandler.removeList(title); + + notifyListeners(); + } + + void addList(String title) async { + TodoList list = + TodoList(title: title, widget: buildTodoListWidget(title), todos: []); + todoLists[title] = list; + + await FileHandler.addList(title); + + notifyListeners(); + } + + void changeList(String title) { + if (!todoLists.containsKey(title)) { + return; + } + + selectedList = title; + todos = todoLists[title]!.todos; + notifyListeners(); + } + + Future writeTodoChanges() async { + Map js = {}; + + for (Todo t in todos) { + js[t.title] = t.checked; + } + + print(js); + + await FileHandler.saveList(js, selectedList); + } + + void addTodo(String title) async { + todos.add(Todo( + title: title, checked: false, widget: buildTodoWidget(title, false))); + + await writeTodoChanges(); + notifyListeners(); + } + + void removeTodo(String title) async { + Todo todo = todoByTitle(title); + todos.remove(todo); + + await writeTodoChanges(); + notifyListeners(); + } + + void updateTodo(String oldTitle, String newTitle) async { + Todo todo = todoByTitle(oldTitle); + todo.title = newTitle; + todo.widget = buildTodoWidget(newTitle, todo.checked); + + await writeTodoChanges(); + notifyListeners(); + } + + void setTodoState(String title, bool checked) async { + Todo todo = todoByTitle(title); + + todo.checked = checked; + todo.widget = buildTodoWidget(title, checked); + + await writeTodoChanges(); + notifyListeners(); + } + + TodoListWidget buildTodoListWidget(String title) { + return TodoListWidget(title: title); + } + + TodoWidget buildTodoWidget(String title, bool checked) { + return TodoWidget(checked: checked, title: title); + } + + Todo todoByTitle(String title) { + for (Todo t in todos) { + if (t.title == title) { + return t; + } + } + return Todo(title: "None", widget: const SizedBox(), checked: false); + } +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..0cfa168 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,309 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + url: "https://pub.dev" + source: hosted + version: "10.0.5" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + url: "https://pub.dev" + source: hosted + version: "1.15.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + path_provider: + dependency: "direct dev" + description: + name: path_provider + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 + url: "https://pub.dev" + source: hosted + version: "2.1.4" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" + url: "https://pub.dev" + source: hosted + version: "2.2.10" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + url: "https://pub.dev" + source: hosted + version: "2.4.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + provider: + dependency: "direct dev" + description: + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + url: "https://pub.dev" + source: hosted + version: "6.1.2" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + url: "https://pub.dev" + source: hosted + version: "14.2.5" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" +sdks: + dart: ">=3.5.2 <4.0.0" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..408a9c0 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,92 @@ +name: todoapp +description: "Simple Todos" +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ^3.5.2 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + +dev_dependencies: + flutter_test: + sdk: flutter + provider: + path_provider: + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^4.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..c7fd942 --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:todoapp/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +}