Compare commits

...

25 commits

Author SHA1 Message Date
Bart Ribbers
79f31f2552 chore: ready the package for deployment to the pub server 2024-08-30 10:39:11 +02:00
Bart Ribbers
0136f84a10 chore: add fvm configuration to gitignore 2024-08-30 10:21:28 +02:00
Vick Top
333c189428 fix(ci): fix deprecated issue 2024-08-30 10:20:57 +02:00
Vick Top
9bac97f831 feat(documentation): Create component-documentation.yml workflow file 2024-08-30 10:20:02 +02:00
mike doornenbal
8b333e7bb7 feat: ability to sort on starttime 2023-12-11 10:09:15 +01:00
Freek van de Ven
32b9f9f2c5 feat: add firebase timetable options 2023-12-03 15:10:58 +01:00
Freek van de Ven
f031828067 feat: add firebase service for fetching timetable events 2023-12-03 11:42:21 +01:00
Freek van de Ven
651ed33cfc feat: create melos package for flutter_timetable 2023-12-03 11:03:02 +01:00
Freek van de Ven
93b25c7db6 feat: add option to sort the blocks by their id 2023-11-12 18:14:31 +01:00
Gorter-dev
01a7cfffb1
Merge pull request #12 from Iconica-Development/1.3.0
feat: add ability to offset hours so table can span across midnight
2023-11-10 11:24:27 +01:00
Freek van de Ven
dc338d4e37 feat: add ability to offset hours so table can span across midnight 2023-11-10 11:19:11 +01:00
Jacques Doeleman
f79ae93962
Merge pull request #10 from Iconica-Development/hotfix/assert_scroll_offset
fix: Fixed the assert to check the values properly
2023-11-07 10:41:48 +01:00
Jacques
3d6a78de65 fix: Fixed the assert to check the values properly 2023-11-07 10:39:22 +01:00
Gorter-dev
77d5e2fb36
Merge pull request #9 from Iconica-Development/feature/border_scroll
feat: Added the border scroll feature. Next page is opened when scrol…
2023-11-07 10:06:50 +01:00
Jacques
dd88521c9d fix: Removed the magic numbers 2023-11-07 09:46:17 +01:00
Jacques
e05105d452 fix: version and changelog 2023-11-06 13:49:47 +01:00
Jacques
18f40b115f feat: Added the border scroll feature. Next page is opened when scrolling to specific offset 2023-11-06 13:42:54 +01:00
Freek van de Ven
2d081f8efa feat: add documentation pipeline 2023-08-22 13:17:16 +02:00
Gorter-dev
7b80839bea
Merge pull request #7 from Iconica-Development/1.1.0
1.1.0
2023-08-17 14:05:39 +02:00
Freek van de Ven
528dc5c240 refactor: rename timetable component 2023-08-17 10:47:08 +02:00
Freek van de Ven
289f1ea25a feat: scroll to currentTime when no items 2023-08-17 10:36:16 +02:00
Freek van de Ven
ae8d135eb4 feat: update component CI 2023-08-17 10:25:33 +02:00
Thomas Klein Langenhorst
8f33c14b83
Merge pull request #5 from Iconica-Development/feature/Improve_README
improved example, updated README and aaded GIF
2023-03-31 13:48:52 +02:00
Thomas Klein Langenhorst
17954a364e Added Badges, added Github Actions 2023-03-31 13:39:41 +02:00
Thomas Klein Langenhorst
8da3ac0585 improved example, updated README and aaded GIF 2022-11-22 16:06:34 +01:00
59 changed files with 1144 additions and 936 deletions

23
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,23 @@
version: 2
updates:
# - package-ecosystem: "pub"
# directory: "/packages/flutter_timetable"
# schedule:
# interval: "weekly"
- package-ecosystem: "pub"
directory: "/packages/flutter_timetable_interface"
schedule:
interval: "weekly"
- package-ecosystem: "pub"
directory: "/packages/flutter_timetable_firebase"
schedule:
interval: "weekly"
- package-ecosystem: "pub"
directory: "/packages/flutter_timetable_view"
schedule:
interval: "weekly"

View file

@ -0,0 +1,14 @@
name: Iconica Standard Component Documentation Workflow
# Workflow Caller version: 1.0.0
on:
release:
types: [published]
workflow_dispatch:
jobs:
call-iconica-component-documentation-workflow:
uses: Iconica-Development/.github/.github/workflows/component-documentation.yml@master
secrets: inherit
permissions: write-all

14
.github/workflows/melos-ci.yml vendored Normal file
View file

@ -0,0 +1,14 @@
name: Iconica Standard Melos CI Workflow
# Workflow Caller version: 1.0.0
on:
pull_request:
workflow_dispatch:
jobs:
call-global-iconica-workflow:
uses: Iconica-Development/.github/.github/workflows/melos-ci.yml@master
secrets: inherit
permissions: write-all
with:
subfolder: '.' # add optional subfolder to run workflow in

20
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,20 @@
name: Component Release Documentation Update
on:
release:
types:
- created
workflow_dispatch:
jobs:
trigger:
runs-on: ubuntu-latest
steps:
- name: Trigger central repository workflow
run: |
curl -X POST \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/Iconica-Development/iconica_component_documentation/actions/workflows/documentation.yml/dispatches \
-d '{"ref":"master", "inputs":{"repository_url":"${{ github.event.repository.html_url }}"}}'

20
.gitignore vendored
View file

@ -23,15 +23,31 @@ migrate_working_dir/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
pubspec.lock
**/doc/api/
.dart_tool/
.packages
build/
.metadata
.flutter-plugins
.flutter-plugins-dependencies
android/
ios/
web/
linux/
macos/
windows/
pubspec_overrides.yaml
example/android/
example/ios/
example/web/
example/linux/
example/macos/
example/windows/
example/windows/
# FVM Version Cache
.fvm/
.fvmrc

View file

@ -1,10 +0,0 @@
# 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: f1875d570e39de09040c8f79aa13cc56baab8db1
channel: stable
project_type: package

View file

@ -10,3 +10,33 @@
* Added horizontal variant
* Adjustable size for the component
## [1.1.0] - 17 August 2023
* Set scrolling to the current time by default if there are no blocks
## [1.2.0] - 6 November 2023
* Add the ability to use BorderScroll. If enabled the next page is shown when scrolling to an specific offset.
## [1.2.1] - 7 November 2023
* Fixed the assert on the [scrollTriggerOffset] and [scrollJumpToOffset].
## [1.3.0] - 8 November 2023
* Add the option for setting an offset for the hours so that the first hour is not 00:00 but 08:00 for example and the last hour can be after 24:00.
## [1.4.0] - 13 November 2023
* Add the option for sorting the blocks by their id.
## [2.0.0] - 03 December 2023
* Create Melos variant of the component where there are multiple packages in the same repository.
* Added the option to sort on the starttime of an event.
## [3.0.0] - 11 July 2024
* Rename main entry point to flutter_timetable

198
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,198 @@
# Contributing
First off, thanks for taking the time to contribute! ❤️
All types of contributions are encouraged and valued.
See the [Table of Contents](#table-of-contents) for different ways to help and details about how we handle them.
Please make sure to read the relevant section before making your contribution.
It will make it a lot easier for us maintainers and smooth out the experience for all involved.
Iconica looks forward to your contributions. 🎉
## Table of contents
- [Code of conduct](#code-of-conduct)
- [I Have a Question](#i-have-a-question)
- [I Want To Contribute](#i-want-to-contribute)
- [Reporting Bugs](#reporting-bugs)
- [Contributing code](#contributing-code)
## Code of conduct
### Legal notice
When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license.
All accepted pull requests and other additions to this project will be considered intellectual property of Iconica.
All repositories should be kept clean of jokes, easter eggs and other unnecessary additions.
## I have a question
If you want to ask a question, we assume that you have read the available documentation found within the code.
Before you ask a question, it is best to search for existing issues that might help you.
In case you have found a suitable issue but still need clarification, you can ask your question
It is also advisable to search the internet for answers first.
If you then still feel the need to ask a question and need clarification, we recommend the following:
- Open an issue.
- Provide as much context as you can about what you're running into.
We will then take care of the issue as soon as possible.
## I want to contribute
### Reporting bugs
<!-- omit in toc -->
**Before submitting a bug report**
A good bug report shouldn't leave others needing to chase you up for more information.
Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report.
Please complete the following steps in advance to help us fix any potential bug as fast as possible.
- Make sure that you are using the latest version.
- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (If you are looking for support, you might want to check [this section](#i-have-a-question)).
- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error.
- Also make sure to search the internet (including Stack Overflow) to see if users outside of Iconica have discussed the issue.
- Collect information about the bug:
- Stack trace (Traceback)
- OS, Platform and Version (Windows, Linux, macOS, x86, ARM)
- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant.
- Time and date of occurance
- Describe the expected result and actual result
- Can you reliably reproduce the issue? And can you also reproduce it with older versions? Describe all steps that lead to the bug.
Once it's filed:
- The project team will label the issue accordingly.
- A team member will try to reproduce the issue with your provided steps.
If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for additional information.
- If the team is able to reproduce the issue, it will be moved into the backlog, as well as marked with a priority, and the issue will be left to be [implemented by someone](#contributing-code).
### Contributing code
When you start working on your contribution, make sure you are aware of the relevant documentation and the functionality of the component you are working on.
When writing code, follow the style guidelines set by Dart: [Effective Dart](https://Dart.dev/guides/language/effective-Dart). This contains most information you will need to write clean Dart code that is well documented.
**Documentation**
As Effective Dart indicates, documenting your public methods with Dart doc comments is recommended.
Aside from Effective Dart, we require specific information in the documentation of a method:
At the very least, your documentation should first name what the code does, then followed below by requirements for calling the method, the result of the method.
Any references to internal variables or other methods should be done through [var] to indicate a reference.
If the method or class is complex enough (determined by the reviewers) an example is required.
If unsure, add an example in the docs using code blocks.
For classes and methods, document the individual parameters with their implications.
> Tip: Remember that the shortest documentation can be written by having good descriptive names in the first place.
An example:
````Dart
library iconica_utilities.bidirectional_sorter;
part 'sorter.Dart';
part 'enum.Dart';
/// Generic sort method, allow sorting of list with primitives or complex types.
/// Uses [SortDirection] to determine the direction, either Ascending or Descending,
/// Gets called on [List] toSort of type [T] which cannot be shorter than 2.
/// Optionally for complex types a [Comparable] [Function] can be given to compare complex types.
/// ```
/// List<TestObject> objects = [];
/// for (int i = 0; i < 10; i++) {
/// objects.add(TestObject(name: "name", id: i));
/// }
///
/// sort<TestObject>(
/// SortDirection.descending, objects, (object) => object.id);
///
/// ```
/// In the above example a list of TestObjects is created, and then sorted in descending order.
/// If the implementation of TestObject is as following:
/// ```
/// class TestObject {
/// final String name;
/// final int id;
///
/// TestObject({required this.name, required this.id});
/// }
/// ```
/// And the list is logged to the console, the following will appear:
/// ```
/// [name9, name8, name7, name6, name5, name4, name3, name2, name1, name0]
/// ```
void sort<T>(
/// Determines the sorting direction, can be either Ascending or Descending
SortDirection sortDirection,
/// Incoming list, which gets sorted
List<T> toSort, [
/// Optional comparable, which is only necessary for complex types
SortFieldGetter<T>? sortValueCallback,
]) {
if (toSort.length < 2) return;
assert(
toSort.whereType<Comparable>().isNotEmpty || sortValueCallback != null);
BidirectionalSorter<T>(
sortInstructions: <SortInstruction<T>>[
SortInstruction(
sortValueCallback ?? (t) => t as Comparable, sortDirection),
],
).sort(toSort);
}
/// same functionality as [sort] but with the added functionality
/// of sorting multiple values
void sortMulti<T>(
/// Incoming list, which gets sorted
List<T> toSort,
/// list of comparables to sort multiple values at once,
/// priority based on index
List<SortInstruction<T>> sortValueCallbacks,
) {
if (toSort.length < 2) return;
assert(sortValueCallbacks.isNotEmpty);
BidirectionalSorter<T>(
sortInstructions: sortValueCallbacks,
).sort(toSort);
}
````
**Tests**
For each public method that was created, excluding widgets, which contains any form of logic (e.g. Calculations, predicates or major side-effects) tests are required.
A set of tests is written for each method, covering at least each path within the method. For example:
```Dart
void foo() {
try {
var bar = doSomething();
if (bar) {
doSomethingElse();
} else {
doSomethingCool();
}
} catch (_) {
displayError();
}
}
```
The method above should result in 3 tests:
1. A test for the path leading to displayError by the cause of an exception
2. A test for if bar is true, resulting in doSomethingElse()
3. A test for if bar is false, resulting in the doSomethingCool() method being called.
This means that we require 100% coverage of each method you test.

View file

@ -1,4 +1,4 @@
Copyright (c) 2022 Iconica, All rights reserved.
Copyright (c) 2023 Iconica, All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

View file

@ -1,24 +1,27 @@
# timetable
A Flutter package for creating a timetable widget in which to display blocks of time with optional widgets in them.
[![pub package](https://img.shields.io/pub/v/flutter_timetable.svg)](https://github.com/Iconica-Development) [![Build status](https://img.shields.io/github/workflow/status/Iconica-Development/flutter_timetable/CI)](https://github.com/Iconica-Development/flutter_timetable/actions/new) [![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://github.com/tenhobi/effective_dart)
# Flutter Timetable
A Flutter package for creating a Timetable widget in which to display blocks of time with optional widgets in them.
The vertical time range is configurable and the widget is horizontally scrollable. The timetable has options to merge blocks below eachother when they are not overlapping or collapse items that are at the same time and have the same identifier.
Supports all Flutter platforms.
![Timetable GIF](flutter_timetable.gif)
## Usage
## Features
## Setup
To use this package, add `timetable` as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/platform-integration/platform-channels).
To use this package, add `timetable` as a dependency in your pubspec.yaml file.
### Example
See [Example Code](example/lib/main.dart) for more info.
### Issues & Feedback
## Issues
Please file an [issue](https://github.com/Iconica-Development/timetable/issues) to send feedback or report a bug,
If you want to ask a question or suggest an idea then you can [open an discussion](https://github.com/Iconica-Development/timetable/discussions).
Thank you!
Please file any issues, bugs or feature request as an issue on our [GitHub](https://github.com/Iconica-Development/flutter_date_time_picker/pulls) page. Commercial support is available if you need help with integration with your app or services. You can contact us at [support@iconica.nl](mailto:support@iconica.nl).
### Contributing
## Want to contribute
Every pull request is welcome.
If you would like to contribute to the plugin (e.g. by improving the documentation, solving a bug or adding a cool new feature), please carefully review our [contribution guide](./CONTRIBUTING.md) and send us your [pull request](https://github.com/Iconica-Development/flutter_date_time_picker/pulls).
## Author
This `timetable` for Flutter is developed by [Iconica](https://iconica.nl). You can contact us at <support@iconica.nl>

View file

@ -1,214 +0,0 @@
include: package:flutter_lints/flutter.yaml
analyzer:
errors:
todo: ignore
exclude: [lib/generated_plugin_registrant.dart]
linter:
# https://dart.dev/tools/linter-rules#lints
rules:
# error rules
always_use_package_imports: false
avoid_dynamic_calls: true
avoid_empty_else: true
avoid_print: true
avoid_relative_lib_imports: true
avoid_returning_null_for_future: true
avoid_slow_async_io: true
avoid_type_to_string: true
avoid_types_as_parameter_names: true
avoid_web_libraries_in_flutter: true
cancel_subscriptions: true
close_sinks: true
comment_references: false
control_flow_in_finally: true
diagnostic_describe_all_properties: false
empty_statements: true
hash_and_equals: true
invariant_booleans: true
iterable_contains_unrelated_type: true
list_remove_unrelated_type: true
literal_only_boolean_expressions: true
no_adjacent_strings_in_list: true
no_duplicate_case_values: true
no_logic_in_create_state: true
prefer_relative_imports: false
prefer_void_to_null: true
test_types_in_equals: true
throw_in_finally: true
unnecessary_statements: true
unrelated_type_equality_checks: true
unsafe_html: true
use_build_context_synchronously: true
use_key_in_widget_constructors: true
valid_regexps: true
# style rules
always_declare_return_types: true
always_put_control_body_on_new_line: true
always_put_required_named_parameters_first: true
always_require_non_null_named_parameters: true
always_specify_types: false
annotate_overrides: true
avoid_annotating_with_dynamic: false
avoid_bool_literals_in_conditional_expressions: true
avoid_catches_without_on_clauses: false
avoid_catching_errors: false
avoid_classes_with_only_static_members: true
avoid_double_and_int_checks: true
avoid_equals_and_hash_code_on_mutable_classes: false
avoid_escaping_inner_quotes: false
avoid_field_initializers_in_const_classes: true
avoid_final_parameters: true
avoid_function_literals_in_foreach_calls: true
avoid_implementing_value_types: true
avoid_init_to_null: true
avoid_js_rounded_ints: true
avoid_multiple_declarations_per_line: true
avoid_null_checks_in_equality_operators: true
avoid_positional_boolean_parameters: true
avoid_private_typedef_functions: true
avoid_redundant_argument_values: false
avoid_renaming_method_parameters: true
avoid_return_types_on_setters: true
avoid_returning_null: true
avoid_returning_null_for_void: true
avoid_returning_this: true
avoid_setters_without_getters: true
avoid_shadowing_type_parameters: true
avoid_single_cascade_in_expression_statements: true
avoid_types_on_closure_parameters: false
avoid_unnecessary_containers: false
avoid_unused_constructor_parameters: true
avoid_void_async: true
await_only_futures: true
camel_case_extensions: true
camel_case_types: true
cascade_invocations: true
cast_nullable_to_non_nullable: true
conditional_uri_does_not_exist: true
constant_identifier_names: true
curly_braces_in_flow_control_structures: true
deprecated_consistency: true
directives_ordering: true
do_not_use_environment: true
empty_catches: true
empty_constructor_bodies: true
eol_at_end_of_file: true
exhaustive_cases: true
file_names: true
flutter_style_todos: true
implementation_imports: true
join_return_with_assignment: true
leading_newlines_in_multiline_strings: true
library_names: true
library_prefixes: true
library_private_types_in_public_api: true
lines_longer_than_80_chars: true
missing_whitespace_between_adjacent_strings: true
no_default_cases: true
no_leading_underscores_for_library_prefixes: true
no_leading_underscores_for_local_identifiers: true
no_runtimeType_toString: true
non_constant_identifier_names: true
noop_primitive_operations: true
null_check_on_nullable_type_parameter: true
null_closures: true
omit_local_variable_types: true
one_member_abstracts: true
only_throw_errors: true
overridden_fields: true
package_api_docs: true
package_prefixed_library_names: true
parameter_assignments: true
prefer_adjacent_string_concatenation: true
prefer_asserts_in_initializer_lists: true
prefer_asserts_with_message: true
prefer_collection_literals: true
prefer_conditional_assignment: true
prefer_const_constructors: true
prefer_const_constructors_in_immutables: true
prefer_const_declarations: false
prefer_const_literals_to_create_immutables: false
prefer_constructors_over_static_methods: true
prefer_contains: true
prefer_double_quotes: false
prefer_equal_for_default_values: true
prefer_expression_function_bodies: false
prefer_final_fields: true
prefer_final_in_for_each: false
prefer_final_locals: false
prefer_final_parameters: false
prefer_for_elements_to_map_fromIterable: true
prefer_foreach: true
prefer_function_declarations_over_variables: true
prefer_generic_function_type_aliases: true
prefer_if_elements_to_conditional_expressions: true
prefer_if_null_operators: true
prefer_initializing_formals: true
prefer_inlined_adds: true
prefer_int_literals: false
prefer_interpolation_to_compose_strings: true
prefer_is_empty: true
prefer_is_not_empty: true
prefer_is_not_operator: true
prefer_iterable_whereType: true
prefer_mixin: true
prefer_null_aware_method_calls: true
prefer_null_aware_operators: true
prefer_single_quotes: true
prefer_spread_collections: true
prefer_typing_uninitialized_variables: true
provide_deprecation_message: true
public_member_api_docs: false
recursive_getters: true
require_trailing_commas: true
sized_box_for_whitespace: true
sized_box_shrink_expand: true
slash_for_doc_comments: true
sort_child_properties_last: true
sort_constructors_first: true
sort_unnamed_constructors_first: true
tighten_type_of_initializing_formals: true
type_annotate_public_apis: true
type_init_formals: true
unawaited_futures: true
unnecessary_await_in_return: true
unnecessary_brace_in_string_interps: true
unnecessary_const: false
unnecessary_constructor_name: true
unnecessary_final: true
unnecessary_getters_setters: true
unnecessary_lambdas: true
unnecessary_late: true
unnecessary_new: true
unnecessary_null_aware_assignments: true
unnecessary_null_checks: true
unnecessary_null_in_if_null_operators: true
unnecessary_nullable_for_final_variable_declarations: true
unnecessary_overrides: true
unnecessary_parenthesis: true
unnecessary_raw_strings: true
unnecessary_string_escapes: true
unnecessary_string_interpolations: true
unnecessary_this: true
use_decorated_box: true
use_full_hex_values_for_flutter_colors: true
use_function_type_syntax_for_parameters: true
use_if_null_to_convert_nulls_to_bools: true
use_is_even_rather_than_modulo: true
use_late_for_private_fields_and_variables: true
use_named_constants: true
use_raw_strings: false
use_rethrow_when_possible: true
use_setters_to_change_properties: true
use_string_buffers: true
use_test_throws_matchers: true
use_to_and_as_if_applicable: true
void_checks: true
# pub rules
depend_on_referenced_packages: true
lowercase_with_underscores: true
secure_pubspec_urls: false
sort_pub_dependencies: false
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View file

@ -1,45 +0,0 @@
# 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.
version:
revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
channel: stable
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
- platform: android
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
- platform: ios
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
- platform: linux
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
- platform: macos
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
- platform: web
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
- platform: windows
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
# 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

@ -1,214 +0,0 @@
include: package:flutter_lints/flutter.yaml
analyzer:
errors:
todo: ignore
exclude: [lib/generated_plugin_registrant.dart]
linter:
# https://dart.dev/tools/linter-rules#lints
rules:
# error rules
always_use_package_imports: false
avoid_dynamic_calls: true
avoid_empty_else: true
avoid_print: true
avoid_relative_lib_imports: true
avoid_returning_null_for_future: true
avoid_slow_async_io: true
avoid_type_to_string: true
avoid_types_as_parameter_names: true
avoid_web_libraries_in_flutter: true
cancel_subscriptions: true
close_sinks: true
comment_references: false
control_flow_in_finally: true
diagnostic_describe_all_properties: false
empty_statements: true
hash_and_equals: true
invariant_booleans: true
iterable_contains_unrelated_type: true
list_remove_unrelated_type: true
literal_only_boolean_expressions: true
no_adjacent_strings_in_list: true
no_duplicate_case_values: true
no_logic_in_create_state: true
prefer_relative_imports: false
prefer_void_to_null: true
test_types_in_equals: true
throw_in_finally: true
unnecessary_statements: true
unrelated_type_equality_checks: true
unsafe_html: true
use_build_context_synchronously: true
use_key_in_widget_constructors: true
valid_regexps: true
# style rules
always_declare_return_types: true
always_put_control_body_on_new_line: true
always_put_required_named_parameters_first: true
always_require_non_null_named_parameters: true
always_specify_types: false
annotate_overrides: true
avoid_annotating_with_dynamic: false
avoid_bool_literals_in_conditional_expressions: true
avoid_catches_without_on_clauses: false
avoid_catching_errors: false
avoid_classes_with_only_static_members: true
avoid_double_and_int_checks: true
avoid_equals_and_hash_code_on_mutable_classes: false
avoid_escaping_inner_quotes: false
avoid_field_initializers_in_const_classes: true
avoid_final_parameters: true
avoid_function_literals_in_foreach_calls: true
avoid_implementing_value_types: true
avoid_init_to_null: true
avoid_js_rounded_ints: true
avoid_multiple_declarations_per_line: true
avoid_null_checks_in_equality_operators: true
avoid_positional_boolean_parameters: true
avoid_private_typedef_functions: true
avoid_redundant_argument_values: false
avoid_renaming_method_parameters: true
avoid_return_types_on_setters: true
avoid_returning_null: true
avoid_returning_null_for_void: true
avoid_returning_this: true
avoid_setters_without_getters: true
avoid_shadowing_type_parameters: true
avoid_single_cascade_in_expression_statements: true
avoid_types_on_closure_parameters: false
avoid_unnecessary_containers: false
avoid_unused_constructor_parameters: true
avoid_void_async: true
await_only_futures: true
camel_case_extensions: true
camel_case_types: true
cascade_invocations: true
cast_nullable_to_non_nullable: true
conditional_uri_does_not_exist: true
constant_identifier_names: true
curly_braces_in_flow_control_structures: true
deprecated_consistency: true
directives_ordering: true
do_not_use_environment: true
empty_catches: true
empty_constructor_bodies: true
eol_at_end_of_file: true
exhaustive_cases: true
file_names: true
flutter_style_todos: true
implementation_imports: true
join_return_with_assignment: true
leading_newlines_in_multiline_strings: true
library_names: true
library_prefixes: true
library_private_types_in_public_api: true
lines_longer_than_80_chars: true
missing_whitespace_between_adjacent_strings: true
no_default_cases: true
no_leading_underscores_for_library_prefixes: true
no_leading_underscores_for_local_identifiers: true
no_runtimeType_toString: true
non_constant_identifier_names: true
noop_primitive_operations: true
null_check_on_nullable_type_parameter: true
null_closures: true
omit_local_variable_types: true
one_member_abstracts: true
only_throw_errors: true
overridden_fields: true
package_api_docs: true
package_prefixed_library_names: true
parameter_assignments: true
prefer_adjacent_string_concatenation: true
prefer_asserts_in_initializer_lists: true
prefer_asserts_with_message: true
prefer_collection_literals: true
prefer_conditional_assignment: true
prefer_const_constructors: true
prefer_const_constructors_in_immutables: true
prefer_const_declarations: false
prefer_const_literals_to_create_immutables: false
prefer_constructors_over_static_methods: true
prefer_contains: true
prefer_double_quotes: false
prefer_equal_for_default_values: true
prefer_expression_function_bodies: false
prefer_final_fields: true
prefer_final_in_for_each: false
prefer_final_locals: false
prefer_final_parameters: false
prefer_for_elements_to_map_fromIterable: true
prefer_foreach: true
prefer_function_declarations_over_variables: true
prefer_generic_function_type_aliases: true
prefer_if_elements_to_conditional_expressions: true
prefer_if_null_operators: true
prefer_initializing_formals: true
prefer_inlined_adds: true
prefer_int_literals: false
prefer_interpolation_to_compose_strings: true
prefer_is_empty: true
prefer_is_not_empty: true
prefer_is_not_operator: true
prefer_iterable_whereType: true
prefer_mixin: true
prefer_null_aware_method_calls: true
prefer_null_aware_operators: true
prefer_single_quotes: true
prefer_spread_collections: true
prefer_typing_uninitialized_variables: true
provide_deprecation_message: true
public_member_api_docs: false
recursive_getters: true
require_trailing_commas: true
sized_box_for_whitespace: true
sized_box_shrink_expand: true
slash_for_doc_comments: true
sort_child_properties_last: true
sort_constructors_first: true
sort_unnamed_constructors_first: true
tighten_type_of_initializing_formals: true
type_annotate_public_apis: true
type_init_formals: true
unawaited_futures: true
unnecessary_await_in_return: true
unnecessary_brace_in_string_interps: true
unnecessary_const: false
unnecessary_constructor_name: true
unnecessary_final: true
unnecessary_getters_setters: true
unnecessary_lambdas: true
unnecessary_late: true
unnecessary_new: true
unnecessary_null_aware_assignments: true
unnecessary_null_checks: true
unnecessary_null_in_if_null_operators: true
unnecessary_nullable_for_final_variable_declarations: true
unnecessary_overrides: true
unnecessary_parenthesis: true
unnecessary_raw_strings: true
unnecessary_string_escapes: true
unnecessary_string_interpolations: true
unnecessary_this: true
use_decorated_box: true
use_full_hex_values_for_flutter_colors: true
use_function_type_syntax_for_parameters: true
use_if_null_to_convert_nulls_to_bools: true
use_is_even_rather_than_modulo: true
use_late_for_private_fields_and_variables: true
use_named_constants: true
use_raw_strings: false
use_rethrow_when_possible: true
use_setters_to_change_properties: true
use_string_buffers: true
use_test_throws_matchers: true
use_to_and_as_if_applicable: true
void_checks: true
# pub rules
depend_on_referenced_packages: true
lowercase_with_underscores: true
secure_pubspec_urls: false
sort_pub_dependencies: false
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View file

@ -1,182 +0,0 @@
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart';
import 'package:timetable/timetable.dart';
void main() {
runApp(const MaterialApp(home: TimetableDemo()));
}
class TimetableDemo extends StatefulWidget {
const TimetableDemo({Key? key}) : super(key: key);
@override
State<TimetableDemo> createState() => _TimetableDemoState();
}
class _TimetableDemoState extends State<TimetableDemo> {
bool _grouped = false;
bool _horizontal = true;
final ScrollController _scrollController = ScrollController();
final List<TimeBlock> blocks = [
TimeBlock(
start: const TimeOfDay(hour: 14, minute: 0),
end: const TimeOfDay(hour: 15, minute: 0),
id: 0,
),
TimeBlock(
start: const TimeOfDay(hour: 8, minute: 0),
end: const TimeOfDay(hour: 9, minute: 0),
id: 1,
),
TimeBlock(
start: const TimeOfDay(hour: 9, minute: 15),
end: const TimeOfDay(hour: 10, minute: 0),
id: 1,
),
TimeBlock(
start: const TimeOfDay(hour: 10, minute: 15),
end: const TimeOfDay(hour: 11, minute: 0),
child: Container(color: Colors.purple, height: 300, width: 50),
childDimension: 300,
id: 2,
),
TimeBlock(
start: const TimeOfDay(hour: 6, minute: 15),
end: const TimeOfDay(hour: 7, minute: 0),
child: Container(color: Colors.blue, height: 300, width: 300),
childDimension: 300,
id: 2,
),
TimeBlock(
start: const TimeOfDay(hour: 18, minute: 0),
end: const TimeOfDay(hour: 18, minute: 30),
child:
const SizedBox(width: 60, height: 60, child: const Text('High Tea')),
childDimension: 60,
id: 10,
),
TimeBlock(
start: const TimeOfDay(hour: 18, minute: 0),
end: const TimeOfDay(hour: 18, minute: 30),
child: const SizedBox(
height: 60,
width: 60,
child: const Text('High Tea'),
),
childDimension: 60,
id: 10,
),
TimeBlock(
start: const TimeOfDay(hour: 18, minute: 0),
end: const TimeOfDay(hour: 18, minute: 30),
child: const SizedBox(
height: 60,
width: 60,
child: const Text('High Tea'),
),
childDimension: 60,
id: 10,
),
TimeBlock(
start: const TimeOfDay(hour: 18, minute: 0),
end: const TimeOfDay(hour: 18, minute: 30),
child: const SizedBox(
height: 50,
width: 50,
child: const Text('High Tea'),
),
childDimension: 60,
id: 0,
),
TimeBlock(
start: const TimeOfDay(hour: 14, minute: 0),
end: const TimeOfDay(hour: 15, minute: 0),
id: 100,
),
TimeBlock(
start: const TimeOfDay(hour: 14, minute: 0),
end: const TimeOfDay(hour: 15, minute: 0),
id: 101,
),
TimeBlock(
start: const TimeOfDay(hour: 14, minute: 0),
end: const TimeOfDay(hour: 15, minute: 0),
id: 102,
),
TimeBlock(
start: const TimeOfDay(hour: 14, minute: 0),
end: const TimeOfDay(hour: 15, minute: 0),
id: 103,
),
];
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
return Scaffold(
// backgroundColor: Colors.green,
body: Padding(
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
child: Column(
children: [
// toggle between horizontal and vertical
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
onPressed: () {
setState(() {
_horizontal = !_horizontal;
});
},
child: Text(_horizontal ? 'Horizontal' : 'Vertical'),
),
],
),
// toggle between grouped and ungrouped blocks
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Grouped'),
Switch(
value: _grouped,
onChanged: (value) {
setState(() {
_grouped = value;
});
},
),
],
),
Container(
color: Colors.white,
child: Timetable(
size: Size(size.width, size.height * 0.64),
tableDirection: _horizontal ? Axis.horizontal : Axis.vertical,
startHour: 3,
endHour: 24,
timeBlocks: blocks,
scrollController: _scrollController,
combineBlocks: true,
mergeBlocks: _grouped,
theme: const TableTheme(
tablePaddingStart: 0,
blockPaddingBetween: 10,
),
),
),
],
),
),
);
}
}

View file

@ -1,168 +0,0 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.9.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.1"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.1"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.16.0"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
flutter:
dependency: "direct main"
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_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
lints:
dependency: transitive
description:
name: lints
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.12"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.5"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.2"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "1.10.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.1"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.12"
timetable:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
version: "0.0.2"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
sdks:
dart: ">=2.17.6 <3.0.0"
flutter: ">=1.17.0"

View file

@ -1,7 +0,0 @@
import 'package:flutter_test/flutter_test.dart';
void main() {
test('', () {
expect(true, isTrue);
});
}

BIN
flutter_timetable.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 MiB

38
melos.yaml Normal file
View file

@ -0,0 +1,38 @@
name: flutter_timetable
packages:
- packages/**
command:
version:
branch: master
scripts:
lint:all:
run: dart run melos run analyze && dart run melos run format-check
description: Run all static analysis checks.
get:
run: |
melos exec -c 1 -- "flutter pub get"
melos exec --scope="*example*" -c 1 -- "flutter pub get"
upgrade:
run: melos exec -c 1 -- "flutter pub upgrade"
create:
run: melos exec --scope="*example*" -c 1 -- "flutter create ."
analyze:
run: |
dart run melos exec -c 1 -- \
flutter analyze --fatal-infos
description: Run `flutter analyze` for all packages.
format:
run: dart run melos exec dart format .
description: Run `dart format` for all packages.
format-check:
run: dart run melos exec dart format . --set-exit-if-changed
description: Run `dart format` checks for all packages.

View file

@ -0,0 +1 @@
../../CHANGELOG.md

View file

@ -0,0 +1 @@
../../LICENSE

View file

@ -0,0 +1 @@
../../README.md

View file

@ -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

View file

@ -0,0 +1,16 @@
# example
A new Flutter project.
## 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.

View file

@ -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

View file

@ -0,0 +1,23 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:flutter_timetable_firebase/flutter_timetable_firebase.dart';
import 'package:flutter_timetable_interface/flutter_timetable_interface.dart';
class MyRosterModel extends TimetableEvent {
const MyRosterModel({
required super.end,
required super.start,
required super.entityId,
required super.eventId,
this.isSick = false,
});
final bool isSick;
}
final myRosterServiceProvider = Provider(
(ref) => FirebaseTimetableService<MyRosterModel>(
options: const FirebaseTimetableOptions(timetableCollectionName: 'roster'),
),
);
void main() {}

View file

@ -0,0 +1,31 @@
name: flutter_timetable_firebase_example
description: Timetable Widget
publish_to: 'none'
version: 1.1.0+2
environment:
sdk: ">=3.0.0 <4.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
flutter_timetable_firebase:
path: ../
flutter_timetable_interface:
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
version: ^2.0.0
hooks_riverpod: ^2.4.9
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
uses-material-design: true

View file

@ -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:example/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);
});
}

View file

@ -0,0 +1,8 @@
// SPDX-FileCopyrightText: 2023 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
library flutter_timetable_firebase;
export 'src/config/firebase_timetable_options.dart';
export 'src/services/firebase_timetable_service.dart';

View file

@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: 2023 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart';
@immutable
class FirebaseTimetableOptions {
const FirebaseTimetableOptions({
this.timetableCollectionName = 'timetable',
this.cachingStrategy = TimetableCachingStrategy.alwaysFetch,
});
// the collection reference name
final String timetableCollectionName;
/// Changes the Firebase Timetable Service to use different caching approaches
final TimetableCachingStrategy cachingStrategy;
}
enum TimetableCachingStrategy {
// Everytime the timetable is requested it will be fetched from the database
alwaysFetch,
// if you use fetchOnce the timetable events will be fetched only once
fetchOnce,
// will first be fetched and after that updates will be listened to
fetchOnceAndListen,
// if you use listen the timetable events will be updated in real time
listen,
}

View file

@ -0,0 +1,54 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_timetable_firebase/src/config/firebase_timetable_options.dart';
import 'package:flutter_timetable_interface/flutter_timetable_interface.dart';
class FirebaseTimetableService<Event extends TimetableEvent>
with ChangeNotifier
implements TimetableService<Event> {
FirebaseTimetableService({
FirebaseApp? app,
options = const FirebaseTimetableOptions(),
}) {
var appInstance = app ?? Firebase.app();
_db = FirebaseFirestore.instanceFor(app: appInstance);
_options = options;
}
late FirebaseTimetableOptions _options;
late FirebaseFirestore _db;
@override
Future<void> addEvent(Event event) async {
event.toJson();
throw UnimplementedError();
}
@override
Future<bool> checkForConflict(Event event, DateTime day) async {
throw UnimplementedError();
}
@override
Future<void> deleteEvent(Event event) async {
throw UnimplementedError();
}
@override
Future<List<Event>> fetchEventsForDay(DateTime day,
{String? category}) async {
throw UnimplementedError();
}
@override
List<Event> getEventsForDay(DateTime day, {String? category}) {
throw UnimplementedError();
}
@override
Future<void> updateEvent(Event event) async {
throw UnimplementedError();
}
}

View file

@ -0,0 +1,27 @@
# SPDX-FileCopyrightText: 2023 Iconica
#
# SPDX-License-Identifier: GPL-3.0-or-later
name: flutter_timetable_firebase
description: A new Flutter package project.
version: 2.0.0
repository: https://github.com/Iconica-Development/flutter_timetable
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
environment:
sdk: ">=3.1.0 <4.0.0"
flutter: ">=1.17.0"
dependencies:
cloud_firestore: ^4.13.3
firebase_core: ^2.24.0
flutter:
sdk: flutter
flutter_timetable_interface:
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
version: ^2.0.0
dev_dependencies:
flutter_lints: ^2.0.0

View file

@ -0,0 +1 @@
../../CHANGELOG.md

View file

@ -0,0 +1 @@
../../LICENSE

View file

@ -0,0 +1 @@
../../README.md

View file

@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View file

@ -0,0 +1,8 @@
// SPDX-FileCopyrightText: 2023 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
library flutter_timetable_interface;
export 'src/models/timetable_event.dart';
export 'src/services/timetable_service.dart';

View file

@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: 2023 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart';
@immutable
class TimetableEvent {
/// The model used for a [Block] in a [TimeTable] which can contain a Widget.
const TimetableEvent({
required this.start,
required this.end,
required this.entityId,
required this.eventId,
this.category,
});
/// The date at which the event starts
final DateTime start;
/// The date at which the event ends
final DateTime end;
/// The unique identifier of the event
/// This is used to store the events
final String eventId;
/// The identifier of the entity that the event belongs to
/// This is used to check for conflicts between entities
final String entityId;
/// This can be used to filter events
final String? category;
// tojson method and a factory fromJson contructor
Map<String, dynamic> toJson() => {};
factory TimetableEvent.fromJson(String eventId, Map<String, dynamic> json) =>
TimetableEvent(
start: json['start'],
end: json['end'],
entityId: json[''],
eventId: eventId,
);
}

View file

@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
abstract class TimetableService<Event> with ChangeNotifier {
Future<List<Event>> fetchEventsForDay(DateTime day, {String? category});
List<Event> getEventsForDay(DateTime day, {String? category});
Future<void> addEvent(Event event);
Future<void> updateEvent(Event event);
Future<void> deleteEvent(Event event);
Future<bool> checkForConflict(Event event, DateTime day);
}

View file

@ -0,0 +1,21 @@
# SPDX-FileCopyrightText: 2023 Iconica
#
# SPDX-License-Identifier: GPL-3.0-or-later
name: flutter_timetable_interface
description: A new Flutter package project.
version: 2.0.0
repository: https://github.com/Iconica-Development/flutter_timetable
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
environment:
sdk: ">=3.1.0 <4.0.0"
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_lints: ^2.0.0

View file

@ -0,0 +1 @@
../../CHANGELOG.md

View file

@ -0,0 +1 @@
../../LICENSE

View file

@ -0,0 +1 @@
../../README.md

View file

@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View file

@ -19,7 +19,7 @@ migrate_working_dir/
# 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/
.vscode/
# Flutter/Dart/Pub related
**/doc/api/
@ -45,3 +45,6 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release
pubspec.lock
.metadata

View file

@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View file

@ -0,0 +1,172 @@
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart';
import 'package:flutter_timetable_view/flutter_timetable_view.dart';
void main() {
runApp(const MaterialApp(home: TimetableDemo()));
}
class TimetableDemo extends StatefulWidget {
const TimetableDemo({Key? key}) : super(key: key);
@override
State<TimetableDemo> createState() => _TimetableDemoState();
}
class _TimetableDemoState extends State<TimetableDemo> {
bool _grouped = false;
bool _horizontal = true;
final ScrollController _scrollController = ScrollController();
final List<TimeBlock> blocks = [
TimeBlock(
start: const TimeOfDay(hour: 8, minute: 0),
end: const TimeOfDay(hour: 9, minute: 0),
child: Container(
color: Colors.red,
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'Exercise',
style: TextStyle(color: Colors.white),
),
),
),
id: 3,
),
TimeBlock(
start: const TimeOfDay(hour: 10, minute: 0),
end: const TimeOfDay(hour: 12, minute: 0),
child: Container(
color: Colors.orange,
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'Brunch',
style: TextStyle(color: Colors.white),
),
),
),
childDimension: 300,
id: 1,
),
TimeBlock(
start: const TimeOfDay(hour: 14, minute: 0),
end: const TimeOfDay(hour: 15, minute: 0),
id: 100,
child: const SizedBox(
height: 300,
child: Text(
'Clean Living Room',
style: TextStyle(color: Colors.white),
),
)),
TimeBlock(
start: const TimeOfDay(hour: 14, minute: 0),
end: const TimeOfDay(hour: 15, minute: 0),
id: 101,
child: const SizedBox(
height: 200,
child: Text(
'Clean Kitchen',
style: TextStyle(color: Colors.white),
),
)),
TimeBlock(
start: const TimeOfDay(hour: 14, minute: 0),
end: const TimeOfDay(hour: 15, minute: 0),
id: 102,
child: const SizedBox(
height: 100,
child: Text(
'Clean Bathroom',
style: TextStyle(color: Colors.white),
),
),
),
TimeBlock(
start: const TimeOfDay(hour: 14, minute: 0),
end: const TimeOfDay(hour: 15, minute: 0),
id: 103,
child: const SizedBox(
height: 50,
child: Text(
'Clean Toilet',
style: TextStyle(color: Colors.white),
),
),
),
];
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
return Scaffold(
appBar: AppBar(
title: const Text('Timetable Demo'),
),
// backgroundColor: Colors.green,
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// toggle between horizontal and vertical
const Text('Axis horizontal'),
Switch(
value: _horizontal,
onChanged: (value) {
setState(() {
_horizontal = value;
});
},
),
// toggle between grouped and ungrouped blocks
const Text('Grouped'),
Switch(
value: _grouped,
onChanged: (value) {
setState(() {
_grouped = value;
});
},
),
],
),
),
Container(
color: Colors.white,
child: Timetable(
onOverScroll: () {},
onUnderScroll: () {},
hoursOffset: 6,
sortByIdAscending: true,
size: Size(size.width, size.height * 0.64),
tableDirection: _horizontal ? Axis.horizontal : Axis.vertical,
startHour: 0,
endHour: 24,
timeBlocks: blocks,
scrollController: _scrollController,
combineBlocks: true,
mergeBlocks: _grouped,
theme: const TableTheme(
tablePaddingStart: 0,
blockPaddingBetween: 10,
),
),
),
],
),
);
}
}

View file

@ -1,20 +1,22 @@
name: timetable_example
name: flutter_timetable_view_example
description: Timetable Widget
publish_to: 'none'
version: 1.0.0+1
version: 1.1.0+2
environment:
sdk: ">=2.17.6 <3.0.0"
sdk: ">=3.0.0 <4.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
timetable:
flutter_timetable_view:
path: ../
flutter_timetable_interface:
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
dev_dependencies:
flutter_test:

View file

@ -0,0 +1,7 @@
// import 'package:flutter_test/flutter_test.dart';
// void main() {
// test('', () {
// expect(true, isTrue);
// });
// }

View file

@ -2,10 +2,10 @@
//
// SPDX-License-Identifier: BSD-3-Clause
library timetable;
library flutter_timetable;
export 'src/models/table_theme.dart';
export 'src/models/time_block.dart';
export 'src/timetable.dart';
export 'src/widgets/block.dart';
export 'src/widgets/table.dart';
export 'src/models/time_block.dart';

View file

@ -3,7 +3,7 @@
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart';
import 'package:timetable/src/models/time_block.dart';
import 'package:flutter_timetable_view/src/models/time_block.dart';
/// Combine blocks that have the same id and the same time.
List<TimeBlock> combineBlocksWithId(List<TimeBlock> blocks) {
@ -39,6 +39,7 @@ List<TimeBlock> combineBlocksWithId(List<TimeBlock> blocks) {
return newBlocks;
}
/// Combines grouped blocks into one block.
void _combineGroupedBlocks(
List<List<TimeBlock>> groupedBlocks,
List<TimeBlock> newBlocks,
@ -74,6 +75,10 @@ void _combineGroupedBlocks(
}
}
/// Checks if a block with a certain id exists in the grouped blocks.
///
/// Returns true if a block with the same id, start time, and end time exists in the grouped blocks list.
/// Otherwise, returns false.
bool _checkIfBlockWithIdExists(
List<List<TimeBlock>> groupedBlocks,
TimeBlock block,

View file

@ -6,11 +6,12 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:timetable/src/block_service.dart';
import 'package:timetable/src/models/table_theme.dart';
import 'package:timetable/src/models/time_block.dart';
import 'package:timetable/src/widgets/block.dart';
import 'package:timetable/src/widgets/table.dart' as table;
import 'package:flutter_timetable_view/src/block_service.dart';
import 'package:flutter_timetable_view/src/models/table_theme.dart';
import 'package:flutter_timetable_view/src/models/time_block.dart';
import 'package:flutter_timetable_view/src/widgets/block.dart';
import 'package:flutter_timetable_view/src/widgets/table.dart' as table;
class Timetable extends StatefulWidget {
/// [Timetable] widget that displays a timetable with [TimeBlock]s.
@ -22,18 +23,34 @@ class Timetable extends StatefulWidget {
this.tableDirection = Axis.vertical,
this.timeBlocks = const [],
this.size,
this.initialScrollTime,
this.scrollController,
this.scrollPhysics,
this.hoursOffset = 0,
this.startHour = 0,
this.endHour = 24,
this.blockDimension = 50,
this.blockColor = const Color(0x80FF0000),
this.blockColor = Colors.blue,
this.hourDimension = 80,
this.theme = const TableTheme(),
this.mergeBlocks = false,
this.combineBlocks = true,
Key? key,
}) : super(key: key);
this.sortByIdAscending = false,
this.sortByStartTime = false,
this.onOverScroll,
this.onUnderScroll,
this.scrollTriggerOffset = 120,
this.scrollJumpToOffset = 115,
super.key,
}) : assert(
scrollTriggerOffset > scrollJumpToOffset,
'ScrollTriggerOffset cannot be smaller'
' then the scrollJumpToOffset.',
),
assert(
!(mergeBlocks && sortByIdAscending),
'mergeBlocks and sortByIdAscending'
' cannot be enabled at the same time.');
/// The Axis in which the table is layed out.
final Axis tableDirection;
@ -47,6 +64,10 @@ class Timetable extends StatefulWidget {
/// Hour at which the timetable ends.
final int endHour;
/// The time offset to increase all hour labels with
/// this is used to make the timetable start at a different time and go past midnight.
final int hoursOffset;
/// The time blocks that will be displayed in the timetable.
final List<TimeBlock> timeBlocks;
@ -63,6 +84,9 @@ class Timetable extends StatefulWidget {
/// The theme of the timetable.
final TableTheme theme;
/// The initial time to scroll to if there are no timeblocks. If nothing is provided it will scroll to the current time or to the first block if there is one.
final TimeOfDay? initialScrollTime;
/// The scroll controller to control the scrolling of the timetable.
final ScrollController? scrollController;
@ -76,6 +100,21 @@ class Timetable extends StatefulWidget {
/// If blocks have the same id and time they will be combined into one block.
final bool combineBlocks;
/// Whether or not to sort blocks by their ID in ascending order.
final bool sortByIdAscending;
/// Whether or not to sort blocks by their StartTime.
final bool sortByStartTime;
/// The offset which trigger the jump to either the previous or next page. Can't be lower then [scrollJumpToOffset].
final double scrollTriggerOffset;
/// When the jump is triggered this offset will be jumped outside of the min or max offset. Can't be higher then [scrollTriggerOffset].
final double scrollJumpToOffset;
final void Function()? onUnderScroll;
final void Function()? onOverScroll;
@override
State<Timetable> createState() => _TimetableState();
}
@ -86,11 +125,46 @@ class _TimetableState extends State<Timetable> {
@override
void initState() {
super.initState();
_scrollController =
widget.scrollController ?? ScrollController(initialScrollOffset: 0);
if (widget.timeBlocks.isNotEmpty) {
_scrollToFirstBlock();
} else {
_scrollToInitialTime();
}
if (widget.onUnderScroll != null && widget.onOverScroll != null) {
_scrollController.addListener(() {
if (_scrollController.offset -
_scrollController.position.maxScrollExtent >
widget.scrollTriggerOffset) {
if (widget.onOverScroll != null) {
_scrollController.jumpTo(
_scrollController.position.minScrollExtent -
widget.scrollJumpToOffset);
widget.onOverScroll?.call();
}
} else if (_scrollController.position.minScrollExtent -
_scrollController.offset >
widget.scrollTriggerOffset) {
if (widget.onUnderScroll != null) {
_scrollController.jumpTo(
_scrollController.position.maxScrollExtent +
widget.scrollJumpToOffset);
widget.onUnderScroll?.call();
}
}
});
}
}
int compareTimeOfDay(TimeOfDay time1, TimeOfDay time2) {
var totalMinutes1 = time1.hour * 60 + time1.minute;
var totalMinutes2 = time2.hour * 60 + time2.minute;
return totalMinutes1.compareTo(totalMinutes2);
}
@override
@ -109,6 +183,18 @@ class _TimetableState extends State<Timetable> {
} else {
blocks = widget.timeBlocks;
}
if (widget.sortByIdAscending) {
// if the id is zero then put it at the end
blocks.sort((a, b) => (a.id != 0 ? a.id : double.infinity).compareTo(
(b.id != 0 ? b.id : double.infinity),
));
}
if (widget.sortByStartTime) {
blocks.sort((a, b) => compareTimeOfDay(a.start, b.start));
}
var linePadding = _calculateTableTextSize().width;
return SizedBox(
width: widget.size?.width,
@ -121,6 +207,7 @@ class _TimetableState extends State<Timetable> {
alignment: Alignment.topLeft,
children: [
table.Table(
hoursOffset: widget.hoursOffset,
tableDirection: widget.tableDirection,
startHour: widget.startHour,
endHour: widget.endHour,
@ -224,32 +311,28 @@ class _TimetableState extends State<Timetable> {
);
}
Size _calculateTableStart(Axis axis) {
return Size(
(axis == Axis.horizontal)
? _calculateTableTextSize().width / 2
: _calculateTableTextSize().width +
widget.theme.tablePaddingStart +
widget.theme.tableTextOffset,
(axis == Axis.vertical)
? _calculateTableTextSize().height / 2
: _calculateTableTextSize().height,
);
}
Size _calculateTableStart(Axis axis) => Size(
(axis == Axis.horizontal)
? _calculateTableTextSize().width / 2
: _calculateTableTextSize().width +
widget.theme.tablePaddingStart +
widget.theme.tableTextOffset,
(axis == Axis.vertical)
? _calculateTableTextSize().height / 2
: _calculateTableTextSize().height,
);
Widget _showBlock(TimeBlock block, {double linePadding = 0}) {
return Block(
blockDirection: widget.tableDirection,
linePadding: linePadding,
start: block.start,
end: block.end,
startHour: widget.startHour,
hourDimension: widget.hourDimension,
blockDimension: widget.blockDimension,
blockColor: widget.blockColor,
child: block.child,
);
}
Widget _showBlock(TimeBlock block, {double linePadding = 0}) => Block(
blockDirection: widget.tableDirection,
linePadding: linePadding,
start: block.start,
end: block.end,
startHour: widget.startHour,
hourDimension: widget.hourDimension,
blockDimension: widget.blockDimension,
blockColor: widget.blockColor,
child: block.child,
);
void _scrollToFirstBlock() {
SchedulerBinding.instance.addPostFrameCallback((_) {
@ -271,19 +354,32 @@ class _TimetableState extends State<Timetable> {
});
}
Size _calculateTableTextSize() {
return (TextPainter(
text: TextSpan(
text: '22:22',
style: widget.theme.timeStyle ?? Theme.of(context).textTheme.bodyText1,
),
maxLines: 1,
textScaleFactor: MediaQuery.of(context).textScaleFactor,
textDirection: TextDirection.ltr,
)..layout())
.size;
void _scrollToInitialTime() {
SchedulerBinding.instance.addPostFrameCallback((_) {
var startingTime = widget.initialScrollTime ?? TimeOfDay.now();
var initialOffset =
(widget.hourDimension * (widget.endHour - widget.startHour)) *
((startingTime.hour - widget.startHour) /
(widget.endHour - widget.startHour)) +
_calculateTableTextSize().width / 2;
_scrollController.jumpTo(
initialOffset,
);
});
}
Size _calculateTableTextSize() => (TextPainter(
text: TextSpan(
text: '22:22',
style:
widget.theme.timeStyle ?? Theme.of(context).textTheme.bodyLarge,
),
maxLines: 1,
textScaler: MediaQuery.of(context).textScaler,
textDirection: TextDirection.ltr,
)..layout())
.size;
double calculateTableHeight() {
var sum = 0.0;
if (widget.mergeBlocks || widget.combineBlocks) {

View file

@ -13,7 +13,7 @@ class Block extends StatelessWidget {
required this.blockDimension,
required this.hourDimension,
required this.blockDirection,
this.blockColor = const Color(0x80FF0000),
this.blockColor = Colors.blue,
this.linePadding = 8,
this.child,
Key? key,
@ -49,6 +49,7 @@ class Block extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
color: blockColor,
margin: EdgeInsets.only(
top: (blockDirection == Axis.vertical)
? (((start.hour - startHour) * Duration.minutesPerHour) +
@ -76,10 +77,9 @@ class Block extends StatelessWidget {
_sizePerMinute()
: null,
child: child ??
Container(
SizedBox(
height: (blockDirection == Axis.horizontal) ? blockDimension : 0,
width: (blockDirection == Axis.vertical) ? blockDimension : 0,
color: blockColor,
),
);
}

View file

@ -3,20 +3,21 @@
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart';
import 'package:timetable/src/models/table_theme.dart';
import 'package:flutter_timetable_view/src/models/table_theme.dart';
class Table extends StatelessWidget {
/// The [Table] to draw an overview of timerange with corresponding hour lines
const Table({
required this.startHour,
required this.endHour,
this.hoursOffset = 0,
this.size,
this.tableDirection = Axis.vertical,
this.hourDimension = 80,
this.tableOffset = 20,
this.theme = const TableTheme(),
Key? key,
}) : super(key: key);
super.key,
});
/// The [Axis] in which the table is layed out.
final Axis tableDirection;
@ -30,6 +31,9 @@ class Table extends StatelessWidget {
/// The hour the table ends at.
final int endHour;
/// The time offset to increase all hour labels with
final int hoursOffset;
/// The length in pixel of a single hour in the table.
final double hourDimension;
@ -55,10 +59,10 @@ class Table extends StatelessWidget {
Column(
children: [
Text(
'${((i == 24) ? '00' : i.toString()).padLeft(2, '0')}'
'${(((i + hoursOffset) == 24) ? '00' : ((i + hoursOffset) % 24).toString()).padLeft(2, '0')}'
':00',
style: theme.timeStyle ??
Theme.of(context).textTheme.bodyText1,
Theme.of(context).textTheme.bodyLarge,
),
SizedBox(height: theme.tableTextOffset),
Container(
@ -124,9 +128,9 @@ class Table extends StatelessWidget {
Row(
children: [
Text(
'${i.toString().padLeft(2, '0')}:00',
'${((i + hoursOffset) % 24).toString().padLeft(2, '0')}:00',
style: theme.timeStyle ??
Theme.of(context).textTheme.bodyText1,
Theme.of(context).textTheme.bodyLarge,
),
SizedBox(
width: theme.tableTextOffset,
@ -183,7 +187,7 @@ class Table extends StatelessWidget {
var textPainter = TextPainter(
text: TextSpan(
text: text,
style: theme.timeStyle ?? Theme.of(context).textTheme.bodyText1,
style: theme.timeStyle ?? Theme.of(context).textTheme.bodyLarge,
),
textDirection: TextDirection.ltr,
)..layout();

View file

@ -0,0 +1,22 @@
name: flutter_timetable_view
description: Flutter package to create a Timetable Widget that display blocks of widgets inside a timetable.
version: 2.0.0
repository: https://github.com/Iconica-Development/flutter_timetable
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
environment:
sdk: ">=3.2.0 <4.0.0"
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
flutter_timetable_interface:
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
version: ^2.0.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0

View file

@ -4,8 +4,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:timetable/src/block_service.dart';
import 'package:timetable/timetable.dart';
import 'package:flutter_timetable_view/src/block_service.dart';
import 'package:flutter_timetable_view/src/models/time_block.dart';
void main() {
group('test combineBlocksWithId', () {

View file

@ -1,19 +1,6 @@
name: timetable
description: Flutter package to create a Timetable Widget that display blocks of widgets inside a timetable.
version: 0.0.2
repository: https://github.com/Iconica-Development/timetable
name: flutter_timetable_workspace
environment:
sdk: ">=2.17.6 <3.0.0"
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
sdk: '>=3.1.0 <4.0.0'
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
melos: ^3.0.1